@kody-ade/kody-engine 0.4.222 → 0.4.224

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
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.222",
18
+ version: "0.4.224",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -146,6 +146,12 @@ var init_claudeBinary = __esm({
146
146
  // src/config.ts
147
147
  import * as fs2 from "fs";
148
148
  import * as path2 from "path";
149
+ function parseReasoningEffort(raw) {
150
+ if (!raw) return null;
151
+ const v = raw.trim().toLowerCase();
152
+ if (REASONING_EFFORTS.includes(v)) return v;
153
+ return null;
154
+ }
149
155
  function parseProviderModel(s) {
150
156
  const slash = s.indexOf("/");
151
157
  if (slash <= 0 || slash === s.length - 1) {
@@ -198,7 +204,8 @@ function loadConfig(projectDir = process.cwd()) {
198
204
  },
199
205
  agent: {
200
206
  model: String(agent.model),
201
- ...parsePerExecutable(agent.perExecutable)
207
+ ...parsePerExecutable(agent.perExecutable),
208
+ ...parseAgentReasoningEffort(agent.reasoningEffort)
202
209
  },
203
210
  issueContext: parseIssueContext(raw.issueContext),
204
211
  testRequirements: parseTestRequirements(raw.testRequirements),
@@ -275,6 +282,10 @@ function parsePerExecutable(raw) {
275
282
  }
276
283
  return Object.keys(out).length > 0 ? { perExecutable: out } : {};
277
284
  }
285
+ function parseAgentReasoningEffort(raw) {
286
+ if (typeof raw !== "string") return {};
287
+ return { reasoningEffort: parseReasoningEffort(raw) ?? void 0 };
288
+ }
278
289
  function parseClassifyConfig(raw) {
279
290
  if (!raw || typeof raw !== "object") return void 0;
280
291
  const r = raw;
@@ -327,10 +338,16 @@ function parseTestRequirements(raw) {
327
338
  function getAnthropicApiKeyOrDummy() {
328
339
  return process.env.ANTHROPIC_API_KEY || `sk-ant-api03-${"0".repeat(64)}`;
329
340
  }
330
- var LITELLM_DEFAULT_PORT, LITELLM_DEFAULT_URL, GITHUB_AUTHOR_ASSOCIATIONS, DEFAULT_ALLOWED_ASSOCIATIONS, BUILTIN_ALIASES;
341
+ var REASONING_EFFORTS, REASONING_BUDGETS, LITELLM_DEFAULT_PORT, LITELLM_DEFAULT_URL, GITHUB_AUTHOR_ASSOCIATIONS, DEFAULT_ALLOWED_ASSOCIATIONS, BUILTIN_ALIASES;
331
342
  var init_config = __esm({
332
343
  "src/config.ts"() {
333
344
  "use strict";
345
+ REASONING_EFFORTS = ["off", "low", "medium", "high"];
346
+ REASONING_BUDGETS = {
347
+ low: 2048,
348
+ medium: 1e4,
349
+ high: 32e3
350
+ };
334
351
  LITELLM_DEFAULT_PORT = 4e3;
335
352
  LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
336
353
  GITHUB_AUTHOR_ASSOCIATIONS = [
@@ -1597,6 +1614,10 @@ function classifySubtype(subtype) {
1597
1614
  if (lower.includes("error")) return "model_error";
1598
1615
  return "generic_failed";
1599
1616
  }
1617
+ function isClaudeLoginRequiredText(text) {
1618
+ const normalized = text.toLowerCase();
1619
+ return normalized.includes("not logged in") && normalized.includes("/login");
1620
+ }
1600
1621
  function resolveTurnTimeoutMs(opts) {
1601
1622
  if (opts.maxTurnTimeoutMs !== void 0 && opts.maxTurnTimeoutMs !== null) {
1602
1623
  return opts.maxTurnTimeoutMs > 0 ? opts.maxTurnTimeoutMs : 0;
@@ -1664,17 +1685,22 @@ async function runAgent(opts) {
1664
1685
  let finalText = "";
1665
1686
  let getSubmitted;
1666
1687
  for (let attempt = 0; ; attempt++) {
1688
+ let ndjsonWriteFailed = false;
1689
+ let ndjsonWriteError;
1667
1690
  const fullLog = fs5.createWriteStream(ndjsonPath, { flags: "w" });
1691
+ fullLog.on("error", (err) => {
1692
+ ndjsonWriteFailed = true;
1693
+ ndjsonWriteError = err instanceof Error ? err.message : String(err);
1694
+ });
1668
1695
  const resultTexts = [];
1669
1696
  outcome = "failed";
1670
1697
  outcomeKind = "generic_failed";
1671
1698
  errorMessage = void 0;
1672
1699
  tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
1673
1700
  messageCount = 0;
1674
- let ndjsonWriteFailed = false;
1675
- let ndjsonWriteError;
1676
1701
  let sawMutatingTool = false;
1677
1702
  let sawTerminalSuccess = false;
1703
+ let sawLoginRequired = false;
1678
1704
  let noWorkSuccess = false;
1679
1705
  try {
1680
1706
  const queryOptions = {
@@ -1747,7 +1773,13 @@ async function runAgent(opts) {
1747
1773
  if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
1748
1774
  queryOptions.maxTurns = opts.maxTurns;
1749
1775
  }
1750
- if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
1776
+ if (opts.reasoningEffort !== void 0 && opts.reasoningEffort !== null) {
1777
+ if (opts.reasoningEffort === "off") {
1778
+ } else {
1779
+ const budget = REASONING_BUDGETS[opts.reasoningEffort];
1780
+ if (budget) queryOptions.maxThinkingTokens = budget;
1781
+ }
1782
+ } else if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
1751
1783
  queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
1752
1784
  }
1753
1785
  if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
@@ -1808,16 +1840,21 @@ async function runAgent(opts) {
1808
1840
  if (next.done) break;
1809
1841
  const msg = next.value;
1810
1842
  messageCount++;
1811
- try {
1812
- fullLog.write(`${JSON.stringify(msg)}
1843
+ if (!ndjsonWriteFailed) {
1844
+ try {
1845
+ fullLog.write(`${JSON.stringify(msg)}
1813
1846
  `);
1814
- } catch (e) {
1815
- ndjsonWriteFailed = true;
1816
- ndjsonWriteError = e instanceof Error ? e.message : String(e);
1847
+ } catch (e) {
1848
+ ndjsonWriteFailed = true;
1849
+ ndjsonWriteError = e instanceof Error ? e.message : String(e);
1850
+ }
1817
1851
  }
1818
1852
  const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
1819
- if (line) process.stdout.write(`${line}
1853
+ if (line) {
1854
+ if (isClaudeLoginRequiredText(line)) sawLoginRequired = true;
1855
+ process.stdout.write(`${line}
1820
1856
  `);
1857
+ }
1821
1858
  const m = msg;
1822
1859
  if (opts.onProgress) {
1823
1860
  const blocks = m.message?.content ?? [];
@@ -1890,6 +1927,7 @@ async function runAgent(opts) {
1890
1927
  outcomeKind = "ok";
1891
1928
  sawTerminalSuccess = true;
1892
1929
  const text = (typeof m.result === "string" ? m.result : "").trim();
1930
+ if (isClaudeLoginRequiredText(text)) sawLoginRequired = true;
1893
1931
  if (text) resultTexts.push(text);
1894
1932
  } else {
1895
1933
  outcome = "failed";
@@ -1919,6 +1957,11 @@ async function runAgent(opts) {
1919
1957
  );
1920
1958
  }
1921
1959
  finalText = resultTexts.join("\n\n---\n\n");
1960
+ if (outcome === "completed" && sawLoginRequired) {
1961
+ outcome = "failed";
1962
+ outcomeKind = "model_error";
1963
+ errorMessage = "Claude Code reported it is not logged in; refusing to mark agent run successful";
1964
+ }
1922
1965
  if (outcome === "completed" && !sawMutatingTool) {
1923
1966
  const backendDead = opts.isBackendHealthy ? !await opts.isBackendHealthy() : false;
1924
1967
  const zeroOutput = tokens.output === 0 && finalText === "";
@@ -1982,6 +2025,99 @@ var init_agent = __esm({
1982
2025
  }
1983
2026
  });
1984
2027
 
2028
+ // src/companyStore.ts
2029
+ import { execFileSync as execFileSync2 } from "child_process";
2030
+ import * as crypto2 from "crypto";
2031
+ import * as fs6 from "fs";
2032
+ import * as os2 from "os";
2033
+ import * as path6 from "path";
2034
+ function getCompanyStoreRoot() {
2035
+ const store = resolveCompanyStore();
2036
+ if (!store) return null;
2037
+ const key = `${store.repo}#${store.ref}`;
2038
+ if (memo?.key === key) return memo.root;
2039
+ const root = fetchCompanyStore(store.repo, store.ref);
2040
+ memo = { key, root };
2041
+ return root;
2042
+ }
2043
+ function getCompanyStoreAssetRoot(kind) {
2044
+ const root = getCompanyStoreRoot();
2045
+ if (!root) return null;
2046
+ return path6.join(root, ".kody", kind);
2047
+ }
2048
+ function resolveCompanyStore() {
2049
+ const envStore = process.env[STORE_ENV]?.trim();
2050
+ if (!envStore && process.env.VITEST) return null;
2051
+ const repo = envStore || DEFAULT_COMPANY_STORE;
2052
+ if (repo === "0" || repo === "false" || repo === "off") return null;
2053
+ const ref = process.env[REF_ENV]?.trim() || DEFAULT_COMPANY_STORE_REF;
2054
+ if (!ref) return null;
2055
+ return { repo, ref };
2056
+ }
2057
+ function fetchCompanyStore(repo, ref) {
2058
+ const url = repoToGitUrl(repo);
2059
+ const cacheDir = path6.join(cacheRoot(), cacheKey(repo, ref));
2060
+ try {
2061
+ if (isGitHubShorthand(repo)) setupGithubGitAuth();
2062
+ fs6.mkdirSync(path6.dirname(cacheDir), { recursive: true });
2063
+ if (!fs6.existsSync(path6.join(cacheDir, ".git"))) {
2064
+ fs6.rmSync(cacheDir, { recursive: true, force: true });
2065
+ runGit(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
2066
+ }
2067
+ runGit(["-C", cacheDir, "remote", "set-url", "origin", url]);
2068
+ runGit(["-C", cacheDir, "fetch", "--depth=1", "origin", ref]);
2069
+ runGit(["-C", cacheDir, "checkout", "--detach", "FETCH_HEAD"]);
2070
+ return cacheDir;
2071
+ } catch (err) {
2072
+ const msg = err instanceof Error ? err.message : String(err);
2073
+ process.stderr.write(`[company-store] failed to fetch ${repo}#${ref}: ${msg}
2074
+ `);
2075
+ return null;
2076
+ }
2077
+ }
2078
+ function repoToGitUrl(repo) {
2079
+ if (/^(https?:|ssh:|git@|file:)/.test(repo)) return repo;
2080
+ if (isGitHubShorthand(repo)) {
2081
+ return `https://github.com/${repo}.git`;
2082
+ }
2083
+ return repo;
2084
+ }
2085
+ function isGitHubShorthand(repo) {
2086
+ return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo);
2087
+ }
2088
+ function cacheRoot() {
2089
+ return process.env[CACHE_ENV]?.trim() || path6.join(os2.homedir(), ".cache", "kody", "company-store");
2090
+ }
2091
+ function cacheKey(repo, ref) {
2092
+ return crypto2.createHash("sha256").update(`${repo}#${ref}`).digest("hex").slice(0, 24);
2093
+ }
2094
+ function runGit(args) {
2095
+ execFileSync2("git", args, { stdio: ["ignore", "ignore", "pipe"] });
2096
+ }
2097
+ function setupGithubGitAuth() {
2098
+ const token = process.env.KODY_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_PAT;
2099
+ if (!token) return;
2100
+ try {
2101
+ execFileSync2("gh", ["auth", "setup-git", "--hostname", "github.com"], {
2102
+ env: { ...process.env, GH_TOKEN: token },
2103
+ stdio: ["ignore", "ignore", "pipe"]
2104
+ });
2105
+ } catch {
2106
+ }
2107
+ }
2108
+ var DEFAULT_COMPANY_STORE, DEFAULT_COMPANY_STORE_REF, STORE_ENV, REF_ENV, CACHE_ENV, memo;
2109
+ var init_companyStore = __esm({
2110
+ "src/companyStore.ts"() {
2111
+ "use strict";
2112
+ DEFAULT_COMPANY_STORE = "aharonyaircohen/kody-company-store";
2113
+ DEFAULT_COMPANY_STORE_REF = "stable";
2114
+ STORE_ENV = "KODY_COMPANY_STORE";
2115
+ REF_ENV = "KODY_COMPANY_STORE_REF";
2116
+ CACHE_ENV = "KODY_COMPANY_STORE_CACHE";
2117
+ memo = null;
2118
+ }
2119
+ });
2120
+
1985
2121
  // src/scripts/scheduleEvery.ts
1986
2122
  function isScheduleEvery(value) {
1987
2123
  return typeof value === "string" && SCHEDULE_EVERY_VALUES.includes(value);
@@ -2033,30 +2169,30 @@ var init_scheduleEvery = __esm({
2033
2169
  });
2034
2170
 
2035
2171
  // src/dutyFolders.ts
2036
- import * as fs6 from "fs";
2037
- import * as path6 from "path";
2172
+ import * as fs7 from "fs";
2173
+ import * as path7 from "path";
2038
2174
  function listDutyFolderSlugs(absDir) {
2039
- if (!fs6.existsSync(absDir)) return [];
2175
+ if (!fs7.existsSync(absDir)) return [];
2040
2176
  let entries;
2041
2177
  try {
2042
- entries = fs6.readdirSync(absDir, { withFileTypes: true });
2178
+ entries = fs7.readdirSync(absDir, { withFileTypes: true });
2043
2179
  } catch {
2044
2180
  return [];
2045
2181
  }
2046
- return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isDutyFolder(path6.join(absDir, e.name))).map((e) => e.name).sort();
2182
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isDutyFolder(path7.join(absDir, e.name))).map((e) => e.name).sort();
2047
2183
  }
2048
2184
  function isDutyFolder(dir) {
2049
- return fs6.existsSync(path6.join(dir, DUTY_PROFILE_FILE)) && fs6.existsSync(path6.join(dir, DUTY_BODY_FILE));
2185
+ return fs7.existsSync(path7.join(dir, DUTY_PROFILE_FILE)) && fs7.existsSync(path7.join(dir, DUTY_BODY_FILE));
2050
2186
  }
2051
2187
  function readDutyFolder(root, slug) {
2052
- const dir = path6.join(root, slug);
2053
- const profilePath = path6.join(dir, DUTY_PROFILE_FILE);
2054
- const bodyPath = path6.join(dir, DUTY_BODY_FILE);
2055
- if (!fs6.existsSync(profilePath) || !fs6.statSync(profilePath).isFile()) return null;
2056
- if (!fs6.existsSync(bodyPath) || !fs6.statSync(bodyPath).isFile()) return null;
2188
+ const dir = path7.join(root, slug);
2189
+ const profilePath = path7.join(dir, DUTY_PROFILE_FILE);
2190
+ const bodyPath = path7.join(dir, DUTY_BODY_FILE);
2191
+ if (!fs7.existsSync(profilePath) || !fs7.statSync(profilePath).isFile()) return null;
2192
+ if (!fs7.existsSync(bodyPath) || !fs7.statSync(bodyPath).isFile()) return null;
2057
2193
  try {
2058
- const rawProfile = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
2059
- const rawBody = fs6.readFileSync(bodyPath, "utf-8");
2194
+ const rawProfile = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
2195
+ const rawBody = fs7.readFileSync(bodyPath, "utf-8");
2060
2196
  const { title, body } = parseDutyBody(rawBody, slug);
2061
2197
  return {
2062
2198
  slug,
@@ -2135,70 +2271,76 @@ var init_dutyFolders = __esm({
2135
2271
  });
2136
2272
 
2137
2273
  // src/registry.ts
2138
- import * as fs7 from "fs";
2139
- import * as path7 from "path";
2274
+ import * as fs8 from "fs";
2275
+ import * as path8 from "path";
2140
2276
  function getExecutablesRoot() {
2141
- const here = path7.dirname(new URL(import.meta.url).pathname);
2277
+ const here = path8.dirname(new URL(import.meta.url).pathname);
2142
2278
  const candidates = [
2143
- path7.join(here, "executables"),
2279
+ path8.join(here, "executables"),
2144
2280
  // dev: src/
2145
- path7.join(here, "..", "executables"),
2281
+ path8.join(here, "..", "executables"),
2146
2282
  // built: dist/bin → dist/executables
2147
- path7.join(here, "..", "src", "executables")
2283
+ path8.join(here, "..", "src", "executables")
2148
2284
  // fallback
2149
2285
  ];
2150
2286
  for (const c of candidates) {
2151
- if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
2287
+ if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
2152
2288
  }
2153
2289
  return candidates[0];
2154
2290
  }
2155
2291
  function getProjectExecutablesRoot() {
2156
- return path7.join(process.cwd(), ".kody", "executables");
2292
+ return path8.join(process.cwd(), ".kody", "executables");
2157
2293
  }
2158
2294
  function getProjectDutiesRoot() {
2159
- return path7.join(process.cwd(), ".kody", "duties");
2295
+ return path8.join(process.cwd(), ".kody", "duties");
2296
+ }
2297
+ function getCompanyStoreExecutablesRoot() {
2298
+ return getCompanyStoreAssetRoot("executables");
2299
+ }
2300
+ function getCompanyStoreDutiesRoot() {
2301
+ return getCompanyStoreAssetRoot("duties");
2160
2302
  }
2161
2303
  function getBuiltinJobsRoot() {
2162
- const here = path7.dirname(new URL(import.meta.url).pathname);
2304
+ const here = path8.dirname(new URL(import.meta.url).pathname);
2163
2305
  const candidates = [
2164
- path7.join(here, "jobs"),
2306
+ path8.join(here, "jobs"),
2165
2307
  // dev: src/
2166
- path7.join(here, "..", "jobs"),
2308
+ path8.join(here, "..", "jobs"),
2167
2309
  // built: dist/bin → dist/jobs
2168
- path7.join(here, "..", "src", "jobs")
2310
+ path8.join(here, "..", "src", "jobs")
2169
2311
  // fallback
2170
2312
  ];
2171
2313
  for (const c of candidates) {
2172
- if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
2314
+ if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
2173
2315
  }
2174
2316
  return candidates[0];
2175
2317
  }
2176
2318
  function getBuiltinDutiesRoot() {
2177
- const here = path7.dirname(new URL(import.meta.url).pathname);
2319
+ const here = path8.dirname(new URL(import.meta.url).pathname);
2178
2320
  const candidates = [
2179
- path7.join(here, "duties"),
2321
+ path8.join(here, "duties"),
2180
2322
  // dev: src/
2181
- path7.join(here, "..", "duties"),
2323
+ path8.join(here, "..", "duties"),
2182
2324
  // built: dist/bin → dist/duties
2183
- path7.join(here, "..", "src", "duties")
2325
+ path8.join(here, "..", "src", "duties")
2184
2326
  // fallback
2185
2327
  ];
2186
2328
  for (const c of candidates) {
2187
- if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
2329
+ if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
2188
2330
  }
2189
2331
  return candidates[0];
2190
2332
  }
2191
2333
  function listBuiltinJobs(root = getBuiltinJobsRoot()) {
2192
- if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
2334
+ if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
2193
2335
  const out = [];
2194
- for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
2336
+ for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
2195
2337
  if (ent.name.startsWith("_") || ent.name.startsWith(".")) continue;
2196
- const full = path7.join(root, ent.name);
2338
+ const full = path8.join(root, ent.name);
2197
2339
  if (ent.isDirectory()) {
2198
- const profilePath = path7.join(full, DUTY_PROFILE_FILE);
2199
- const bodyPath = path7.join(full, DUTY_BODY_FILE);
2200
- if (!fs7.existsSync(profilePath) || !fs7.statSync(profilePath).isFile()) continue;
2201
- if (!fs7.existsSync(bodyPath) || !fs7.statSync(bodyPath).isFile()) continue;
2340
+ const profilePath = path8.join(full, DUTY_PROFILE_FILE);
2341
+ const bodyPath = path8.join(full, DUTY_BODY_FILE);
2342
+ if (!fs8.existsSync(profilePath) || !fs8.statSync(profilePath).isFile()) continue;
2343
+ if (!fs8.existsSync(bodyPath) || !fs8.statSync(bodyPath).isFile()) continue;
2202
2344
  out.push({ slug: ent.name, dir: full, profilePath, bodyPath });
2203
2345
  }
2204
2346
  }
@@ -2206,20 +2348,25 @@ function listBuiltinJobs(root = getBuiltinJobsRoot()) {
2206
2348
  return out;
2207
2349
  }
2208
2350
  function getExecutableRoots() {
2209
- return [getProjectExecutablesRoot(), getExecutablesRoot()];
2351
+ const storeRoot = getCompanyStoreExecutablesRoot();
2352
+ return [getProjectExecutablesRoot(), ...storeRoot ? [storeRoot] : [], getExecutablesRoot()];
2353
+ }
2354
+ function getDutyRoots(projectDutiesRoot = getProjectDutiesRoot()) {
2355
+ const storeRoot = getCompanyStoreDutiesRoot();
2356
+ return [projectDutiesRoot, ...storeRoot ? [storeRoot] : [], getBuiltinDutiesRoot()];
2210
2357
  }
2211
2358
  function listExecutables(roots = getExecutableRoots()) {
2212
2359
  const rootList = typeof roots === "string" ? [roots] : roots;
2213
2360
  const seen = /* @__PURE__ */ new Set();
2214
2361
  const out = [];
2215
2362
  for (const root of rootList) {
2216
- if (!fs7.existsSync(root)) continue;
2217
- const entries = fs7.readdirSync(root, { withFileTypes: true });
2363
+ if (!fs8.existsSync(root)) continue;
2364
+ const entries = fs8.readdirSync(root, { withFileTypes: true });
2218
2365
  for (const ent of entries) {
2219
2366
  if (!ent.isDirectory()) continue;
2220
2367
  if (seen.has(ent.name)) continue;
2221
- const profilePath = path7.join(root, ent.name, DUTY_PROFILE_FILE);
2222
- if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
2368
+ const profilePath = path8.join(root, ent.name, DUTY_PROFILE_FILE);
2369
+ if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
2223
2370
  out.push({ name: ent.name, profilePath });
2224
2371
  seen.add(ent.name);
2225
2372
  }
@@ -2231,8 +2378,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
2231
2378
  if (!isSafeName(name)) return null;
2232
2379
  const rootList = typeof roots === "string" ? [roots] : roots;
2233
2380
  for (const root of rootList) {
2234
- const profilePath = path7.join(root, name, "profile.json");
2235
- if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
2381
+ const profilePath = path8.join(root, name, "profile.json");
2382
+ if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
2236
2383
  return profilePath;
2237
2384
  }
2238
2385
  }
@@ -2247,8 +2394,14 @@ function listDutyActions(projectDutiesRoot = getProjectDutiesRoot()) {
2247
2394
  seen.add(action.action);
2248
2395
  out.push(action);
2249
2396
  };
2250
- for (const action of listProjectFolderDutyActions(projectDutiesRoot)) add(action);
2251
- for (const action of listBuiltinDutyActions()) add(action);
2397
+ const roots = getDutyRoots(projectDutiesRoot);
2398
+ for (const action of listFolderDutyActions(roots[0], "project-folder")) add(action);
2399
+ if (roots.length === 3) {
2400
+ for (const action of listFolderDutyActions(roots[1], "company-store")) add(action);
2401
+ for (const action of listBuiltinDutyActions(roots[2])) add(action);
2402
+ } else {
2403
+ for (const action of listBuiltinDutyActions(roots[1])) add(action);
2404
+ }
2252
2405
  return out.sort((a, b) => a.action.localeCompare(b.action));
2253
2406
  }
2254
2407
  function resolveDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
@@ -2258,11 +2411,19 @@ function resolveDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
2258
2411
  function hasDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
2259
2412
  return resolveDutyAction(action, projectDutiesRoot) !== null;
2260
2413
  }
2414
+ function resolveDutyFolder(slug, projectDutiesRoot = getProjectDutiesRoot()) {
2415
+ if (!isSafeName(slug)) return null;
2416
+ for (const root of getDutyRoots(projectDutiesRoot)) {
2417
+ const duty = readDutyFolder(root, slug);
2418
+ if (duty) return duty;
2419
+ }
2420
+ return null;
2421
+ }
2261
2422
  function isSafeName(name) {
2262
2423
  return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
2263
2424
  }
2264
- function listProjectFolderDutyActions(root) {
2265
- if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
2425
+ function listFolderDutyActions(root, source) {
2426
+ if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
2266
2427
  const out = [];
2267
2428
  for (const slug of listDutyFolderSlugs(root)) {
2268
2429
  if (!isSafeName(slug)) continue;
@@ -2275,7 +2436,7 @@ function listProjectFolderDutyActions(root) {
2275
2436
  duty: slug,
2276
2437
  executable,
2277
2438
  cliArgs: duty.config.executable ? {} : { duty: slug },
2278
- source: "project-folder",
2439
+ source,
2279
2440
  describe: duty.config.describe ?? duty.title,
2280
2441
  profilePath: duty.profilePath,
2281
2442
  bodyPath: duty.bodyPath
@@ -2284,7 +2445,7 @@ function listProjectFolderDutyActions(root) {
2284
2445
  return out.sort((a, b) => a.action.localeCompare(b.action));
2285
2446
  }
2286
2447
  function listBuiltinDutyActions(root = getBuiltinDutiesRoot()) {
2287
- if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
2448
+ if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
2288
2449
  const out = [];
2289
2450
  for (const slug of listDutyFolderSlugs(root)) {
2290
2451
  if (!isSafeName(slug)) continue;
@@ -2309,7 +2470,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
2309
2470
  const profilePath = resolveExecutable(name, roots);
2310
2471
  if (!profilePath) return null;
2311
2472
  try {
2312
- const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
2473
+ const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
2313
2474
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
2314
2475
  return raw.inputs;
2315
2476
  } catch {
@@ -2344,26 +2505,27 @@ function parseGenericFlags(argv) {
2344
2505
  var init_registry = __esm({
2345
2506
  "src/registry.ts"() {
2346
2507
  "use strict";
2508
+ init_companyStore();
2347
2509
  init_dutyFolders();
2348
2510
  }
2349
2511
  });
2350
2512
 
2351
2513
  // src/task-artifacts.ts
2352
- import fs8 from "fs";
2353
- import path8 from "path";
2514
+ import fs9 from "fs";
2515
+ import path9 from "path";
2354
2516
  function prepareTaskArtifactsDir(cwd, taskId) {
2355
2517
  const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
2356
- const relDir = path8.join(".kody", "tasks", safeId);
2357
- const absDir = path8.join(cwd, relDir);
2358
- fs8.mkdirSync(absDir, { recursive: true });
2518
+ const relDir = path9.join(".kody", "tasks", safeId);
2519
+ const absDir = path9.join(cwd, relDir);
2520
+ fs9.mkdirSync(absDir, { recursive: true });
2359
2521
  return { taskId: safeId, absDir, relDir };
2360
2522
  }
2361
2523
  function verifyTaskArtifacts(absDir) {
2362
2524
  const missing = [];
2363
2525
  for (const name of TASK_ARTIFACT_FILES) {
2364
- const full = path8.join(absDir, name);
2526
+ const full = path9.join(absDir, name);
2365
2527
  try {
2366
- const stat = fs8.statSync(full);
2528
+ const stat = fs9.statSync(full);
2367
2529
  if (!stat.isFile() || stat.size === 0) missing.push(name);
2368
2530
  } catch {
2369
2531
  missing.push(name);
@@ -2438,8 +2600,8 @@ var init_task_artifacts = __esm({
2438
2600
  });
2439
2601
 
2440
2602
  // src/gha.ts
2441
- import { execFileSync as execFileSync2 } from "child_process";
2442
- import * as fs14 from "fs";
2603
+ import { execFileSync as execFileSync3 } from "child_process";
2604
+ import * as fs15 from "fs";
2443
2605
  function getRunUrl() {
2444
2606
  const server = process.env.GITHUB_SERVER_URL;
2445
2607
  const repo = process.env.GITHUB_REPOSITORY;
@@ -2450,10 +2612,10 @@ function getRunUrl() {
2450
2612
  function reactToTriggerComment(cwd) {
2451
2613
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
2452
2614
  const eventPath = process.env.GITHUB_EVENT_PATH;
2453
- if (!eventPath || !fs14.existsSync(eventPath)) return;
2615
+ if (!eventPath || !fs15.existsSync(eventPath)) return;
2454
2616
  let event = null;
2455
2617
  try {
2456
- event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
2618
+ event = JSON.parse(fs15.readFileSync(eventPath, "utf-8"));
2457
2619
  } catch {
2458
2620
  return;
2459
2621
  }
@@ -2481,7 +2643,7 @@ function reactToTriggerComment(cwd) {
2481
2643
  for (let attempt = 0; attempt < 3; attempt++) {
2482
2644
  if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
2483
2645
  try {
2484
- execFileSync2("gh", args, opts);
2646
+ execFileSync3("gh", args, opts);
2485
2647
  return;
2486
2648
  } catch (err) {
2487
2649
  lastErr = err;
@@ -2494,7 +2656,7 @@ function reactToTriggerComment(cwd) {
2494
2656
  }
2495
2657
  function sleepMs(ms) {
2496
2658
  try {
2497
- execFileSync2("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
2659
+ execFileSync3("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
2498
2660
  } catch {
2499
2661
  }
2500
2662
  }
@@ -2664,31 +2826,31 @@ var init_lifecycles = __esm({
2664
2826
  });
2665
2827
 
2666
2828
  // src/scripts/buildSyntheticPlugin.ts
2667
- import * as fs15 from "fs";
2668
- import * as os2 from "os";
2669
- import * as path13 from "path";
2829
+ import * as fs16 from "fs";
2830
+ import * as os3 from "os";
2831
+ import * as path14 from "path";
2670
2832
  function getPluginsCatalogRoot() {
2671
- const here = path13.dirname(new URL(import.meta.url).pathname);
2833
+ const here = path14.dirname(new URL(import.meta.url).pathname);
2672
2834
  const candidates = [
2673
- path13.join(here, "..", "plugins"),
2835
+ path14.join(here, "..", "plugins"),
2674
2836
  // dev: src/scripts → src/plugins
2675
- path13.join(here, "..", "..", "plugins"),
2837
+ path14.join(here, "..", "..", "plugins"),
2676
2838
  // built: dist/scripts → dist/plugins
2677
- path13.join(here, "..", "..", "src", "plugins")
2839
+ path14.join(here, "..", "..", "src", "plugins")
2678
2840
  // fallback
2679
2841
  ];
2680
2842
  for (const c of candidates) {
2681
- if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
2843
+ if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
2682
2844
  }
2683
2845
  return candidates[0];
2684
2846
  }
2685
2847
  function copyDir(src, dst) {
2686
- fs15.mkdirSync(dst, { recursive: true });
2687
- for (const ent of fs15.readdirSync(src, { withFileTypes: true })) {
2688
- const s = path13.join(src, ent.name);
2689
- const d = path13.join(dst, ent.name);
2848
+ fs16.mkdirSync(dst, { recursive: true });
2849
+ for (const ent of fs16.readdirSync(src, { withFileTypes: true })) {
2850
+ const s = path14.join(src, ent.name);
2851
+ const d = path14.join(dst, ent.name);
2690
2852
  if (ent.isDirectory()) copyDir(s, d);
2691
- else if (ent.isFile()) fs15.copyFileSync(s, d);
2853
+ else if (ent.isFile()) fs16.copyFileSync(s, d);
2692
2854
  }
2693
2855
  }
2694
2856
  var buildSyntheticPlugin;
@@ -2701,45 +2863,45 @@ var init_buildSyntheticPlugin = __esm({
2701
2863
  if (!needsSynthetic) return;
2702
2864
  const catalog = getPluginsCatalogRoot();
2703
2865
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2704
- const root = path13.join(os2.tmpdir(), `kody-synth-${runId}`);
2705
- fs15.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
2866
+ const root = path14.join(os3.tmpdir(), `kody-synth-${runId}`);
2867
+ fs16.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
2706
2868
  const resolvePart = (bucket, entry) => {
2707
- const local = path13.join(profile.dir, bucket, entry);
2708
- if (fs15.existsSync(local)) return local;
2709
- const central = path13.join(catalog, bucket, entry);
2710
- if (fs15.existsSync(central)) return central;
2869
+ const local = path14.join(profile.dir, bucket, entry);
2870
+ if (fs16.existsSync(local)) return local;
2871
+ const central = path14.join(catalog, bucket, entry);
2872
+ if (fs16.existsSync(central)) return central;
2711
2873
  throw new Error(
2712
2874
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
2713
2875
  );
2714
2876
  };
2715
2877
  if (cc.skills.length > 0) {
2716
- const dst = path13.join(root, "skills");
2717
- fs15.mkdirSync(dst, { recursive: true });
2878
+ const dst = path14.join(root, "skills");
2879
+ fs16.mkdirSync(dst, { recursive: true });
2718
2880
  for (const name of cc.skills) {
2719
- copyDir(resolvePart("skills", name), path13.join(dst, name));
2881
+ copyDir(resolvePart("skills", name), path14.join(dst, name));
2720
2882
  }
2721
2883
  }
2722
2884
  if (cc.commands.length > 0) {
2723
- const dst = path13.join(root, "commands");
2724
- fs15.mkdirSync(dst, { recursive: true });
2885
+ const dst = path14.join(root, "commands");
2886
+ fs16.mkdirSync(dst, { recursive: true });
2725
2887
  for (const name of cc.commands) {
2726
- fs15.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
2888
+ fs16.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
2727
2889
  }
2728
2890
  }
2729
2891
  if (cc.hooks.length > 0) {
2730
- const dst = path13.join(root, "hooks");
2731
- fs15.mkdirSync(dst, { recursive: true });
2892
+ const dst = path14.join(root, "hooks");
2893
+ fs16.mkdirSync(dst, { recursive: true });
2732
2894
  const merged = { hooks: {} };
2733
2895
  for (const name of cc.hooks) {
2734
2896
  const src = resolvePart("hooks", `${name}.json`);
2735
- const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
2897
+ const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
2736
2898
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
2737
2899
  if (!Array.isArray(entries)) continue;
2738
2900
  if (!merged.hooks[event]) merged.hooks[event] = [];
2739
2901
  merged.hooks[event].push(...entries);
2740
2902
  }
2741
2903
  }
2742
- fs15.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2904
+ fs16.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2743
2905
  `);
2744
2906
  }
2745
2907
  const manifest = {
@@ -2749,7 +2911,7 @@ var init_buildSyntheticPlugin = __esm({
2749
2911
  };
2750
2912
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
2751
2913
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
2752
- fs15.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2914
+ fs16.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2753
2915
  `);
2754
2916
  ctx.data.syntheticPluginPath = root;
2755
2917
  };
@@ -2757,8 +2919,8 @@ var init_buildSyntheticPlugin = __esm({
2757
2919
  });
2758
2920
 
2759
2921
  // src/subagents.ts
2760
- import * as fs16 from "fs";
2761
- import * as path14 from "path";
2922
+ import * as fs17 from "fs";
2923
+ import * as path15 from "path";
2762
2924
  function splitFrontmatter(raw) {
2763
2925
  const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
2764
2926
  if (!match) return { fm: {}, body: raw.trim() };
@@ -2771,10 +2933,10 @@ function splitFrontmatter(raw) {
2771
2933
  return { fm, body: (match[2] ?? "").trim() };
2772
2934
  }
2773
2935
  function resolveAgentFile(profileDir, name) {
2774
- const local = path14.join(profileDir, "agents", `${name}.md`);
2775
- if (fs16.existsSync(local)) return local;
2776
- const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
2777
- if (fs16.existsSync(central)) return central;
2936
+ const local = path15.join(profileDir, "agents", `${name}.md`);
2937
+ if (fs17.existsSync(local)) return local;
2938
+ const central = path15.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
2939
+ if (fs17.existsSync(central)) return central;
2778
2940
  throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
2779
2941
  }
2780
2942
  function captureSubagentTemplates(profile) {
@@ -2783,7 +2945,7 @@ function captureSubagentTemplates(profile) {
2783
2945
  const out = {};
2784
2946
  for (const name of names) {
2785
2947
  try {
2786
- out[name] = fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2948
+ out[name] = fs17.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2787
2949
  } catch {
2788
2950
  }
2789
2951
  }
@@ -2794,7 +2956,7 @@ function loadSubagents(profile) {
2794
2956
  if (!names || names.length === 0) return void 0;
2795
2957
  const agents = {};
2796
2958
  for (const name of names) {
2797
- const raw = profile.subagentTemplates?.[name] ?? fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2959
+ const raw = profile.subagentTemplates?.[name] ?? fs17.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2798
2960
  const { fm, body } = splitFrontmatter(raw);
2799
2961
  if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
2800
2962
  const def = {
@@ -2818,15 +2980,15 @@ var init_subagents = __esm({
2818
2980
  });
2819
2981
 
2820
2982
  // src/profile.ts
2821
- import * as fs17 from "fs";
2822
- import * as path15 from "path";
2983
+ import * as fs18 from "fs";
2984
+ import * as path16 from "path";
2823
2985
  function loadProfile(profilePath) {
2824
- if (!fs17.existsSync(profilePath)) {
2986
+ if (!fs18.existsSync(profilePath)) {
2825
2987
  throw new ProfileError(profilePath, "file not found");
2826
2988
  }
2827
2989
  let raw;
2828
2990
  try {
2829
- raw = JSON.parse(fs17.readFileSync(profilePath, "utf-8"));
2991
+ raw = JSON.parse(fs18.readFileSync(profilePath, "utf-8"));
2830
2992
  } catch (err) {
2831
2993
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
2832
2994
  }
@@ -2837,7 +2999,7 @@ function loadProfile(profilePath) {
2837
2999
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
2838
3000
  if (unknownKeys.length > 0) {
2839
3001
  process.stderr.write(
2840
- `[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
3002
+ `[kody profile] ${path16.basename(path16.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
2841
3003
  `
2842
3004
  );
2843
3005
  }
@@ -2926,8 +3088,8 @@ function loadProfile(profilePath) {
2926
3088
  // Phase 5 in-process handoff opt-in. Default false; containers
2927
3089
  // flip to true after end-to-end verification.
2928
3090
  preloadContext: r.preloadContext === true,
2929
- dir: path15.dirname(profilePath),
2930
- promptTemplates: readPromptTemplates(path15.dirname(profilePath))
3091
+ dir: path16.dirname(profilePath),
3092
+ promptTemplates: readPromptTemplates(path16.dirname(profilePath))
2931
3093
  };
2932
3094
  if (lifecycle) {
2933
3095
  applyLifecycle(profile, profilePath);
@@ -2945,7 +3107,7 @@ function loadProfile(profilePath) {
2945
3107
  const preNames = new Set(profile.scripts.preflight.map((e) => e.script).filter(Boolean));
2946
3108
  const postNames = profile.scripts.postflight.map((e) => e.script).filter(Boolean);
2947
3109
  const needsState = postNames.includes("writeJobStateFile") || postNames.includes("parseJobStateFromAgentResult");
2948
- const STATE_LOADERS = ["loadDutyState", "loadJobFromFile", "runTickScript"];
3110
+ const STATE_LOADERS = ["loadDutyState", "loadJobFromFile", "runTickScript", "runScheduledExecutableTick"];
2949
3111
  if (needsState && !STATE_LOADERS.some((s) => preNames.has(s))) {
2950
3112
  throw new ProfileError(
2951
3113
  profilePath,
@@ -2959,16 +3121,16 @@ function readPromptTemplates(dir) {
2959
3121
  const out = {};
2960
3122
  const read = (p) => {
2961
3123
  try {
2962
- out[p] = fs17.readFileSync(p, "utf-8");
3124
+ out[p] = fs18.readFileSync(p, "utf-8");
2963
3125
  } catch {
2964
3126
  }
2965
3127
  };
2966
- read(path15.join(dir, "prompt.md"));
2967
- read(path15.join(dir, "duty.md"));
3128
+ read(path16.join(dir, "prompt.md"));
3129
+ read(path16.join(dir, "duty.md"));
2968
3130
  try {
2969
- const promptsDir = path15.join(dir, "prompts");
2970
- for (const ent of fs17.readdirSync(promptsDir)) {
2971
- if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
3131
+ const promptsDir = path16.join(dir, "prompts");
3132
+ for (const ent of fs18.readdirSync(promptsDir)) {
3133
+ if (ent.endsWith(".md")) read(path16.join(promptsDir, ent));
2972
3134
  }
2973
3135
  } catch {
2974
3136
  }
@@ -3275,7 +3437,7 @@ var init_profile = __esm({
3275
3437
  });
3276
3438
 
3277
3439
  // src/state.ts
3278
- import { execFileSync as execFileSync3 } from "child_process";
3440
+ import { execFileSync as execFileSync4 } from "child_process";
3279
3441
  function emptyState() {
3280
3442
  return {
3281
3443
  schemaVersion: 1,
@@ -3298,7 +3460,7 @@ function ghToken2() {
3298
3460
  function gh2(args, input, cwd) {
3299
3461
  const token = ghToken2();
3300
3462
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
3301
- return execFileSync3("gh", args, {
3463
+ return execFileSync4("gh", args, {
3302
3464
  encoding: "utf-8",
3303
3465
  timeout: API_TIMEOUT_MS2,
3304
3466
  cwd,
@@ -3645,16 +3807,16 @@ var init_state = __esm({
3645
3807
  });
3646
3808
 
3647
3809
  // src/prompt.ts
3648
- import * as fs18 from "fs";
3649
- import * as path16 from "path";
3810
+ import * as fs19 from "fs";
3811
+ import * as path17 from "path";
3650
3812
  function loadProjectConventions(projectDir) {
3651
3813
  const out = [];
3652
3814
  for (const rel of CONVENTION_FILES) {
3653
- const abs = path16.join(projectDir, rel);
3654
- if (!fs18.existsSync(abs)) continue;
3815
+ const abs = path17.join(projectDir, rel);
3816
+ if (!fs19.existsSync(abs)) continue;
3655
3817
  let content;
3656
3818
  try {
3657
- content = fs18.readFileSync(abs, "utf-8");
3819
+ content = fs19.readFileSync(abs, "utf-8");
3658
3820
  } catch {
3659
3821
  continue;
3660
3822
  }
@@ -3889,28 +4051,28 @@ var loadMemoryContext_exports = {};
3889
4051
  __export(loadMemoryContext_exports, {
3890
4052
  loadMemoryContext: () => loadMemoryContext
3891
4053
  });
3892
- import * as fs19 from "fs";
3893
- import * as path17 from "path";
4054
+ import * as fs20 from "fs";
4055
+ import * as path18 from "path";
3894
4056
  function collectPages(memoryAbs) {
3895
4057
  const out = [];
3896
4058
  walkMd(memoryAbs, (file) => {
3897
4059
  let stat;
3898
4060
  try {
3899
- stat = fs19.statSync(file);
4061
+ stat = fs20.statSync(file);
3900
4062
  } catch {
3901
4063
  return;
3902
4064
  }
3903
4065
  let raw;
3904
4066
  try {
3905
- raw = fs19.readFileSync(file, "utf-8");
4067
+ raw = fs20.readFileSync(file, "utf-8");
3906
4068
  } catch {
3907
4069
  return;
3908
4070
  }
3909
4071
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
3910
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path17.basename(file, ".md");
4072
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path18.basename(file, ".md");
3911
4073
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
3912
4074
  out.push({
3913
- relPath: path17.relative(memoryAbs, file),
4075
+ relPath: path18.relative(memoryAbs, file),
3914
4076
  title,
3915
4077
  updated,
3916
4078
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
@@ -3978,16 +4140,16 @@ function walkMd(root, visit) {
3978
4140
  const dir = stack.pop();
3979
4141
  let names;
3980
4142
  try {
3981
- names = fs19.readdirSync(dir);
4143
+ names = fs20.readdirSync(dir);
3982
4144
  } catch {
3983
4145
  continue;
3984
4146
  }
3985
4147
  for (const name of names) {
3986
4148
  if (name.startsWith(".")) continue;
3987
- const full = path17.join(dir, name);
4149
+ const full = path18.join(dir, name);
3988
4150
  let stat;
3989
4151
  try {
3990
- stat = fs19.statSync(full);
4152
+ stat = fs20.statSync(full);
3991
4153
  } catch {
3992
4154
  continue;
3993
4155
  }
@@ -4010,8 +4172,8 @@ var init_loadMemoryContext = __esm({
4010
4172
  TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
4011
4173
  loadMemoryContext = async (ctx) => {
4012
4174
  if (typeof ctx.data.memoryContext === "string") return;
4013
- const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
4014
- if (!fs19.existsSync(memoryAbs)) {
4175
+ const memoryAbs = path18.join(ctx.cwd, MEMORY_DIR_RELATIVE);
4176
+ if (!fs20.existsSync(memoryAbs)) {
4015
4177
  ctx.data.memoryContext = "";
4016
4178
  return;
4017
4179
  }
@@ -4054,12 +4216,12 @@ var init_loadCoverageRules = __esm({
4054
4216
  });
4055
4217
 
4056
4218
  // src/container.ts
4057
- import { execFileSync as execFileSync4 } from "child_process";
4058
- import * as fs20 from "fs";
4219
+ import { execFileSync as execFileSync5 } from "child_process";
4220
+ import * as fs21 from "fs";
4059
4221
  function getProfileInputsForChild(profileName, _cwd) {
4060
4222
  try {
4061
4223
  const profilePath = resolveProfilePath(profileName);
4062
- if (!fs20.existsSync(profilePath)) return null;
4224
+ if (!fs21.existsSync(profilePath)) return null;
4063
4225
  return loadProfile(profilePath).inputs;
4064
4226
  } catch {
4065
4227
  return null;
@@ -4293,7 +4455,7 @@ async function runContainerLoop(profile, ctx, input) {
4293
4455
  }
4294
4456
  function resetWorkingTree(cwd) {
4295
4457
  try {
4296
- execFileSync4("git", ["reset", "--hard", "HEAD"], {
4458
+ execFileSync5("git", ["reset", "--hard", "HEAD"], {
4297
4459
  cwd,
4298
4460
  stdio: ["ignore", "pipe", "pipe"],
4299
4461
  timeout: 3e4
@@ -4520,10 +4682,10 @@ var init_lifecycleLabels = __esm({
4520
4682
  });
4521
4683
 
4522
4684
  // src/litellm.ts
4523
- import { execFileSync as execFileSync5, spawn as spawn3 } from "child_process";
4524
- import * as fs21 from "fs";
4525
- import * as os3 from "os";
4526
- import * as path18 from "path";
4685
+ import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
4686
+ import * as fs22 from "fs";
4687
+ import * as os4 from "os";
4688
+ import * as path19 from "path";
4527
4689
  async function checkLitellmHealth(url) {
4528
4690
  try {
4529
4691
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -4553,7 +4715,7 @@ function generateLitellmConfigYaml(model) {
4553
4715
  }
4554
4716
  function litellmImportable() {
4555
4717
  try {
4556
- execFileSync5("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
4718
+ execFileSync6("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
4557
4719
  return true;
4558
4720
  } catch {
4559
4721
  return false;
@@ -4561,7 +4723,7 @@ function litellmImportable() {
4561
4723
  }
4562
4724
  function locateLitellmScript() {
4563
4725
  try {
4564
- const out = execFileSync5(
4726
+ const out = execFileSync6(
4565
4727
  "python3",
4566
4728
  [
4567
4729
  "-c",
@@ -4576,7 +4738,7 @@ function locateLitellmScript() {
4576
4738
  }
4577
4739
  function resolveLitellmCommand() {
4578
4740
  try {
4579
- execFileSync5("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
4741
+ execFileSync6("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
4580
4742
  return "litellm";
4581
4743
  } catch {
4582
4744
  if (!litellmImportable()) {
@@ -4584,7 +4746,7 @@ function resolveLitellmCommand() {
4584
4746
  let installed = false;
4585
4747
  for (const pip of ["pip", "pip3"]) {
4586
4748
  try {
4587
- execFileSync5(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
4749
+ execFileSync6(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
4588
4750
  installed = true;
4589
4751
  break;
4590
4752
  } catch {
@@ -4608,13 +4770,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4608
4770
  let child;
4609
4771
  let logPath;
4610
4772
  const spawnProxy = () => {
4611
- const configPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
4612
- fs21.writeFileSync(configPath, generateLitellmConfigYaml(model));
4773
+ const configPath = path19.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
4774
+ fs22.writeFileSync(configPath, generateLitellmConfigYaml(model));
4613
4775
  const args = ["--config", configPath, "--port", port];
4614
- const nextLogPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
4615
- const outFd = fs21.openSync(nextLogPath, "w");
4776
+ const nextLogPath = path19.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
4777
+ const outFd = fs22.openSync(nextLogPath, "w");
4616
4778
  child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
4617
- fs21.closeSync(outFd);
4779
+ fs22.closeSync(outFd);
4618
4780
  logPath = nextLogPath;
4619
4781
  };
4620
4782
  const waitForHealth = async () => {
@@ -4628,7 +4790,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4628
4790
  const readLogTail = () => {
4629
4791
  if (!logPath) return "";
4630
4792
  try {
4631
- return fs21.readFileSync(logPath, "utf-8").slice(-2e3);
4793
+ return fs22.readFileSync(logPath, "utf-8").slice(-2e3);
4632
4794
  } catch {
4633
4795
  return "";
4634
4796
  }
@@ -4680,10 +4842,10 @@ ${tail}`
4680
4842
  return { url, kill: killChild, isHealthy, ensureHealthy };
4681
4843
  }
4682
4844
  function readDotenvApiKeys(projectDir) {
4683
- const dotenvPath = path18.join(projectDir, ".env");
4684
- if (!fs21.existsSync(dotenvPath)) return {};
4845
+ const dotenvPath = path19.join(projectDir, ".env");
4846
+ if (!fs22.existsSync(dotenvPath)) return {};
4685
4847
  const result = {};
4686
- for (const rawLine of fs21.readFileSync(dotenvPath, "utf-8").split("\n")) {
4848
+ for (const rawLine of fs22.readFileSync(dotenvPath, "utf-8").split("\n")) {
4687
4849
  const line = rawLine.trim();
4688
4850
  if (!line || line.startsWith("#")) continue;
4689
4851
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -4715,14 +4877,14 @@ var init_litellm = __esm({
4715
4877
  });
4716
4878
 
4717
4879
  // src/pushWithRetry.ts
4718
- import { execFileSync as execFileSync6 } from "child_process";
4880
+ import { execFileSync as execFileSync7 } from "child_process";
4719
4881
  function sleepSync(ms) {
4720
4882
  if (ms <= 0) return;
4721
4883
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
4722
4884
  }
4723
- function runGit(args, cwd) {
4885
+ function runGit2(args, cwd) {
4724
4886
  try {
4725
- const stdout = execFileSync6("git", args, {
4887
+ const stdout = execFileSync7("git", args, {
4726
4888
  cwd,
4727
4889
  encoding: "utf-8",
4728
4890
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4738,7 +4900,7 @@ function runGit(args, cwd) {
4738
4900
  }
4739
4901
  function resolveBranch(cwd, explicit) {
4740
4902
  if (explicit?.trim()) return explicit.trim();
4741
- const r = runGit(["symbolic-ref", "--short", "HEAD"], cwd);
4903
+ const r = runGit2(["symbolic-ref", "--short", "HEAD"], cwd);
4742
4904
  return r.ok ? r.stdout.trim() : "";
4743
4905
  }
4744
4906
  function pushWithRetry(opts = {}) {
@@ -4752,14 +4914,14 @@ function pushWithRetry(opts = {}) {
4752
4914
  const pushArgs = opts.setUpstream ? ["push", "-u", "origin", `HEAD:${branch}`] : ["push", "origin", "HEAD"];
4753
4915
  let lastError = "";
4754
4916
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
4755
- const push = runGit(pushArgs, cwd);
4917
+ const push = runGit2(pushArgs, cwd);
4756
4918
  if (push.ok) return { ok: true, attempts: attempt };
4757
4919
  lastError = push.stderr || push.stdout || "(no error detail)";
4758
4920
  if (!NON_FAST_FORWARD_RE.test(lastError)) {
4759
4921
  return { ok: false, reason: `push failed (not retryable): ${lastError.trim().slice(-400)}`, attempts: attempt };
4760
4922
  }
4761
4923
  if (attempt === maxRetries) break;
4762
- const fetch2 = runGit(["fetch", "origin", branch], cwd);
4924
+ const fetch2 = runGit2(["fetch", "origin", branch], cwd);
4763
4925
  if (!fetch2.ok) {
4764
4926
  return {
4765
4927
  ok: false,
@@ -4767,9 +4929,9 @@ function pushWithRetry(opts = {}) {
4767
4929
  attempts: attempt
4768
4930
  };
4769
4931
  }
4770
- const rebase = runGit(["rebase", "--rebase-merges", `origin/${branch}`], cwd);
4932
+ const rebase = runGit2(["rebase", "--rebase-merges", `origin/${branch}`], cwd);
4771
4933
  if (!rebase.ok) {
4772
- runGit(["rebase", "--abort"], cwd);
4934
+ runGit2(["rebase", "--abort"], cwd);
4773
4935
  return {
4774
4936
  ok: false,
4775
4937
  reason: `rebase onto origin/${branch} failed (conflict?): ${(rebase.stderr || rebase.stdout).trim().slice(-400)}`,
@@ -4797,12 +4959,12 @@ var init_pushWithRetry = __esm({
4797
4959
  });
4798
4960
 
4799
4961
  // src/commit.ts
4800
- import { execFileSync as execFileSync7 } from "child_process";
4801
- import * as fs22 from "fs";
4802
- import * as path19 from "path";
4962
+ import { execFileSync as execFileSync8 } from "child_process";
4963
+ import * as fs23 from "fs";
4964
+ import * as path20 from "path";
4803
4965
  function git(args, cwd) {
4804
4966
  try {
4805
- return execFileSync7("git", args, {
4967
+ return execFileSync8("git", args, {
4806
4968
  encoding: "utf-8",
4807
4969
  timeout: 12e4,
4808
4970
  cwd,
@@ -4837,18 +4999,18 @@ function ensureGitIdentity(cwd) {
4837
4999
  }
4838
5000
  function abortUnfinishedGitOps(cwd) {
4839
5001
  const aborted = [];
4840
- const gitDir = path19.join(cwd ?? process.cwd(), ".git");
4841
- if (!fs22.existsSync(gitDir)) return aborted;
4842
- if (fs22.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
5002
+ const gitDir = path20.join(cwd ?? process.cwd(), ".git");
5003
+ if (!fs23.existsSync(gitDir)) return aborted;
5004
+ if (fs23.existsSync(path20.join(gitDir, "MERGE_HEAD"))) {
4843
5005
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
4844
5006
  }
4845
- if (fs22.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
5007
+ if (fs23.existsSync(path20.join(gitDir, "CHERRY_PICK_HEAD"))) {
4846
5008
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
4847
5009
  }
4848
- if (fs22.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
5010
+ if (fs23.existsSync(path20.join(gitDir, "REVERT_HEAD"))) {
4849
5011
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
4850
5012
  }
4851
- if (fs22.existsSync(path19.join(gitDir, "rebase-merge")) || fs22.existsSync(path19.join(gitDir, "rebase-apply"))) {
5013
+ if (fs23.existsSync(path20.join(gitDir, "rebase-merge")) || fs23.existsSync(path20.join(gitDir, "rebase-apply"))) {
4852
5014
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
4853
5015
  }
4854
5016
  try {
@@ -4869,7 +5031,7 @@ function isForbiddenPath(p) {
4869
5031
  return false;
4870
5032
  }
4871
5033
  function listChangedFiles(cwd) {
4872
- const raw = execFileSync7("git", ["status", "--porcelain=v1", "-z"], {
5034
+ const raw = execFileSync8("git", ["status", "--porcelain=v1", "-z"], {
4873
5035
  encoding: "utf-8",
4874
5036
  cwd,
4875
5037
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4881,7 +5043,7 @@ function listChangedFiles(cwd) {
4881
5043
  }
4882
5044
  function listFilesInCommit(ref = "HEAD", cwd) {
4883
5045
  try {
4884
- const raw = execFileSync7("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
5046
+ const raw = execFileSync8("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
4885
5047
  encoding: "utf-8",
4886
5048
  cwd,
4887
5049
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4904,7 +5066,7 @@ function normalizeCommitMessage(raw) {
4904
5066
  function commitAndPush(branch, agentMessage, cwd) {
4905
5067
  const allChanged = listChangedFiles(cwd);
4906
5068
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
4907
- const mergeHeadExists = fs22.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
5069
+ const mergeHeadExists = fs23.existsSync(path20.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
4908
5070
  if (allowedFiles.length === 0 && !mergeHeadExists) {
4909
5071
  return { committed: false, pushed: false, sha: "", message: "" };
4910
5072
  }
@@ -5060,10 +5222,10 @@ var init_saveTaskState = __esm({
5060
5222
  });
5061
5223
 
5062
5224
  // src/scripts/advanceFlow.ts
5063
- import { execFileSync as execFileSync8 } from "child_process";
5225
+ import { execFileSync as execFileSync9 } from "child_process";
5064
5226
  function ghComment(issueNumber, body, cwd, label) {
5065
5227
  try {
5066
- execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5228
+ execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5067
5229
  timeout: API_TIMEOUT_MS3,
5068
5230
  cwd,
5069
5231
  stdio: ["ignore", "pipe", "pipe"]
@@ -5257,7 +5419,7 @@ var init_appendCompanyActivity = __esm({
5257
5419
  });
5258
5420
 
5259
5421
  // src/coverage.ts
5260
- import { execFileSync as execFileSync9 } from "child_process";
5422
+ import { execFileSync as execFileSync10 } from "child_process";
5261
5423
  function patternToRegex(pattern) {
5262
5424
  let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
5263
5425
  s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
@@ -5275,7 +5437,7 @@ function renderSiblingPath(file, requireSibling) {
5275
5437
  }
5276
5438
  function safeGit(args, cwd) {
5277
5439
  try {
5278
- return execFileSync9("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
5440
+ return execFileSync10("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
5279
5441
  } catch {
5280
5442
  return "";
5281
5443
  }
@@ -5422,11 +5584,11 @@ var init_classifyByLabel = __esm({
5422
5584
  });
5423
5585
 
5424
5586
  // src/scripts/commitAndPush.ts
5425
- import * as fs23 from "fs";
5426
- import * as path20 from "path";
5587
+ import * as fs24 from "fs";
5588
+ import * as path21 from "path";
5427
5589
  function sentinelPathForStage(cwd, profileName) {
5428
5590
  const runId = resolveRunId();
5429
- return path20.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
5591
+ return path21.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
5430
5592
  }
5431
5593
  var DEFAULT_COMMIT_MESSAGE, commitAndPush2;
5432
5594
  var init_commitAndPush = __esm({
@@ -5443,9 +5605,9 @@ var init_commitAndPush = __esm({
5443
5605
  }
5444
5606
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
5445
5607
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
5446
- if (sentinel && fs23.existsSync(sentinel)) {
5608
+ if (sentinel && fs24.existsSync(sentinel)) {
5447
5609
  try {
5448
- const replay = JSON.parse(fs23.readFileSync(sentinel, "utf-8"));
5610
+ const replay = JSON.parse(fs24.readFileSync(sentinel, "utf-8"));
5449
5611
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
5450
5612
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
5451
5613
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -5498,8 +5660,8 @@ var init_commitAndPush = __esm({
5498
5660
  const result = ctx.data.commitResult;
5499
5661
  if (sentinel && result?.committed) {
5500
5662
  try {
5501
- fs23.mkdirSync(path20.dirname(sentinel), { recursive: true });
5502
- fs23.writeFileSync(
5663
+ fs24.mkdirSync(path21.dirname(sentinel), { recursive: true });
5664
+ fs24.writeFileSync(
5503
5665
  sentinel,
5504
5666
  JSON.stringify(
5505
5667
  {
@@ -5521,8 +5683,8 @@ var init_commitAndPush = __esm({
5521
5683
  });
5522
5684
 
5523
5685
  // src/goal/state.ts
5524
- import * as fs24 from "fs";
5525
- import * as path21 from "path";
5686
+ import * as fs25 from "fs";
5687
+ import * as path22 from "path";
5526
5688
  function parseGoalState(filePath, raw) {
5527
5689
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
5528
5690
  throw new GoalStateError(filePath, "must be a JSON object");
@@ -5574,10 +5736,10 @@ var init_state2 = __esm({
5574
5736
  "use strict";
5575
5737
  VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
5576
5738
  GoalStateError = class extends Error {
5577
- constructor(path44, message) {
5578
- super(`Invalid goal state at ${path44}:
5739
+ constructor(path45, message) {
5740
+ super(`Invalid goal state at ${path45}:
5579
5741
  ${message}`);
5580
- this.path = path44;
5742
+ this.path = path45;
5581
5743
  this.name = "GoalStateError";
5582
5744
  }
5583
5745
  path;
@@ -5689,8 +5851,8 @@ var init_commitGoalState = __esm({
5689
5851
  });
5690
5852
 
5691
5853
  // src/scripts/composePrompt.ts
5692
- import * as fs25 from "fs";
5693
- import * as path22 from "path";
5854
+ import * as fs26 from "fs";
5855
+ import * as path23 from "path";
5694
5856
  function fenceUntrusted(value) {
5695
5857
  if (value.trim().length === 0) return value;
5696
5858
  const safe = value.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
@@ -5811,10 +5973,10 @@ var init_composePrompt = __esm({
5811
5973
  const explicit = ctx.data.promptTemplate;
5812
5974
  const mode = ctx.args.mode;
5813
5975
  const candidates = [
5814
- explicit ? path22.join(profile.dir, explicit) : null,
5815
- mode ? path22.join(profile.dir, "prompts", `${mode}.md`) : null,
5816
- path22.join(profile.dir, "prompt.md"),
5817
- path22.join(profile.dir, "duty.md")
5976
+ explicit ? path23.join(profile.dir, explicit) : null,
5977
+ mode ? path23.join(profile.dir, "prompts", `${mode}.md`) : null,
5978
+ path23.join(profile.dir, "prompt.md"),
5979
+ path23.join(profile.dir, "duty.md")
5818
5980
  ].filter(Boolean);
5819
5981
  let templatePath = "";
5820
5982
  let template = "";
@@ -5827,7 +5989,7 @@ var init_composePrompt = __esm({
5827
5989
  break;
5828
5990
  }
5829
5991
  try {
5830
- template = fs25.readFileSync(c, "utf-8");
5992
+ template = fs26.readFileSync(c, "utf-8");
5831
5993
  templatePath = c;
5832
5994
  break;
5833
5995
  } catch (err) {
@@ -5838,7 +6000,7 @@ var init_composePrompt = __esm({
5838
6000
  if (!templatePath) {
5839
6001
  let dirState;
5840
6002
  try {
5841
- dirState = `dir contents: [${fs25.readdirSync(profile.dir).join(", ")}]`;
6003
+ dirState = `dir contents: [${fs26.readdirSync(profile.dir).join(", ")}]`;
5842
6004
  } catch (err) {
5843
6005
  dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
5844
6006
  }
@@ -6664,20 +6826,20 @@ var init_deriveQaScopeFromIssue = __esm({
6664
6826
  });
6665
6827
 
6666
6828
  // src/scripts/diagMcp.ts
6667
- import { execFileSync as execFileSync10 } from "child_process";
6668
- import * as fs26 from "fs";
6669
- import * as os4 from "os";
6670
- import * as path23 from "path";
6829
+ import { execFileSync as execFileSync11 } from "child_process";
6830
+ import * as fs27 from "fs";
6831
+ import * as os5 from "os";
6832
+ import * as path24 from "path";
6671
6833
  var diagMcp;
6672
6834
  var init_diagMcp = __esm({
6673
6835
  "src/scripts/diagMcp.ts"() {
6674
6836
  "use strict";
6675
6837
  diagMcp = async (_ctx) => {
6676
- const home = os4.homedir();
6677
- const cacheDir = path23.join(home, ".cache", "ms-playwright");
6838
+ const home = os5.homedir();
6839
+ const cacheDir = path24.join(home, ".cache", "ms-playwright");
6678
6840
  let entries = [];
6679
6841
  try {
6680
- entries = fs26.readdirSync(cacheDir);
6842
+ entries = fs27.readdirSync(cacheDir);
6681
6843
  } catch {
6682
6844
  }
6683
6845
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -6688,7 +6850,7 @@ var init_diagMcp = __esm({
6688
6850
  process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
6689
6851
  `);
6690
6852
  try {
6691
- const v = execFileSync10("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
6853
+ const v = execFileSync11("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
6692
6854
  stdio: "pipe",
6693
6855
  timeout: 6e4,
6694
6856
  encoding: "utf8"
@@ -6705,13 +6867,13 @@ var init_diagMcp = __esm({
6705
6867
  });
6706
6868
 
6707
6869
  // src/scripts/frameworkDetectors.ts
6708
- import * as fs27 from "fs";
6709
- import * as path24 from "path";
6870
+ import * as fs28 from "fs";
6871
+ import * as path25 from "path";
6710
6872
  function detectFrameworks(cwd) {
6711
6873
  const out = [];
6712
6874
  let deps = {};
6713
6875
  try {
6714
- const pkg = JSON.parse(fs27.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
6876
+ const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
6715
6877
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
6716
6878
  } catch {
6717
6879
  return out;
@@ -6748,25 +6910,25 @@ function detectFrameworks(cwd) {
6748
6910
  }
6749
6911
  function findFile(cwd, candidates) {
6750
6912
  for (const c of candidates) {
6751
- if (fs27.existsSync(path24.join(cwd, c))) return c;
6913
+ if (fs28.existsSync(path25.join(cwd, c))) return c;
6752
6914
  }
6753
6915
  return null;
6754
6916
  }
6755
6917
  function discoverPayloadCollections(cwd) {
6756
6918
  const out = [];
6757
6919
  for (const dir of COLLECTION_DIRS) {
6758
- const full = path24.join(cwd, dir);
6759
- if (!fs27.existsSync(full)) continue;
6920
+ const full = path25.join(cwd, dir);
6921
+ if (!fs28.existsSync(full)) continue;
6760
6922
  let files;
6761
6923
  try {
6762
- files = fs27.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6924
+ files = fs28.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6763
6925
  } catch {
6764
6926
  continue;
6765
6927
  }
6766
6928
  for (const file of files) {
6767
6929
  try {
6768
- const filePath = path24.join(full, file);
6769
- const content = fs27.readFileSync(filePath, "utf-8").slice(0, 1e4);
6930
+ const filePath = path25.join(full, file);
6931
+ const content = fs28.readFileSync(filePath, "utf-8").slice(0, 1e4);
6770
6932
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
6771
6933
  if (!slugMatch) continue;
6772
6934
  const slug = slugMatch[1];
@@ -6780,7 +6942,7 @@ function discoverPayloadCollections(cwd) {
6780
6942
  out.push({
6781
6943
  name,
6782
6944
  slug,
6783
- filePath: path24.relative(cwd, filePath),
6945
+ filePath: path25.relative(cwd, filePath),
6784
6946
  fields: fields.slice(0, 20),
6785
6947
  hasAdmin
6786
6948
  });
@@ -6793,28 +6955,28 @@ function discoverPayloadCollections(cwd) {
6793
6955
  function discoverAdminComponents(cwd, collections) {
6794
6956
  const out = [];
6795
6957
  for (const dir of ADMIN_COMPONENT_DIRS) {
6796
- const full = path24.join(cwd, dir);
6797
- if (!fs27.existsSync(full)) continue;
6958
+ const full = path25.join(cwd, dir);
6959
+ if (!fs28.existsSync(full)) continue;
6798
6960
  let entries;
6799
6961
  try {
6800
- entries = fs27.readdirSync(full, { withFileTypes: true });
6962
+ entries = fs28.readdirSync(full, { withFileTypes: true });
6801
6963
  } catch {
6802
6964
  continue;
6803
6965
  }
6804
6966
  for (const entry of entries) {
6805
- const entryPath = path24.join(full, entry.name);
6967
+ const entryPath = path25.join(full, entry.name);
6806
6968
  let name;
6807
6969
  let filePath;
6808
6970
  if (entry.isDirectory()) {
6809
6971
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
6810
- (f) => fs27.existsSync(path24.join(entryPath, f))
6972
+ (f) => fs28.existsSync(path25.join(entryPath, f))
6811
6973
  );
6812
6974
  if (!indexFile) continue;
6813
6975
  name = entry.name;
6814
- filePath = path24.relative(cwd, path24.join(entryPath, indexFile));
6976
+ filePath = path25.relative(cwd, path25.join(entryPath, indexFile));
6815
6977
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
6816
6978
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
6817
- filePath = path24.relative(cwd, entryPath);
6979
+ filePath = path25.relative(cwd, entryPath);
6818
6980
  } else {
6819
6981
  continue;
6820
6982
  }
@@ -6822,7 +6984,7 @@ function discoverAdminComponents(cwd, collections) {
6822
6984
  if (collections) {
6823
6985
  for (const col of collections) {
6824
6986
  try {
6825
- const colContent = fs27.readFileSync(path24.join(cwd, col.filePath), "utf-8");
6987
+ const colContent = fs28.readFileSync(path25.join(cwd, col.filePath), "utf-8");
6826
6988
  if (colContent.includes(name)) {
6827
6989
  usedInCollection = col.slug;
6828
6990
  break;
@@ -6840,8 +7002,8 @@ function scanApiRoutes(cwd) {
6840
7002
  const out = [];
6841
7003
  const appDirs = ["src/app", "app"];
6842
7004
  for (const appDir of appDirs) {
6843
- const apiDir = path24.join(cwd, appDir, "api");
6844
- if (!fs27.existsSync(apiDir)) continue;
7005
+ const apiDir = path25.join(cwd, appDir, "api");
7006
+ if (!fs28.existsSync(apiDir)) continue;
6845
7007
  walkApiRoutes(apiDir, "/api", cwd, out);
6846
7008
  break;
6847
7009
  }
@@ -6850,14 +7012,14 @@ function scanApiRoutes(cwd) {
6850
7012
  function walkApiRoutes(dir, prefix, cwd, out) {
6851
7013
  let entries;
6852
7014
  try {
6853
- entries = fs27.readdirSync(dir, { withFileTypes: true });
7015
+ entries = fs28.readdirSync(dir, { withFileTypes: true });
6854
7016
  } catch {
6855
7017
  return;
6856
7018
  }
6857
7019
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
6858
7020
  if (routeFile) {
6859
7021
  try {
6860
- const content = fs27.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
7022
+ const content = fs28.readFileSync(path25.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
6861
7023
  const methods = HTTP_METHODS.filter(
6862
7024
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
6863
7025
  );
@@ -6865,7 +7027,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6865
7027
  out.push({
6866
7028
  path: prefix,
6867
7029
  methods,
6868
- filePath: path24.relative(cwd, path24.join(dir, routeFile.name))
7030
+ filePath: path25.relative(cwd, path25.join(dir, routeFile.name))
6869
7031
  });
6870
7032
  }
6871
7033
  } catch {
@@ -6876,7 +7038,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6876
7038
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6877
7039
  let segment = entry.name;
6878
7040
  if (segment.startsWith("(") && segment.endsWith(")")) {
6879
- walkApiRoutes(path24.join(dir, entry.name), prefix, cwd, out);
7041
+ walkApiRoutes(path25.join(dir, entry.name), prefix, cwd, out);
6880
7042
  continue;
6881
7043
  }
6882
7044
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -6884,16 +7046,16 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6884
7046
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
6885
7047
  segment = `:${segment.slice(1, -1)}`;
6886
7048
  }
6887
- walkApiRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
7049
+ walkApiRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
6888
7050
  }
6889
7051
  }
6890
7052
  function scanEnvVars(cwd) {
6891
7053
  const candidates = [".env.example", ".env.local.example", ".env.template"];
6892
7054
  for (const envFile of candidates) {
6893
- const envPath = path24.join(cwd, envFile);
6894
- if (!fs27.existsSync(envPath)) continue;
7055
+ const envPath = path25.join(cwd, envFile);
7056
+ if (!fs28.existsSync(envPath)) continue;
6895
7057
  try {
6896
- const content = fs27.readFileSync(envPath, "utf-8");
7058
+ const content = fs28.readFileSync(envPath, "utf-8");
6897
7059
  const vars = [];
6898
7060
  for (const line of content.split("\n")) {
6899
7061
  const trimmed = line.trim();
@@ -6938,8 +7100,8 @@ var init_frameworkDetectors = __esm({
6938
7100
  });
6939
7101
 
6940
7102
  // src/scripts/discoverQaContext.ts
6941
- import * as fs28 from "fs";
6942
- import * as path25 from "path";
7103
+ import * as fs29 from "fs";
7104
+ import * as path26 from "path";
6943
7105
  function runQaDiscovery(cwd) {
6944
7106
  const out = {
6945
7107
  routes: [],
@@ -6970,9 +7132,9 @@ function runQaDiscovery(cwd) {
6970
7132
  }
6971
7133
  function detectDevServer(cwd, out) {
6972
7134
  try {
6973
- const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
7135
+ const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
6974
7136
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
6975
- const pm = fs28.existsSync(path25.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs28.existsSync(path25.join(cwd, "yarn.lock")) ? "yarn" : fs28.existsSync(path25.join(cwd, "bun.lockb")) ? "bun" : "npm";
7137
+ 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";
6976
7138
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
6977
7139
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
6978
7140
  else if (allDeps.vite) out.devPort = 5173;
@@ -6982,8 +7144,8 @@ function detectDevServer(cwd, out) {
6982
7144
  function scanFrontendRoutes(cwd, out) {
6983
7145
  const appDirs = ["src/app", "app"];
6984
7146
  for (const appDir of appDirs) {
6985
- const full = path25.join(cwd, appDir);
6986
- if (!fs28.existsSync(full)) continue;
7147
+ const full = path26.join(cwd, appDir);
7148
+ if (!fs29.existsSync(full)) continue;
6987
7149
  walkFrontendRoutes(full, "", out);
6988
7150
  break;
6989
7151
  }
@@ -6991,7 +7153,7 @@ function scanFrontendRoutes(cwd, out) {
6991
7153
  function walkFrontendRoutes(dir, prefix, out) {
6992
7154
  let entries;
6993
7155
  try {
6994
- entries = fs28.readdirSync(dir, { withFileTypes: true });
7156
+ entries = fs29.readdirSync(dir, { withFileTypes: true });
6995
7157
  } catch {
6996
7158
  return;
6997
7159
  }
@@ -7008,7 +7170,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7008
7170
  if (entry.name === "node_modules" || entry.name === ".next") continue;
7009
7171
  let segment = entry.name;
7010
7172
  if (segment.startsWith("(") && segment.endsWith(")")) {
7011
- walkFrontendRoutes(path25.join(dir, entry.name), prefix, out);
7173
+ walkFrontendRoutes(path26.join(dir, entry.name), prefix, out);
7012
7174
  continue;
7013
7175
  }
7014
7176
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -7016,7 +7178,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7016
7178
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
7017
7179
  segment = `:${segment.slice(1, -1)}`;
7018
7180
  }
7019
- walkFrontendRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, out);
7181
+ walkFrontendRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, out);
7020
7182
  }
7021
7183
  }
7022
7184
  function detectAuthFiles(cwd, out) {
@@ -7033,23 +7195,23 @@ function detectAuthFiles(cwd, out) {
7033
7195
  "src/app/api/oauth"
7034
7196
  ];
7035
7197
  for (const c of candidates) {
7036
- if (fs28.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
7198
+ if (fs29.existsSync(path26.join(cwd, c))) out.authFiles.push(c);
7037
7199
  }
7038
7200
  }
7039
7201
  function detectRoles(cwd, out) {
7040
7202
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
7041
7203
  for (const rp of rolePaths) {
7042
- const dir = path25.join(cwd, rp);
7043
- if (!fs28.existsSync(dir)) continue;
7204
+ const dir = path26.join(cwd, rp);
7205
+ if (!fs29.existsSync(dir)) continue;
7044
7206
  let files;
7045
7207
  try {
7046
- files = fs28.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7208
+ files = fs29.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7047
7209
  } catch {
7048
7210
  continue;
7049
7211
  }
7050
7212
  for (const f of files) {
7051
7213
  try {
7052
- const content = fs28.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
7214
+ const content = fs29.readFileSync(path26.join(dir, f), "utf-8").slice(0, 5e3);
7053
7215
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
7054
7216
  if (roleMatches) {
7055
7217
  for (const m of roleMatches) {
@@ -7192,7 +7354,7 @@ var init_dispatch = __esm({
7192
7354
  });
7193
7355
 
7194
7356
  // src/scripts/dispatchClassified.ts
7195
- import { execFileSync as execFileSync11 } from "child_process";
7357
+ import { execFileSync as execFileSync12 } from "child_process";
7196
7358
  var API_TIMEOUT_MS4, VALID_CLASSES2, dispatchClassified;
7197
7359
  var init_dispatchClassified = __esm({
7198
7360
  "src/scripts/dispatchClassified.ts"() {
@@ -7222,7 +7384,7 @@ ${stateBody}`;
7222
7384
  try {
7223
7385
  const existing = findStateComment("issue", issueNumber, ctx.cwd);
7224
7386
  if (existing) {
7225
- execFileSync11(
7387
+ execFileSync12(
7226
7388
  "gh",
7227
7389
  ["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"],
7228
7390
  {
@@ -7233,7 +7395,7 @@ ${stateBody}`;
7233
7395
  }
7234
7396
  );
7235
7397
  } else {
7236
- execFileSync11("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7398
+ execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7237
7399
  cwd: ctx.cwd,
7238
7400
  timeout: API_TIMEOUT_MS4,
7239
7401
  stdio: ["ignore", "pipe", "pipe"]
@@ -7475,8 +7637,8 @@ var init_contentsApiBackend = __esm({
7475
7637
  });
7476
7638
 
7477
7639
  // src/scripts/jobState/localFileBackend.ts
7478
- import * as fs29 from "fs";
7479
- import * as path26 from "path";
7640
+ import * as fs30 from "fs";
7641
+ import * as path27 from "path";
7480
7642
  function sanitizeKey(s) {
7481
7643
  return s.replace(/[^A-Za-z0-9._-]/g, "-");
7482
7644
  }
@@ -7532,7 +7694,7 @@ var init_localFileBackend = __esm({
7532
7694
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
7533
7695
  this.cwd = opts.cwd;
7534
7696
  this.jobsDir = opts.jobsDir;
7535
- this.absDir = path26.join(opts.cwd, opts.jobsDir);
7697
+ this.absDir = path27.join(opts.cwd, opts.jobsDir);
7536
7698
  this.owner = opts.owner;
7537
7699
  this.repo = opts.repo;
7538
7700
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -7547,7 +7709,7 @@ var init_localFileBackend = __esm({
7547
7709
  `);
7548
7710
  return;
7549
7711
  }
7550
- fs29.mkdirSync(this.absDir, { recursive: true });
7712
+ fs30.mkdirSync(this.absDir, { recursive: true });
7551
7713
  const prefix = this.cacheKeyPrefix();
7552
7714
  const probeKey = `${prefix}probe-${Date.now()}`;
7553
7715
  try {
@@ -7576,7 +7738,7 @@ var init_localFileBackend = __esm({
7576
7738
  `);
7577
7739
  return;
7578
7740
  }
7579
- if (!fs29.existsSync(this.absDir)) {
7741
+ if (!fs30.existsSync(this.absDir)) {
7580
7742
  return;
7581
7743
  }
7582
7744
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -7592,11 +7754,11 @@ var init_localFileBackend = __esm({
7592
7754
  }
7593
7755
  load(slug) {
7594
7756
  const relPath = stateFilePath(this.jobsDir, slug);
7595
- const absPath = path26.join(this.cwd, relPath);
7596
- if (!fs29.existsSync(absPath)) {
7757
+ const absPath = path27.join(this.cwd, relPath);
7758
+ if (!fs30.existsSync(absPath)) {
7597
7759
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
7598
7760
  }
7599
- const raw = fs29.readFileSync(absPath, "utf-8");
7761
+ const raw = fs30.readFileSync(absPath, "utf-8");
7600
7762
  let parsed;
7601
7763
  try {
7602
7764
  parsed = JSON.parse(raw);
@@ -7613,13 +7775,13 @@ var init_localFileBackend = __esm({
7613
7775
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
7614
7776
  return false;
7615
7777
  }
7616
- const absPath = path26.join(this.cwd, loaded.path);
7617
- fs29.mkdirSync(path26.dirname(absPath), { recursive: true });
7778
+ const absPath = path27.join(this.cwd, loaded.path);
7779
+ fs30.mkdirSync(path27.dirname(absPath), { recursive: true });
7618
7780
  const body = `${JSON.stringify(next, null, 2)}
7619
7781
  `;
7620
7782
  const tmpPath = `${absPath}.${process.pid}.tmp`;
7621
- fs29.writeFileSync(tmpPath, body, "utf-8");
7622
- fs29.renameSync(tmpPath, absPath);
7783
+ fs30.writeFileSync(tmpPath, body, "utf-8");
7784
+ fs30.renameSync(tmpPath, absPath);
7623
7785
  return true;
7624
7786
  }
7625
7787
  cacheKeyPrefix() {
@@ -7794,7 +7956,7 @@ var init_planTaskJobs = __esm({
7794
7956
  });
7795
7957
 
7796
7958
  // src/scripts/dispatchDutyFileTicks.ts
7797
- import * as path27 from "path";
7959
+ import * as path28 from "path";
7798
7960
  async function decideShouldFire(every, slug, backend, now) {
7799
7961
  if (!every) return { skip: false, reason: "no schedule (every cron tick)" };
7800
7962
  if (every === "manual") {
@@ -7841,6 +8003,18 @@ function parseDutyFilter(raw) {
7841
8003
  function filterSlugs(slugs, onlyDuty) {
7842
8004
  return onlyDuty ? slugs.filter((slug) => slug === onlyDuty) : slugs;
7843
8005
  }
8006
+ function listDutySlugs(roots) {
8007
+ const seen = /* @__PURE__ */ new Set();
8008
+ const out = [];
8009
+ for (const root of roots) {
8010
+ for (const slug of listDutyFolderSlugs(root)) {
8011
+ if (seen.has(slug)) continue;
8012
+ seen.add(slug);
8013
+ out.push(slug);
8014
+ }
8015
+ }
8016
+ return out.sort();
8017
+ }
7844
8018
  function createDutyTaskIssue(opts) {
7845
8019
  const title = `Duty ${opts.slug} - multi-executable task`;
7846
8020
  const body = buildDutyTaskIssueBody(opts.slug, opts.body, opts.config);
@@ -7891,9 +8065,10 @@ var init_dispatchDutyFileTicks = __esm({
7891
8065
  init_dutyFolders();
7892
8066
  init_issue();
7893
8067
  init_job();
7894
- init_scheduleEvery();
8068
+ init_registry();
7895
8069
  init_jobState();
7896
8070
  init_planTaskJobs();
8071
+ init_scheduleEvery();
7897
8072
  dispatchDutyFileTicks = async (ctx, _profile, args) => {
7898
8073
  ctx.skipAgent = true;
7899
8074
  const targetExecutable = String(args?.targetExecutable ?? "");
@@ -7909,8 +8084,10 @@ var init_dispatchDutyFileTicks = __esm({
7909
8084
  }
7910
8085
  try {
7911
8086
  const onlyDuty = parseDutyFilter(ctx.args.duty);
7912
- const jobsPath = path27.join(ctx.cwd, jobsDir);
7913
- const slugs = filterSlugs(listDutyFolderSlugs(jobsPath), onlyDuty);
8087
+ const jobsPath = path28.join(ctx.cwd, jobsDir);
8088
+ const storeJobsPath = getCompanyStoreDutiesRoot();
8089
+ const dutyRoots = [jobsPath, ...storeJobsPath ? [storeJobsPath] : []];
8090
+ const slugs = filterSlugs(listDutySlugs(dutyRoots), onlyDuty);
7914
8091
  ctx.data.jobSlugCount = slugs.length;
7915
8092
  if (slugs.length === 0) {
7916
8093
  const filter = onlyDuty ? ` matching ${onlyDuty}` : "";
@@ -7924,7 +8101,7 @@ var init_dispatchDutyFileTicks = __esm({
7924
8101
  const results = [];
7925
8102
  const now = Date.now();
7926
8103
  for (const slug of slugs) {
7927
- const duty = readDutyFolder(jobsPath, slug);
8104
+ const duty = resolveDutyFolder(slug, jobsPath);
7928
8105
  if (!duty) {
7929
8106
  process.stderr.write(`[jobs] \u23ED skip ${slug}: duty folder is missing profile.json or duty.md
7930
8107
  `);
@@ -7986,7 +8163,7 @@ var init_dispatchDutyFileTicks = __esm({
7986
8163
  continue;
7987
8164
  }
7988
8165
  const slugTarget = config.tickScript ? scriptedExecutable : config.executable ?? targetExecutable;
7989
- const cliArgs = config.executable ? {} : { [slugArg]: slug };
8166
+ const cliArgs = { [slugArg]: slug };
7990
8167
  process.stdout.write(`[jobs] \u2192 tick ${slug} (${slugTarget})
7991
8168
  `);
7992
8169
  try {
@@ -8177,7 +8354,7 @@ var init_dispatchNextTaskJob = __esm({
8177
8354
  });
8178
8355
 
8179
8356
  // src/pr.ts
8180
- import { execFileSync as execFileSync12 } from "child_process";
8357
+ import { execFileSync as execFileSync13 } from "child_process";
8181
8358
  function prMergeStatus(prNumber, cwd) {
8182
8359
  try {
8183
8360
  const out = gh(["pr", "view", String(prNumber), "--json", "mergeable,mergeStateStatus"], { cwd });
@@ -8298,7 +8475,7 @@ function isAlreadyExistsError(err) {
8298
8475
  return ALREADY_EXISTS_RE.test(msg);
8299
8476
  }
8300
8477
  function git2(args, cwd) {
8301
- return execFileSync12("git", args, {
8478
+ return execFileSync13("git", args, {
8302
8479
  encoding: "utf-8",
8303
8480
  timeout: 3e4,
8304
8481
  cwd,
@@ -8692,7 +8869,7 @@ var init_finalizeTerminal = __esm({
8692
8869
  });
8693
8870
 
8694
8871
  // src/scripts/finishFlow.ts
8695
- import { execFileSync as execFileSync13 } from "child_process";
8872
+ import { execFileSync as execFileSync14 } from "child_process";
8696
8873
  var TERMINAL_PHASE, API_TIMEOUT_MS5, STATUS_ICON, finishFlow;
8697
8874
  var init_finishFlow = __esm({
8698
8875
  "src/scripts/finishFlow.ts"() {
@@ -8739,7 +8916,7 @@ var init_finishFlow = __esm({
8739
8916
  **PR:** ${state.core.prUrl}` : "";
8740
8917
  const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
8741
8918
  try {
8742
- execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", body], {
8919
+ execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
8743
8920
  timeout: API_TIMEOUT_MS5,
8744
8921
  cwd: ctx.cwd,
8745
8922
  stdio: ["ignore", "pipe", "pipe"]
@@ -8771,9 +8948,9 @@ var init_finishFlow = __esm({
8771
8948
  });
8772
8949
 
8773
8950
  // src/branch.ts
8774
- import { execFileSync as execFileSync14 } from "child_process";
8951
+ import { execFileSync as execFileSync15 } from "child_process";
8775
8952
  function git3(args, cwd) {
8776
- return execFileSync14("git", args, {
8953
+ return execFileSync15("git", args, {
8777
8954
  encoding: "utf-8",
8778
8955
  timeout: 3e4,
8779
8956
  cwd,
@@ -8790,11 +8967,11 @@ function getCurrentBranch(cwd) {
8790
8967
  }
8791
8968
  function resetWorkingTree2(cwd) {
8792
8969
  try {
8793
- execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8970
+ execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8794
8971
  } catch {
8795
8972
  }
8796
8973
  try {
8797
- execFileSync14("git", ["clean", "-fd", "-e", ".kody"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8974
+ execFileSync15("git", ["clean", "-fd", "-e", ".kody"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8798
8975
  } catch {
8799
8976
  }
8800
8977
  }
@@ -8806,11 +8983,11 @@ function checkoutPrBranch(prNumber, cwd) {
8806
8983
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
8807
8984
  };
8808
8985
  try {
8809
- execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8986
+ execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8810
8987
  } catch {
8811
8988
  }
8812
8989
  try {
8813
- execFileSync14("git", ["clean", "-fd", "-e", ".kody"], {
8990
+ execFileSync15("git", ["clean", "-fd", "-e", ".kody"], {
8814
8991
  cwd,
8815
8992
  env,
8816
8993
  stdio: ["ignore", "pipe", "pipe"],
@@ -8818,7 +8995,7 @@ function checkoutPrBranch(prNumber, cwd) {
8818
8995
  });
8819
8996
  } catch {
8820
8997
  }
8821
- execFileSync14("gh", ["pr", "checkout", String(prNumber)], {
8998
+ execFileSync15("gh", ["pr", "checkout", String(prNumber)], {
8822
8999
  cwd,
8823
9000
  env,
8824
9001
  stdio: ["ignore", "pipe", "pipe"],
@@ -8851,7 +9028,7 @@ function mergeBase(baseBranch, cwd) {
8851
9028
  }
8852
9029
  function restoreKodyAssets(cwd) {
8853
9030
  try {
8854
- execFileSync14("git", ["checkout", "HEAD", "--", ".kody"], {
9031
+ execFileSync15("git", ["checkout", "HEAD", "--", ".kody"], {
8855
9032
  cwd,
8856
9033
  stdio: ["ignore", "pipe", "pipe"],
8857
9034
  timeout: 3e4
@@ -8965,14 +9142,14 @@ var init_branch = __esm({
8965
9142
  });
8966
9143
 
8967
9144
  // src/workflow.ts
8968
- import { execFileSync as execFileSync15 } from "child_process";
9145
+ import { execFileSync as execFileSync16 } from "child_process";
8969
9146
  function ghToken3() {
8970
9147
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
8971
9148
  }
8972
9149
  function gh3(args, cwd) {
8973
9150
  const token = ghToken3();
8974
9151
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
8975
- return execFileSync15("gh", args, {
9152
+ return execFileSync16("gh", args, {
8976
9153
  encoding: "utf-8",
8977
9154
  timeout: GH_TIMEOUT_MS,
8978
9155
  cwd,
@@ -9239,13 +9416,13 @@ var init_handleAbandonedGoal = __esm({
9239
9416
  });
9240
9417
 
9241
9418
  // src/scripts/initFlow.ts
9242
- import { execFileSync as execFileSync16 } from "child_process";
9243
- import * as fs30 from "fs";
9244
- import * as path28 from "path";
9419
+ import { execFileSync as execFileSync17 } from "child_process";
9420
+ import * as fs31 from "fs";
9421
+ import * as path29 from "path";
9245
9422
  function detectPackageManager(cwd) {
9246
- if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9247
- if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
9248
- if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
9423
+ if (fs31.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9424
+ if (fs31.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
9425
+ if (fs31.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
9249
9426
  return "npm";
9250
9427
  }
9251
9428
  function qualityCommandsFor(pm) {
@@ -9265,7 +9442,7 @@ function schemaUrlFromPkg() {
9265
9442
  function detectOwnerRepo(cwd) {
9266
9443
  let url;
9267
9444
  try {
9268
- url = execFileSync16("git", ["remote", "get-url", "origin"], {
9445
+ url = execFileSync17("git", ["remote", "get-url", "origin"], {
9269
9446
  cwd,
9270
9447
  encoding: "utf-8",
9271
9448
  stdio: ["ignore", "pipe", "pipe"]
@@ -9293,7 +9470,7 @@ function makeConfig(pm, ownerRepo, defaultBranch2) {
9293
9470
  }
9294
9471
  function defaultBranchFromGit(cwd) {
9295
9472
  try {
9296
- const ref = execFileSync16("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
9473
+ const ref = execFileSync17("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
9297
9474
  cwd,
9298
9475
  encoding: "utf-8",
9299
9476
  stdio: ["ignore", "pipe", "pipe"]
@@ -9301,7 +9478,7 @@ function defaultBranchFromGit(cwd) {
9301
9478
  return ref.replace("refs/remotes/origin/", "");
9302
9479
  } catch {
9303
9480
  try {
9304
- return execFileSync16("git", ["branch", "--show-current"], {
9481
+ return execFileSync17("git", ["branch", "--show-current"], {
9305
9482
  cwd,
9306
9483
  encoding: "utf-8",
9307
9484
  stdio: ["ignore", "pipe", "pipe"]
@@ -9317,51 +9494,51 @@ function performInit(cwd, force) {
9317
9494
  const pm = detectPackageManager(cwd);
9318
9495
  const ownerRepo = detectOwnerRepo(cwd);
9319
9496
  const defaultBranch2 = defaultBranchFromGit(cwd);
9320
- const configPath = path28.join(cwd, "kody.config.json");
9321
- if (fs30.existsSync(configPath) && !force) {
9497
+ const configPath = path29.join(cwd, "kody.config.json");
9498
+ if (fs31.existsSync(configPath) && !force) {
9322
9499
  skipped.push("kody.config.json");
9323
9500
  } else {
9324
9501
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
9325
- fs30.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9502
+ fs31.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9326
9503
  `);
9327
9504
  wrote.push("kody.config.json");
9328
9505
  }
9329
- const workflowDir = path28.join(cwd, ".github", "workflows");
9330
- const workflowPath = path28.join(workflowDir, "kody.yml");
9331
- if (fs30.existsSync(workflowPath) && !force) {
9506
+ const workflowDir = path29.join(cwd, ".github", "workflows");
9507
+ const workflowPath = path29.join(workflowDir, "kody.yml");
9508
+ if (fs31.existsSync(workflowPath) && !force) {
9332
9509
  skipped.push(".github/workflows/kody.yml");
9333
9510
  } else {
9334
- fs30.mkdirSync(workflowDir, { recursive: true });
9335
- fs30.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9511
+ fs31.mkdirSync(workflowDir, { recursive: true });
9512
+ fs31.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9336
9513
  wrote.push(".github/workflows/kody.yml");
9337
9514
  }
9338
9515
  const builtinJobs = listBuiltinJobs();
9339
9516
  if (builtinJobs.length > 0) {
9340
- const jobsDir = path28.join(cwd, ".kody", "duties");
9341
- fs30.mkdirSync(jobsDir, { recursive: true });
9517
+ const jobsDir = path29.join(cwd, ".kody", "duties");
9518
+ fs31.mkdirSync(jobsDir, { recursive: true });
9342
9519
  for (const job of builtinJobs) {
9343
- const targetDir = path28.join(jobsDir, job.slug);
9344
- const relProfile = path28.join(".kody", "duties", job.slug, "profile.json");
9345
- const relBody = path28.join(".kody", "duties", job.slug, "duty.md");
9346
- if (fs30.existsSync(targetDir) && fs30.existsSync(path28.join(targetDir, "profile.json")) && !force) {
9520
+ const targetDir = path29.join(jobsDir, job.slug);
9521
+ const relProfile = path29.join(".kody", "duties", job.slug, "profile.json");
9522
+ const relBody = path29.join(".kody", "duties", job.slug, "duty.md");
9523
+ if (fs31.existsSync(targetDir) && fs31.existsSync(path29.join(targetDir, "profile.json")) && !force) {
9347
9524
  skipped.push(relProfile);
9348
9525
  skipped.push(relBody);
9349
9526
  continue;
9350
9527
  }
9351
- fs30.mkdirSync(targetDir, { recursive: true });
9352
- fs30.writeFileSync(path28.join(targetDir, "profile.json"), fs30.readFileSync(job.profilePath, "utf-8"));
9353
- fs30.writeFileSync(path28.join(targetDir, "duty.md"), fs30.readFileSync(job.bodyPath, "utf-8"));
9528
+ fs31.mkdirSync(targetDir, { recursive: true });
9529
+ fs31.writeFileSync(path29.join(targetDir, "profile.json"), fs31.readFileSync(job.profilePath, "utf-8"));
9530
+ fs31.writeFileSync(path29.join(targetDir, "duty.md"), fs31.readFileSync(job.bodyPath, "utf-8"));
9354
9531
  wrote.push(relProfile);
9355
9532
  wrote.push(relBody);
9356
9533
  }
9357
9534
  }
9358
- const staffDir = path28.join(cwd, ".kody", "staff");
9359
- const staffPath = path28.join(staffDir, "kody.md");
9360
- if (fs30.existsSync(staffPath) && !force) {
9535
+ const staffDir = path29.join(cwd, ".kody", "staff");
9536
+ const staffPath = path29.join(staffDir, "kody.md");
9537
+ if (fs31.existsSync(staffPath) && !force) {
9361
9538
  skipped.push(".kody/staff/kody.md");
9362
9539
  } else {
9363
- fs30.mkdirSync(staffDir, { recursive: true });
9364
- fs30.writeFileSync(staffPath, DEFAULT_STAFF_PERSONA);
9540
+ fs31.mkdirSync(staffDir, { recursive: true });
9541
+ fs31.writeFileSync(staffPath, DEFAULT_STAFF_PERSONA);
9365
9542
  wrote.push(".kody/staff/kody.md");
9366
9543
  }
9367
9544
  for (const exe of listExecutables()) {
@@ -9372,12 +9549,12 @@ function performInit(cwd, force) {
9372
9549
  continue;
9373
9550
  }
9374
9551
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
9375
- const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
9376
- if (fs30.existsSync(target) && !force) {
9552
+ const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
9553
+ if (fs31.existsSync(target) && !force) {
9377
9554
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
9378
9555
  continue;
9379
9556
  }
9380
- fs30.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9557
+ fs31.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9381
9558
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
9382
9559
  }
9383
9560
  let labels;
@@ -9390,7 +9567,7 @@ function performInit(cwd, force) {
9390
9567
  }
9391
9568
  function renderScheduledWorkflow(name, cron) {
9392
9569
  return `# Scheduled kody duty: ${name}
9393
- # Generated by \`kody init\`. Regenerate with \`kody init --force\`.
9570
+ # Generated by \`kody-engine init\`. Regenerate with \`kody-engine init --force\`.
9394
9571
  # Edit the cron below or the duty's implementation profile.json#schedule.
9395
9572
 
9396
9573
  name: kody ${name}
@@ -9453,6 +9630,16 @@ on:
9453
9630
  description: "GitHub issue number"
9454
9631
  required: true
9455
9632
  type: string
9633
+ duty:
9634
+ description: "Duty action to run (default: run)"
9635
+ required: false
9636
+ type: string
9637
+ default: ""
9638
+ executable:
9639
+ description: "Legacy alias for duty action"
9640
+ required: false
9641
+ type: string
9642
+ default: ""
9456
9643
  issue_comment:
9457
9644
  types: [created]
9458
9645
 
@@ -9498,7 +9685,7 @@ When a duty writes a report or dispatches work, keep the output factual and conc
9498
9685
  const force = ctx.args.force === true;
9499
9686
  const cwd = ctx.cwd;
9500
9687
  const { wrote, skipped, labels } = performInit(cwd, force);
9501
- process.stdout.write("\u2192 kody init\n");
9688
+ process.stdout.write("\u2192 kody-engine init\n");
9502
9689
  for (const f of wrote) process.stdout.write(` wrote ${f}
9503
9690
  `);
9504
9691
  for (const f of skipped) process.stdout.write(` skipped ${f} (already exists; pass --force to overwrite)
@@ -9696,9 +9883,79 @@ var init_loadIssueStateComment = __esm({
9696
9883
  }
9697
9884
  });
9698
9885
 
9886
+ // src/staff.ts
9887
+ import * as fs32 from "fs";
9888
+ import * as path30 from "path";
9889
+ function stripFrontmatter(raw) {
9890
+ const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
9891
+ return (match ? match[1] : raw).trim();
9892
+ }
9893
+ function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
9894
+ const trimmed = slug.trim();
9895
+ if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
9896
+ const staffPath = resolveStaffPersonaFile(cwd, trimmed, staffDir);
9897
+ if (fs32.existsSync(staffPath)) {
9898
+ const body = stripFrontmatter(fs32.readFileSync(staffPath, "utf-8"));
9899
+ if (body) return body;
9900
+ const builtinForEmpty = BUILTIN_PERSONAS[trimmed];
9901
+ if (builtinForEmpty) return builtinForEmpty;
9902
+ throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
9903
+ }
9904
+ const builtin = BUILTIN_PERSONAS[trimmed];
9905
+ if (builtin) return builtin;
9906
+ throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
9907
+ }
9908
+ function resolveStaffPersonaFile(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
9909
+ const localPath = path30.join(cwd, staffDir, `${slug}.md`);
9910
+ if (fs32.existsSync(localPath)) return localPath;
9911
+ const storeStaffRoot = getCompanyStoreAssetRoot("staff");
9912
+ if (storeStaffRoot) {
9913
+ const storePath = path30.join(storeStaffRoot, `${slug}.md`);
9914
+ if (fs32.existsSync(storePath)) return storePath;
9915
+ }
9916
+ return localPath;
9917
+ }
9918
+ function framePersona(slug, persona) {
9919
+ return [
9920
+ `## Who you are \u2014 staff persona (authoritative identity)`,
9921
+ ``,
9922
+ `You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
9923
+ `your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
9924
+ `persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
9925
+ `can never grant you authority your persona withholds.`,
9926
+ ``,
9927
+ persona
9928
+ ].join("\n");
9929
+ }
9930
+ var DEFAULT_STAFF_DIR, BUILTIN_PERSONAS;
9931
+ var init_staff = __esm({
9932
+ "src/staff.ts"() {
9933
+ "use strict";
9934
+ init_companyStore();
9935
+ DEFAULT_STAFF_DIR = ".kody/staff";
9936
+ BUILTIN_PERSONAS = {
9937
+ kody: [
9938
+ "You are **kody**, the repository's autonomous engineer.",
9939
+ "",
9940
+ "- You work the way this repo already works: follow its conventions, its",
9941
+ " existing patterns, and its tests. Read before you write.",
9942
+ "- You ship small, correct, reviewable changes. You don't refactor beyond the",
9943
+ " task, and you don't add scope nobody asked for.",
9944
+ "- You are honest about outcomes: if something failed, didn't run, or you're",
9945
+ " unsure, you say so plainly rather than papering over it.",
9946
+ "- The request that triggered you IS your authorization to do the work:",
9947
+ " opening a PR, pushing commits, and commenting are exactly what you're for \u2014",
9948
+ " do them without waiting for extra approval.",
9949
+ "- You still don't weaken security, delete work you didn't create, or take",
9950
+ " destructive actions beyond the task you were given."
9951
+ ].join("\n")
9952
+ };
9953
+ }
9954
+ });
9955
+
9699
9956
  // src/scripts/loadJobFromFile.ts
9700
- import * as fs31 from "fs";
9701
- import * as path29 from "path";
9957
+ import * as fs33 from "fs";
9958
+ import * as path31 from "path";
9702
9959
  function parseJobFile(raw, slug) {
9703
9960
  let stripped = raw;
9704
9961
  if (stripped.startsWith("---\n")) {
@@ -9723,8 +9980,9 @@ var DUTY_TOOL_PALETTE2, loadJobFromFile;
9723
9980
  var init_loadJobFromFile = __esm({
9724
9981
  "src/scripts/loadJobFromFile.ts"() {
9725
9982
  "use strict";
9726
- init_dutyFolders();
9727
9983
  init_dutyMcp();
9984
+ init_registry();
9985
+ init_staff();
9728
9986
  init_jobState();
9729
9987
  DUTY_TOOL_PALETTE2 = new Set(DUTY_MCP_TOOL_NAMES);
9730
9988
  loadJobFromFile = async (ctx, profile, args) => {
@@ -9735,9 +9993,9 @@ var init_loadJobFromFile = __esm({
9735
9993
  if (!slug) {
9736
9994
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
9737
9995
  }
9738
- const duty = readDutyFolder(path29.join(ctx.cwd, jobsDir), slug);
9996
+ const duty = resolveDutyFolder(slug, path31.join(ctx.cwd, jobsDir));
9739
9997
  if (!duty) {
9740
- throw new Error(`loadJobFromFile: duty folder not found or incomplete: ${path29.join(ctx.cwd, jobsDir, slug)}`);
9998
+ throw new Error(`loadJobFromFile: duty folder not found or incomplete: ${path31.join(ctx.cwd, jobsDir, slug)}`);
9741
9999
  }
9742
10000
  const { title, body, config } = duty;
9743
10001
  const mentions = (config.mentions ?? []).map((login) => `@${login}`).join(" ");
@@ -9745,11 +10003,11 @@ var init_loadJobFromFile = __esm({
9745
10003
  let workerTitle = "";
9746
10004
  let workerPersona = "";
9747
10005
  if (workerSlug) {
9748
- const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9749
- if (!fs31.existsSync(workerPath)) {
10006
+ const workerPath = resolveStaffPersonaFile(ctx.cwd, workerSlug, workersDir);
10007
+ if (!fs33.existsSync(workerPath)) {
9750
10008
  throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
9751
10009
  }
9752
- const workerRaw = fs31.readFileSync(workerPath, "utf-8");
10010
+ const workerRaw = fs33.readFileSync(workerPath, "utf-8");
9753
10011
  const parsed = parseJobFile(workerRaw, workerSlug);
9754
10012
  workerTitle = parsed.title;
9755
10013
  workerPersona = parsed.body;
@@ -9823,13 +10081,13 @@ ${truncate2(issue.body, FINDING_BODY_MAX_BYTES)}`;
9823
10081
  });
9824
10082
 
9825
10083
  // src/scripts/kodyVariables.ts
9826
- import * as fs32 from "fs";
9827
- import * as path30 from "path";
10084
+ import * as fs34 from "fs";
10085
+ import * as path32 from "path";
9828
10086
  function readKodyVariables(cwd) {
9829
- const full = path30.join(cwd, KODY_VARIABLES_REL_PATH);
10087
+ const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
9830
10088
  let raw;
9831
10089
  try {
9832
- raw = fs32.readFileSync(full, "utf-8");
10090
+ raw = fs34.readFileSync(full, "utf-8");
9833
10091
  } catch {
9834
10092
  return {};
9835
10093
  }
@@ -9854,8 +10112,8 @@ var init_kodyVariables = __esm({
9854
10112
  });
9855
10113
 
9856
10114
  // src/scripts/loadQaContext.ts
9857
- import * as fs33 from "fs";
9858
- import * as path31 from "path";
10115
+ import * as fs35 from "fs";
10116
+ import * as path33 from "path";
9859
10117
  function parseSlugList(value) {
9860
10118
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
9861
10119
  return inner.split(",").map(
@@ -9884,18 +10142,18 @@ function readProfileStaff(raw) {
9884
10142
  return { staff: staff ?? legacy ?? ["kody"], body };
9885
10143
  }
9886
10144
  function readProfile(cwd) {
9887
- const dir = path31.join(cwd, CONTEXT_DIR_REL_PATH);
9888
- if (!fs33.existsSync(dir)) return "";
10145
+ const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
10146
+ if (!fs35.existsSync(dir)) return "";
9889
10147
  let entries;
9890
10148
  try {
9891
- entries = fs33.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
10149
+ entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
9892
10150
  } catch {
9893
10151
  return "";
9894
10152
  }
9895
10153
  const blocks = [];
9896
10154
  for (const file of entries) {
9897
10155
  try {
9898
- const raw = fs33.readFileSync(path31.join(dir, file), "utf-8");
10156
+ const raw = fs35.readFileSync(path33.join(dir, file), "utf-8");
9899
10157
  const { staff, body } = readProfileStaff(raw);
9900
10158
  if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
9901
10159
  blocks.push(`## ${file}
@@ -9941,8 +10199,8 @@ var init_loadQaContext = __esm({
9941
10199
  });
9942
10200
 
9943
10201
  // src/taskContext.ts
9944
- import * as fs34 from "fs";
9945
- import * as path32 from "path";
10202
+ import * as fs36 from "fs";
10203
+ import * as path34 from "path";
9946
10204
  function buildTaskContext(args) {
9947
10205
  return {
9948
10206
  schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
@@ -9957,10 +10215,10 @@ function buildTaskContext(args) {
9957
10215
  }
9958
10216
  function persistTaskContext(cwd, ctx) {
9959
10217
  try {
9960
- const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
9961
- fs34.mkdirSync(dir, { recursive: true });
9962
- const file = path32.join(dir, "task-context.json");
9963
- fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
10218
+ const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
10219
+ fs36.mkdirSync(dir, { recursive: true });
10220
+ const file = path34.join(dir, "task-context.json");
10221
+ fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
9964
10222
  `);
9965
10223
  return file;
9966
10224
  } catch (err) {
@@ -10048,8 +10306,7 @@ var init_loadTaskState = __esm({
10048
10306
  });
10049
10307
 
10050
10308
  // src/scripts/loadWorkerAdhoc.ts
10051
- import * as fs35 from "fs";
10052
- import * as path33 from "path";
10309
+ import * as fs37 from "fs";
10053
10310
  function resolveMessage(messageArg) {
10054
10311
  const fromComment = readCommentBody();
10055
10312
  if (fromComment) return stripDirective(fromComment);
@@ -10057,9 +10314,9 @@ function resolveMessage(messageArg) {
10057
10314
  }
10058
10315
  function readCommentBody() {
10059
10316
  const eventPath = process.env.GITHUB_EVENT_PATH;
10060
- if (!eventPath || !fs35.existsSync(eventPath)) return "";
10317
+ if (!eventPath || !fs37.existsSync(eventPath)) return "";
10061
10318
  try {
10062
- const event = JSON.parse(fs35.readFileSync(eventPath, "utf-8"));
10319
+ const event = JSON.parse(fs37.readFileSync(eventPath, "utf-8"));
10063
10320
  return String(event.comment?.body ?? "");
10064
10321
  } catch {
10065
10322
  return "";
@@ -10104,17 +10361,18 @@ var loadWorkerAdhoc;
10104
10361
  var init_loadWorkerAdhoc = __esm({
10105
10362
  "src/scripts/loadWorkerAdhoc.ts"() {
10106
10363
  "use strict";
10364
+ init_staff();
10107
10365
  loadWorkerAdhoc = async (ctx, _profile, args) => {
10108
10366
  const workersDir = String(args?.workersDir ?? ".kody/staff");
10109
10367
  const workerSlug = String(ctx.args.worker ?? "").trim();
10110
10368
  if (!workerSlug) {
10111
10369
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
10112
10370
  }
10113
- const workerPath = path33.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10114
- if (!fs35.existsSync(workerPath)) {
10371
+ const workerPath = resolveStaffPersonaFile(ctx.cwd, workerSlug, workersDir);
10372
+ if (!fs37.existsSync(workerPath)) {
10115
10373
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
10116
10374
  }
10117
- const { title, body } = parsePersona(fs35.readFileSync(workerPath, "utf-8"), workerSlug);
10375
+ const { title, body } = parsePersona(fs37.readFileSync(workerPath, "utf-8"), workerSlug);
10118
10376
  const message = resolveMessage(ctx.args.message);
10119
10377
  if (!message) {
10120
10378
  throw new Error(
@@ -10257,7 +10515,7 @@ var init_mergeFlow = __esm({
10257
10515
  });
10258
10516
 
10259
10517
  // src/scripts/mergeReleasePr.ts
10260
- import { execFileSync as execFileSync17 } from "child_process";
10518
+ import { execFileSync as execFileSync18 } from "child_process";
10261
10519
  function makeAction(type, payload) {
10262
10520
  return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
10263
10521
  }
@@ -10289,7 +10547,7 @@ var init_mergeReleasePr = __esm({
10289
10547
  process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
10290
10548
  `);
10291
10549
  try {
10292
- const out = execFileSync17("gh", ["pr", "merge", String(prNumber), "--merge"], {
10550
+ const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
10293
10551
  timeout: API_TIMEOUT_MS6,
10294
10552
  cwd: ctx.cwd,
10295
10553
  stdio: ["ignore", "pipe", "pipe"]
@@ -11085,7 +11343,7 @@ var init_promoteQaGoal = __esm({
11085
11343
  });
11086
11344
 
11087
11345
  // src/scripts/recordClassification.ts
11088
- import { execFileSync as execFileSync18 } from "child_process";
11346
+ import { execFileSync as execFileSync19 } from "child_process";
11089
11347
  function parseClassification(prSummary) {
11090
11348
  if (!prSummary) return null;
11091
11349
  const classMatch = prSummary.match(/classification:\s*(feature|bug|spec|chore)\b/i);
@@ -11097,7 +11355,7 @@ function parseClassification(prSummary) {
11097
11355
  }
11098
11356
  function tryAuditComment(issueNumber, body, cwd) {
11099
11357
  try {
11100
- execFileSync18("gh", ["issue", "comment", String(issueNumber), "--body", body], {
11358
+ execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
11101
11359
  cwd,
11102
11360
  timeout: API_TIMEOUT_MS7,
11103
11361
  stdio: ["ignore", "pipe", "pipe"]
@@ -11277,7 +11535,7 @@ var init_resolveArtifacts = __esm({
11277
11535
  });
11278
11536
 
11279
11537
  // src/scripts/resolveFlow.ts
11280
- import { execFileSync as execFileSync19 } from "child_process";
11538
+ import { execFileSync as execFileSync20 } from "child_process";
11281
11539
  function buildPreferBlock(prefer, baseBranch) {
11282
11540
  if (prefer !== "ours" && prefer !== "theirs") return "";
11283
11541
  const keepSide = prefer === "ours" ? "HEAD (this PR branch)" : `origin/${baseBranch} (base branch)`;
@@ -11297,7 +11555,7 @@ function buildPreferBlock(prefer, baseBranch) {
11297
11555
  }
11298
11556
  function getConflictedFiles(cwd) {
11299
11557
  try {
11300
- const out = execFileSync19("git", ["diff", "--name-only", "--diff-filter=U"], {
11558
+ const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
11301
11559
  encoding: "utf-8",
11302
11560
  cwd,
11303
11561
  env: { ...process.env, HUSKY: "0" }
@@ -11312,7 +11570,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
11312
11570
  let total = 0;
11313
11571
  for (const f of files) {
11314
11572
  try {
11315
- const content = execFileSync19("cat", [f], { encoding: "utf-8", cwd }).toString();
11573
+ const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
11316
11574
  const snippet = `### ${f}
11317
11575
 
11318
11576
  \`\`\`
@@ -11336,12 +11594,12 @@ function tryPostPr3(prNumber, body, cwd) {
11336
11594
  function pushEmptyCommit(branch, cwd) {
11337
11595
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
11338
11596
  try {
11339
- execFileSync19(
11597
+ execFileSync20(
11340
11598
  "git",
11341
11599
  ["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
11342
11600
  { cwd, env, stdio: ["ignore", "pipe", "pipe"] }
11343
11601
  );
11344
- execFileSync19("git", ["push", "-u", "origin", branch], {
11602
+ execFileSync20("git", ["push", "-u", "origin", branch], {
11345
11603
  cwd,
11346
11604
  env,
11347
11605
  stdio: ["ignore", "pipe", "pipe"]
@@ -11527,10 +11785,10 @@ var init_resolvePreviewUrl = __esm({
11527
11785
  });
11528
11786
 
11529
11787
  // src/scripts/resolveQaUrl.ts
11530
- import { execFileSync as execFileSync20 } from "child_process";
11788
+ import { execFileSync as execFileSync21 } from "child_process";
11531
11789
  function ghQuery(args, cwd) {
11532
11790
  try {
11533
- const out = execFileSync20("gh", args, {
11791
+ const out = execFileSync21("gh", args, {
11534
11792
  cwd,
11535
11793
  stdio: ["ignore", "pipe", "pipe"],
11536
11794
  encoding: "utf-8",
@@ -11607,7 +11865,7 @@ var init_resolveQaUrl = __esm({
11607
11865
  });
11608
11866
 
11609
11867
  // src/scripts/revertFlow.ts
11610
- import { execFileSync as execFileSync21 } from "child_process";
11868
+ import { execFileSync as execFileSync22 } from "child_process";
11611
11869
  function buildCommitMessage(resolved) {
11612
11870
  if (resolved.length === 1) {
11613
11871
  const { full, subject } = resolved[0];
@@ -11620,7 +11878,7 @@ function buildPrSummary(resolved) {
11620
11878
  return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
11621
11879
  }
11622
11880
  function git4(args, cwd) {
11623
- return execFileSync21("git", args, {
11881
+ return execFileSync22("git", args, {
11624
11882
  encoding: "utf-8",
11625
11883
  timeout: 3e4,
11626
11884
  cwd,
@@ -11630,7 +11888,7 @@ function git4(args, cwd) {
11630
11888
  }
11631
11889
  function isAncestorOfHead(sha, cwd) {
11632
11890
  try {
11633
- execFileSync21("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
11891
+ execFileSync22("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
11634
11892
  cwd,
11635
11893
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
11636
11894
  stdio: ["ignore", "ignore", "ignore"]
@@ -11825,9 +12083,9 @@ var init_runFlow = __esm({
11825
12083
  });
11826
12084
 
11827
12085
  // src/scripts/previewBuildHelpers.ts
11828
- import { createDecipheriv, createHash } from "crypto";
12086
+ import { createDecipheriv, createHash as createHash2 } from "crypto";
11829
12087
  function shortHash(s) {
11830
- return createHash("sha256").update(s).digest("hex").slice(0, 6);
12088
+ return createHash2("sha256").update(s).digest("hex").slice(0, 6);
11831
12089
  }
11832
12090
  function previewAppName(repo, pr) {
11833
12091
  const [owner, name] = repo.split("/");
@@ -11881,7 +12139,7 @@ function formatPreviewComment(args) {
11881
12139
  ].join("\n");
11882
12140
  }
11883
12141
  function defaultImageTag(repo, ref) {
11884
- return createHash("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
12142
+ return createHash2("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
11885
12143
  }
11886
12144
  var NEVER_PASS_TO_BUILD;
11887
12145
  var init_previewBuildHelpers = __esm({
@@ -11985,12 +12243,12 @@ fi
11985
12243
 
11986
12244
  // src/scripts/runPreviewBuild.ts
11987
12245
  import { copyFile, writeFile } from "fs/promises";
11988
- import * as path34 from "path";
12246
+ import * as path35 from "path";
11989
12247
  import { fileURLToPath } from "url";
11990
12248
  function bundledDockerfilePath(mode) {
11991
- const here = path34.dirname(fileURLToPath(import.meta.url));
12249
+ const here = path35.dirname(fileURLToPath(import.meta.url));
11992
12250
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
11993
- return path34.join(here, "preview-build-templates", file);
12251
+ return path35.join(here, "preview-build-templates", file);
11994
12252
  }
11995
12253
  function required(name) {
11996
12254
  const v = (process.env[name] ?? "").trim();
@@ -12249,10 +12507,10 @@ var init_runPreviewBuild = __esm({
12249
12507
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
12250
12508
  if (Object.keys(buildEnv).length > 0) {
12251
12509
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
12252
- await writeFile(path34.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
12510
+ await writeFile(path35.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
12253
12511
  `, "utf8");
12254
12512
  }
12255
- const consumerDockerfile = path34.join(ctx.cwd, "Dockerfile.preview");
12513
+ const consumerDockerfile = path35.join(ctx.cwd, "Dockerfile.preview");
12256
12514
  const { stat } = await import("fs/promises");
12257
12515
  let hasConsumerDockerfile = false;
12258
12516
  try {
@@ -12352,11 +12610,46 @@ var init_runPreviewBuild = __esm({
12352
12610
  }
12353
12611
  });
12354
12612
 
12355
- // src/scripts/runTickScript.ts
12613
+ // src/scripts/tickShellRunner.ts
12356
12614
  import { spawnSync as spawnSync2 } from "child_process";
12357
- import * as fs36 from "fs";
12358
- import * as path35 from "path";
12359
- function buildChildEnv(parent, force) {
12615
+ function runTickShellAndParse(opts) {
12616
+ const childEnv = buildTickChildEnv(process.env, opts.force);
12617
+ const result = spawnSync2("bash", [opts.scriptPath], {
12618
+ cwd: opts.ctx.cwd,
12619
+ env: childEnv,
12620
+ stdio: ["ignore", "pipe", "pipe"],
12621
+ encoding: "utf-8",
12622
+ timeout: 5 * 60 * 1e3,
12623
+ maxBuffer: 16 * 1024 * 1024
12624
+ });
12625
+ if (result.stdout) process.stdout.write(result.stdout);
12626
+ if (result.stderr) process.stderr.write(result.stderr);
12627
+ if (result.error) {
12628
+ opts.ctx.output.exitCode = 99;
12629
+ opts.ctx.output.reason = `${opts.displayName}: spawn error: ${result.error.message}`;
12630
+ return;
12631
+ }
12632
+ if (result.signal) {
12633
+ opts.ctx.output.exitCode = 124;
12634
+ opts.ctx.output.reason = `${opts.displayName}: ${opts.reasonSubject ? `${opts.reasonSubject} ` : ""}killed by ${result.signal} (likely 5min timeout)`;
12635
+ return;
12636
+ }
12637
+ if (result.status !== 0) {
12638
+ opts.ctx.output.exitCode = result.status ?? 99;
12639
+ opts.ctx.output.reason = `${opts.displayName}: ${opts.reasonSubject ? `${opts.reasonSubject} ` : ""}exited ${result.status}`;
12640
+ return;
12641
+ }
12642
+ const prevRev = opts.loaded.state.rev ?? 0;
12643
+ const parsed = extractNextStateFromText(result.stdout ?? "", opts.fenceLabel, prevRev);
12644
+ if (parsed.error) {
12645
+ opts.ctx.data.nextStateParseError = parsed.error;
12646
+ opts.ctx.output.exitCode = 1;
12647
+ opts.ctx.output.reason = `${opts.displayName}: ${parsed.error}`;
12648
+ return;
12649
+ }
12650
+ opts.ctx.data.nextJobState = parsed.envelope;
12651
+ }
12652
+ function buildTickChildEnv(parent, force) {
12360
12653
  const allow = /* @__PURE__ */ new Set([
12361
12654
  "PATH",
12362
12655
  "HOME",
@@ -12367,45 +12660,104 @@ function buildChildEnv(parent, force) {
12367
12660
  "LANG",
12368
12661
  "LC_ALL",
12369
12662
  "TERM",
12370
- // GitHub auth — `gh` reads these.
12371
12663
  "GH_TOKEN",
12372
12664
  "GH_PAT",
12373
12665
  "GITHUB_TOKEN",
12374
- // CI metadata commonly read by tick scripts (`gh repo view`,
12375
- // workflow run links, etc.). All public values from GitHub Actions.
12376
- "GITHUB_ACTIONS",
12377
- "GITHUB_ACTOR",
12378
12666
  "GITHUB_REPOSITORY",
12379
- "GITHUB_REPOSITORY_OWNER",
12380
12667
  "GITHUB_REF",
12381
12668
  "GITHUB_SHA",
12382
12669
  "GITHUB_RUN_ID",
12383
- "GITHUB_RUN_NUMBER",
12670
+ "GITHUB_RUN_ATTEMPT",
12384
12671
  "GITHUB_WORKFLOW",
12385
- "GITHUB_JOB",
12386
- "GITHUB_SERVER_URL",
12387
- "GITHUB_API_URL",
12388
- "GITHUB_EVENT_NAME",
12389
- "RUNNER_OS",
12390
- "RUNNER_ARCH"
12672
+ "GITHUB_ACTIONS",
12673
+ "CI",
12674
+ "KODY_DRY_RUN",
12675
+ "KODY_NO_COMMIT"
12391
12676
  ]);
12392
- const out = {};
12677
+ const env = {};
12393
12678
  for (const [key, value] of Object.entries(parent)) {
12394
12679
  if (value === void 0) continue;
12395
- if (allow.has(key) || key.startsWith("KODY_PUBLIC_")) {
12396
- out[key] = value;
12397
- }
12680
+ if (allow.has(key) || key.startsWith("KODY_PUBLIC_")) env[key] = value;
12398
12681
  }
12399
- if (force) out.KODY_FORCE = "1";
12400
- return out;
12682
+ if (force) env.KODY_FORCE = "1";
12683
+ return env;
12401
12684
  }
12685
+ var init_tickShellRunner = __esm({
12686
+ "src/scripts/tickShellRunner.ts"() {
12687
+ "use strict";
12688
+ init_parseJobStateFromAgentResult();
12689
+ }
12690
+ });
12691
+
12692
+ // src/scripts/runScheduledExecutableTick.ts
12693
+ import * as fs38 from "fs";
12694
+ import * as path36 from "path";
12695
+ var runScheduledExecutableTick;
12696
+ var init_runScheduledExecutableTick = __esm({
12697
+ "src/scripts/runScheduledExecutableTick.ts"() {
12698
+ "use strict";
12699
+ init_dutyFolders();
12700
+ init_jobState();
12701
+ init_tickShellRunner();
12702
+ runScheduledExecutableTick = async (ctx, profile, args) => {
12703
+ ctx.skipAgent = true;
12704
+ const jobsDir = String(args?.jobsDir ?? ".kody/duties");
12705
+ const slugArg = String(args?.slugArg ?? "duty");
12706
+ const fenceLabel = String(args?.fenceLabel ?? "kody-job-next-state");
12707
+ const shell = String(args?.shell ?? "tick.sh");
12708
+ const slug = String(ctx.args[slugArg] ?? "").trim();
12709
+ if (!slug) {
12710
+ ctx.output.exitCode = 99;
12711
+ ctx.output.reason = `runScheduledExecutableTick: ctx.args.${slugArg} must be non-empty duty slug`;
12712
+ return;
12713
+ }
12714
+ const duty = readDutyFolder(path36.join(ctx.cwd, jobsDir), slug);
12715
+ if (!duty) {
12716
+ ctx.output.exitCode = 99;
12717
+ ctx.output.reason = `runScheduledExecutableTick: duty folder not found or incomplete: ${path36.join(jobsDir, slug)}`;
12718
+ return;
12719
+ }
12720
+ const shellPath = path36.join(profile.dir, shell);
12721
+ if (!fs38.existsSync(shellPath)) {
12722
+ ctx.output.exitCode = 99;
12723
+ ctx.output.reason = `runScheduledExecutableTick: shell not found: ${shell} (looked in ${profile.dir})`;
12724
+ return;
12725
+ }
12726
+ const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
12727
+ let loaded;
12728
+ try {
12729
+ loaded = await backend.load(slug);
12730
+ } catch (err) {
12731
+ ctx.output.exitCode = 99;
12732
+ ctx.output.reason = `runScheduledExecutableTick: state load failed: ${err instanceof Error ? err.message : String(err)}`;
12733
+ return;
12734
+ }
12735
+ ctx.data.jobSlug = slug;
12736
+ ctx.data.dutySlug = slug;
12737
+ ctx.data.executableSlug = profile.name;
12738
+ ctx.data.jobState = loaded;
12739
+ runTickShellAndParse({
12740
+ ctx,
12741
+ loaded,
12742
+ scriptPath: shellPath,
12743
+ displayName: `runScheduledExecutableTick: ${shell}`,
12744
+ fenceLabel,
12745
+ force: Boolean(ctx.args.force)
12746
+ });
12747
+ };
12748
+ }
12749
+ });
12750
+
12751
+ // src/scripts/runTickScript.ts
12752
+ import * as fs39 from "fs";
12753
+ import * as path37 from "path";
12402
12754
  var runTickScript;
12403
12755
  var init_runTickScript = __esm({
12404
12756
  "src/scripts/runTickScript.ts"() {
12405
12757
  "use strict";
12406
12758
  init_dutyFolders();
12407
12759
  init_jobState();
12408
- init_parseJobStateFromAgentResult();
12760
+ init_tickShellRunner();
12409
12761
  runTickScript = async (ctx, _profile, args) => {
12410
12762
  ctx.skipAgent = true;
12411
12763
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -12417,10 +12769,10 @@ var init_runTickScript = __esm({
12417
12769
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
12418
12770
  return;
12419
12771
  }
12420
- const duty = readDutyFolder(path35.join(ctx.cwd, jobsDir), slug);
12772
+ const duty = readDutyFolder(path37.join(ctx.cwd, jobsDir), slug);
12421
12773
  if (!duty) {
12422
12774
  ctx.output.exitCode = 99;
12423
- ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${path35.join(ctx.cwd, jobsDir, slug)}`;
12775
+ ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${path37.join(ctx.cwd, jobsDir, slug)}`;
12424
12776
  return;
12425
12777
  }
12426
12778
  const tickScript = duty.config.tickScript;
@@ -12429,8 +12781,8 @@ var init_runTickScript = __esm({
12429
12781
  ctx.output.reason = `runTickScript: duty ${slug} has no \`tickScript\` in profile.json \u2014 route via duty-tick instead`;
12430
12782
  return;
12431
12783
  }
12432
- const scriptPath = path35.isAbsolute(tickScript) ? tickScript : path35.join(ctx.cwd, tickScript);
12433
- if (!fs36.existsSync(scriptPath)) {
12784
+ const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
12785
+ if (!fs39.existsSync(scriptPath)) {
12434
12786
  ctx.output.exitCode = 99;
12435
12787
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
12436
12788
  return;
@@ -12446,46 +12798,15 @@ var init_runTickScript = __esm({
12446
12798
  }
12447
12799
  ctx.data.jobSlug = slug;
12448
12800
  ctx.data.jobState = loaded;
12449
- const childEnv = buildChildEnv(process.env, Boolean(ctx.args.force));
12450
- const result = spawnSync2("bash", [scriptPath], {
12451
- cwd: ctx.cwd,
12452
- env: childEnv,
12453
- stdio: ["ignore", "pipe", "pipe"],
12454
- encoding: "utf-8",
12455
- timeout: 5 * 60 * 1e3,
12456
- // Default maxBuffer is 1MB — a chatty `gh pr list --json …` over a
12457
- // busy repo (or an accidental `set -x`) can blow that and silently
12458
- // truncate stdout, which is the exact "silent state drop" failure
12459
- // mode this whole executable was written to prevent. 16MB is well
12460
- // above any realistic tick output.
12461
- maxBuffer: 16 * 1024 * 1024
12801
+ runTickShellAndParse({
12802
+ ctx,
12803
+ loaded,
12804
+ scriptPath,
12805
+ displayName: "runTickScript",
12806
+ reasonSubject: tickScript,
12807
+ fenceLabel,
12808
+ force: Boolean(ctx.args.force)
12462
12809
  });
12463
- if (result.stdout) process.stdout.write(result.stdout);
12464
- if (result.stderr) process.stderr.write(result.stderr);
12465
- if (result.error) {
12466
- ctx.output.exitCode = 99;
12467
- ctx.output.reason = `runTickScript: spawn error: ${result.error.message}`;
12468
- return;
12469
- }
12470
- if (result.signal) {
12471
- ctx.output.exitCode = 124;
12472
- ctx.output.reason = `runTickScript: ${tickScript} killed by ${result.signal} (likely 5min timeout)`;
12473
- return;
12474
- }
12475
- if (result.status !== 0) {
12476
- ctx.output.exitCode = result.status ?? 99;
12477
- ctx.output.reason = `runTickScript: ${tickScript} exited ${result.status}`;
12478
- return;
12479
- }
12480
- const prevRev = loaded.state.rev ?? 0;
12481
- const parsed = extractNextStateFromText(result.stdout ?? "", fenceLabel, prevRev);
12482
- if (parsed.error) {
12483
- ctx.data.nextStateParseError = parsed.error;
12484
- ctx.output.exitCode = 1;
12485
- ctx.output.reason = `runTickScript: ${parsed.error}`;
12486
- return;
12487
- }
12488
- ctx.data.nextJobState = parsed.envelope;
12489
12810
  };
12490
12811
  }
12491
12812
  });
@@ -12582,7 +12903,7 @@ var init_skipAgent = __esm({
12582
12903
  });
12583
12904
 
12584
12905
  // src/scripts/stageMergeConflicts.ts
12585
- import { execFileSync as execFileSync22 } from "child_process";
12906
+ import { execFileSync as execFileSync23 } from "child_process";
12586
12907
  var stageMergeConflicts;
12587
12908
  var init_stageMergeConflicts = __esm({
12588
12909
  "src/scripts/stageMergeConflicts.ts"() {
@@ -12590,7 +12911,7 @@ var init_stageMergeConflicts = __esm({
12590
12911
  stageMergeConflicts = async (ctx) => {
12591
12912
  if (ctx.data.agentDone === false) return;
12592
12913
  try {
12593
- execFileSync22("git", ["add", "-A"], {
12914
+ execFileSync23("git", ["add", "-A"], {
12594
12915
  cwd: ctx.cwd,
12595
12916
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
12596
12917
  stdio: "pipe"
@@ -12644,7 +12965,7 @@ var init_startFlow = __esm({
12644
12965
  });
12645
12966
 
12646
12967
  // src/scripts/syncFlow.ts
12647
- import { execFileSync as execFileSync23 } from "child_process";
12968
+ import { execFileSync as execFileSync24 } from "child_process";
12648
12969
  function restoreDone(prNumber, cwd) {
12649
12970
  try {
12650
12971
  setKodyLabel(prNumber, DONE2, cwd);
@@ -12661,7 +12982,7 @@ function bail2(ctx, prNumber, reason) {
12661
12982
  }
12662
12983
  function revParseHead(cwd) {
12663
12984
  try {
12664
- return execFileSync23("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
12985
+ return execFileSync24("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
12665
12986
  } catch {
12666
12987
  return "";
12667
12988
  }
@@ -13002,10 +13323,10 @@ var init_verifyWithRetry = __esm({
13002
13323
  });
13003
13324
 
13004
13325
  // src/scripts/waitForCi.ts
13005
- import { execFileSync as execFileSync24 } from "child_process";
13326
+ import { execFileSync as execFileSync25 } from "child_process";
13006
13327
  function fetchChecks(prNumber, cwd) {
13007
13328
  try {
13008
- const raw = execFileSync24("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
13329
+ const raw = execFileSync25("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
13009
13330
  encoding: "utf-8",
13010
13331
  timeout: API_TIMEOUT_MS8,
13011
13332
  cwd,
@@ -13394,7 +13715,7 @@ var init_writeJobStateFile = __esm({
13394
13715
  });
13395
13716
 
13396
13717
  // src/scripts/writeRunSummary.ts
13397
- import * as fs37 from "fs";
13718
+ import * as fs40 from "fs";
13398
13719
  var writeRunSummary;
13399
13720
  var init_writeRunSummary = __esm({
13400
13721
  "src/scripts/writeRunSummary.ts"() {
@@ -13420,7 +13741,7 @@ var init_writeRunSummary = __esm({
13420
13741
  if (reason) lines.push(`- **Reason:** ${reason}`);
13421
13742
  lines.push("");
13422
13743
  try {
13423
- fs37.appendFileSync(summaryPath, `${lines.join("\n")}
13744
+ fs40.appendFileSync(summaryPath, `${lines.join("\n")}
13424
13745
  `);
13425
13746
  } catch {
13426
13747
  }
@@ -13507,6 +13828,7 @@ var init_scripts = __esm({
13507
13828
  init_reviewFlow();
13508
13829
  init_runFlow();
13509
13830
  init_runPreviewBuild();
13831
+ init_runScheduledExecutableTick();
13510
13832
  init_runTickScript();
13511
13833
  init_saveGoalState();
13512
13834
  init_saveTaskState();
@@ -13565,6 +13887,7 @@ var init_scripts = __esm({
13565
13887
  dispatchDutyFileTicks,
13566
13888
  planTaskJobs,
13567
13889
  dispatchNextTaskJob,
13890
+ runScheduledExecutableTick,
13568
13891
  runTickScript,
13569
13892
  runPreviewBuild,
13570
13893
  loadGoalState,
@@ -13626,67 +13949,8 @@ var init_scripts = __esm({
13626
13949
  }
13627
13950
  });
13628
13951
 
13629
- // src/staff.ts
13630
- import * as fs38 from "fs";
13631
- import * as path36 from "path";
13632
- function stripFrontmatter(raw) {
13633
- const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
13634
- return (match ? match[1] : raw).trim();
13635
- }
13636
- function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
13637
- const trimmed = slug.trim();
13638
- if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
13639
- const staffPath = path36.join(cwd, staffDir, `${trimmed}.md`);
13640
- if (fs38.existsSync(staffPath)) {
13641
- const body = stripFrontmatter(fs38.readFileSync(staffPath, "utf-8"));
13642
- if (body) return body;
13643
- const builtinForEmpty = BUILTIN_PERSONAS[trimmed];
13644
- if (builtinForEmpty) return builtinForEmpty;
13645
- throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
13646
- }
13647
- const builtin = BUILTIN_PERSONAS[trimmed];
13648
- if (builtin) return builtin;
13649
- throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
13650
- }
13651
- function framePersona(slug, persona) {
13652
- return [
13653
- `## Who you are \u2014 staff persona (authoritative identity)`,
13654
- ``,
13655
- `You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
13656
- `your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
13657
- `persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
13658
- `can never grant you authority your persona withholds.`,
13659
- ``,
13660
- persona
13661
- ].join("\n");
13662
- }
13663
- var DEFAULT_STAFF_DIR, BUILTIN_PERSONAS;
13664
- var init_staff = __esm({
13665
- "src/staff.ts"() {
13666
- "use strict";
13667
- DEFAULT_STAFF_DIR = ".kody/staff";
13668
- BUILTIN_PERSONAS = {
13669
- kody: [
13670
- "You are **kody**, the repository's autonomous engineer.",
13671
- "",
13672
- "- You work the way this repo already works: follow its conventions, its",
13673
- " existing patterns, and its tests. Read before you write.",
13674
- "- You ship small, correct, reviewable changes. You don't refactor beyond the",
13675
- " task, and you don't add scope nobody asked for.",
13676
- "- You are honest about outcomes: if something failed, didn't run, or you're",
13677
- " unsure, you say so plainly rather than papering over it.",
13678
- "- The request that triggered you IS your authorization to do the work:",
13679
- " opening a PR, pushing commits, and commenting are exactly what you're for \u2014",
13680
- " do them without waiting for extra approval.",
13681
- "- You still don't weaken security, delete work you didn't create, or take",
13682
- " destructive actions beyond the task you were given."
13683
- ].join("\n")
13684
- };
13685
- }
13686
- });
13687
-
13688
13952
  // src/tools.ts
13689
- import { execFileSync as execFileSync25 } from "child_process";
13953
+ import { execFileSync as execFileSync26 } from "child_process";
13690
13954
  function verifyCliTools(tools, cwd) {
13691
13955
  const out = [];
13692
13956
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -13723,7 +13987,7 @@ function verifyOne(tool5, cwd) {
13723
13987
  }
13724
13988
  function runShell(cmd, cwd, timeoutMs = 3e4) {
13725
13989
  try {
13726
- const stdout = execFileSync25("sh", ["-c", cmd], {
13990
+ const stdout = execFileSync26("sh", ["-c", cmd], {
13727
13991
  cwd,
13728
13992
  stdio: ["ignore", "pipe", "pipe"],
13729
13993
  timeout: timeoutMs,
@@ -13751,8 +14015,8 @@ var init_tools = __esm({
13751
14015
 
13752
14016
  // src/executor.ts
13753
14017
  import { spawn as spawn7 } from "child_process";
13754
- import * as fs39 from "fs";
13755
- import * as path37 from "path";
14018
+ import * as fs41 from "fs";
14019
+ import * as path38 from "path";
13756
14020
  function isMutatingPostflight(scriptName) {
13757
14021
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
13758
14022
  }
@@ -13865,17 +14129,7 @@ async function runExecutable(profileName, input) {
13865
14129
  reason: `agent.model invalid: ${err instanceof Error ? err.message : String(err)}`
13866
14130
  });
13867
14131
  }
13868
- let litellm = null;
13869
- if (profileName !== "preview-build") {
13870
- try {
13871
- litellm = await startLitellmIfNeeded(model, input.cwd);
13872
- } catch (err) {
13873
- return finishAndEnd({
13874
- exitCode: 99,
13875
- reason: `litellm startup failed: ${err instanceof Error ? err.message : String(err)}`
13876
- });
13877
- }
13878
- }
14132
+ let litellm;
13879
14133
  const ctx = {
13880
14134
  args,
13881
14135
  cwd: input.cwd,
@@ -13903,16 +14157,23 @@ async function runExecutable(profileName, input) {
13903
14157
  })
13904
14158
  };
13905
14159
  })() : null;
13906
- const ndjsonDir = path37.join(input.cwd, ".kody");
14160
+ const ndjsonDir = path38.join(input.cwd, ".kody");
13907
14161
  const personaSlug = typeof profile.staff === "string" && profile.staff.length > 0 ? profile.staff : typeof ctx.data.jobPersona === "string" && ctx.data.jobPersona.length > 0 ? ctx.data.jobPersona : null;
13908
14162
  const staffPersona = personaSlug ? framePersona(personaSlug, loadStaffPersona(input.cwd, personaSlug)) : null;
13909
14163
  const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
13910
14164
  const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
13911
14165
  const invokeAgent = async (prompt) => {
13912
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path37.isAbsolute(p) ? p : path37.resolve(profile.dir, p)).filter((p) => p.length > 0);
14166
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path38.isAbsolute(p) ? p : path38.resolve(profile.dir, p)).filter((p) => p.length > 0);
13913
14167
  const syntheticPath = ctx.data.syntheticPluginPath;
13914
14168
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
13915
14169
  const agents = loadSubagents(profile);
14170
+ if (litellm === void 0) {
14171
+ try {
14172
+ litellm = await startLitellmIfNeeded(model, input.cwd);
14173
+ } catch (err) {
14174
+ throw new Error(`litellm startup failed: ${err instanceof Error ? err.message : String(err)}`);
14175
+ }
14176
+ }
13916
14177
  const lm = litellm;
13917
14178
  return runAgent({
13918
14179
  prompt,
@@ -14020,7 +14281,14 @@ async function runExecutable(profileName, input) {
14020
14281
  });
14021
14282
  }
14022
14283
  emitEvent(input.cwd, { executable: profileName, kind: "agent_start" });
14023
- agentResult = await invokeAgent(prompt);
14284
+ try {
14285
+ agentResult = await invokeAgent(prompt);
14286
+ } catch (err) {
14287
+ return finishAndEnd({
14288
+ exitCode: 99,
14289
+ reason: err instanceof Error ? err.message : String(err)
14290
+ });
14291
+ }
14024
14292
  emitEvent(input.cwd, {
14025
14293
  executable: profileName,
14026
14294
  kind: "agent_end",
@@ -14274,17 +14542,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
14274
14542
  function resolveProfilePath(profileName) {
14275
14543
  const found = resolveExecutable(profileName);
14276
14544
  if (found) return found;
14277
- const here = path37.dirname(new URL(import.meta.url).pathname);
14545
+ const here = path38.dirname(new URL(import.meta.url).pathname);
14278
14546
  const candidates = [
14279
- path37.join(here, "executables", profileName, "profile.json"),
14547
+ path38.join(here, "executables", profileName, "profile.json"),
14280
14548
  // same-dir sibling (dev)
14281
- path37.join(here, "..", "executables", profileName, "profile.json"),
14549
+ path38.join(here, "..", "executables", profileName, "profile.json"),
14282
14550
  // up one (prod: dist/bin → dist/executables)
14283
- path37.join(here, "..", "src", "executables", profileName, "profile.json")
14551
+ path38.join(here, "..", "src", "executables", profileName, "profile.json")
14284
14552
  // fallback
14285
14553
  ];
14286
14554
  for (const c of candidates) {
14287
- if (fs39.existsSync(c)) return c;
14555
+ if (fs41.existsSync(c)) return c;
14288
14556
  }
14289
14557
  return candidates[0];
14290
14558
  }
@@ -14382,8 +14650,8 @@ function resolveShellTimeoutMs(entry) {
14382
14650
  }
14383
14651
  async function runShellEntry(entry, ctx, profile) {
14384
14652
  const shellName = entry.shell;
14385
- const shellPath = path37.join(profile.dir, shellName);
14386
- if (!fs39.existsSync(shellPath)) {
14653
+ const shellPath = path38.join(profile.dir, shellName);
14654
+ if (!fs41.existsSync(shellPath)) {
14387
14655
  ctx.skipAgent = true;
14388
14656
  ctx.output.exitCode = 99;
14389
14657
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -14542,7 +14810,7 @@ __export(job_exports, {
14542
14810
  stableJobKey: () => stableJobKey,
14543
14811
  validateJob: () => validateJob
14544
14812
  });
14545
- import * as path38 from "path";
14813
+ import * as path39 from "path";
14546
14814
  function newJobId(flavor) {
14547
14815
  localJobSeq += 1;
14548
14816
  const runId = process.env.GITHUB_RUN_ID;
@@ -14579,7 +14847,7 @@ function validateJob(input) {
14579
14847
  async function runJob(job, base) {
14580
14848
  const valid = validateJob(job);
14581
14849
  const action = valid.action ?? valid.duty;
14582
- const projectDutiesRoot = path38.join(base.cwd, ".kody", "duties");
14850
+ const projectDutiesRoot = path39.join(base.cwd, ".kody", "duties");
14583
14851
  const resolvedDuty = action ? resolveDutyAction(action, projectDutiesRoot) : null;
14584
14852
  const dutyIdentity = valid.duty ?? resolvedDuty?.duty;
14585
14853
  const dutyContext = loadDutyContext(dutyIdentity, base.cwd);
@@ -14635,7 +14903,7 @@ async function runJob(job, base) {
14635
14903
  }
14636
14904
  function loadDutyContext(slug, cwd) {
14637
14905
  if (!slug) return null;
14638
- return readDutyFolder(path38.join(cwd, ".kody", "duties"), slug) ?? readDutyFolder(getProjectDutiesRoot(), slug) ?? readDutyFolder(getBuiltinDutiesRoot(), slug);
14906
+ return resolveDutyFolder(slug, path39.join(cwd, ".kody", "duties"));
14639
14907
  }
14640
14908
  function mintInstantJob(dispatch2, opts) {
14641
14909
  return {
@@ -14664,7 +14932,6 @@ var DEFAULT_INSTANT_PERSONA, localJobSeq, InvalidJobError;
14664
14932
  var init_job = __esm({
14665
14933
  "src/job.ts"() {
14666
14934
  "use strict";
14667
- init_dutyFolders();
14668
14935
  init_executor();
14669
14936
  init_registry();
14670
14937
  init_jobIdentity();
@@ -14787,20 +15054,20 @@ function translateOpenAISseToBrain(opts) {
14787
15054
  }
14788
15055
 
14789
15056
  // src/servers/brain-serve.ts
14790
- import * as fs42 from "fs";
15057
+ import * as fs44 from "fs";
14791
15058
  import { createServer } from "http";
14792
- import * as path41 from "path";
15059
+ import * as path42 from "path";
14793
15060
 
14794
15061
  // src/chat/loop.ts
14795
15062
  init_agent();
14796
15063
  init_registry();
14797
15064
  init_task_artifacts();
14798
- import * as fs12 from "fs";
14799
- import * as path12 from "path";
15065
+ import * as fs13 from "fs";
15066
+ import * as path13 from "path";
14800
15067
 
14801
15068
  // src/chat/attachments.ts
14802
- import * as fs9 from "fs";
14803
- import * as path9 from "path";
15069
+ import * as fs10 from "fs";
15070
+ import * as path10 from "path";
14804
15071
  var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
14805
15072
  var EXT_BY_MIME = {
14806
15073
  "image/png": "png",
@@ -14816,7 +15083,7 @@ function extFor(mime) {
14816
15083
  return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
14817
15084
  }
14818
15085
  function attachmentsDir(cwd, sessionId) {
14819
- return path9.join(cwd, ".kody", "tmp", "attachments", sessionId);
15086
+ return path10.join(cwd, ".kody", "tmp", "attachments", sessionId);
14820
15087
  }
14821
15088
  function prepareAttachments(turns, cwd, sessionId) {
14822
15089
  const imagePaths = [];
@@ -14833,11 +15100,11 @@ function prepareAttachments(turns, cwd, sessionId) {
14833
15100
  if (!isImage) return `[File: ${name}]`;
14834
15101
  try {
14835
15102
  if (!dirEnsured) {
14836
- fs9.mkdirSync(dir, { recursive: true });
15103
+ fs10.mkdirSync(dir, { recursive: true });
14837
15104
  dirEnsured = true;
14838
15105
  }
14839
- const filePath = path9.join(dir, `${imageCounter}.${extFor(mime)}`);
14840
- fs9.writeFileSync(filePath, Buffer.from(data, "base64"));
15106
+ const filePath = path10.join(dir, `${imageCounter}.${extFor(mime)}`);
15107
+ fs10.writeFileSync(filePath, Buffer.from(data, "base64"));
14841
15108
  imageCounter += 1;
14842
15109
  imagePaths.push(filePath);
14843
15110
  return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it.]`;
@@ -14853,10 +15120,10 @@ function prepareAttachments(turns, cwd, sessionId) {
14853
15120
  }
14854
15121
 
14855
15122
  // src/chat/events.ts
14856
- import * as fs10 from "fs";
14857
- import * as path10 from "path";
15123
+ import * as fs11 from "fs";
15124
+ import * as path11 from "path";
14858
15125
  function eventsFilePath(cwd, sessionId) {
14859
- return path10.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
15126
+ return path11.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
14860
15127
  }
14861
15128
  var FileSink = class {
14862
15129
  constructor(file) {
@@ -14864,8 +15131,8 @@ var FileSink = class {
14864
15131
  }
14865
15132
  file;
14866
15133
  async emit(event) {
14867
- fs10.mkdirSync(path10.dirname(this.file), { recursive: true });
14868
- fs10.appendFileSync(this.file, `${JSON.stringify(event)}
15134
+ fs11.mkdirSync(path11.dirname(this.file), { recursive: true });
15135
+ fs11.appendFileSync(this.file, `${JSON.stringify(event)}
14869
15136
  `);
14870
15137
  }
14871
15138
  };
@@ -14919,14 +15186,14 @@ function makeRunId(sessionId, suffix) {
14919
15186
  }
14920
15187
 
14921
15188
  // src/chat/session.ts
14922
- import * as fs11 from "fs";
14923
- import * as path11 from "path";
15189
+ import * as fs12 from "fs";
15190
+ import * as path12 from "path";
14924
15191
  function sessionFilePath(cwd, sessionId) {
14925
- return path11.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
15192
+ return path12.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
14926
15193
  }
14927
15194
  function readMeta(file) {
14928
- if (!fs11.existsSync(file)) return null;
14929
- const raw = fs11.readFileSync(file, "utf-8");
15195
+ if (!fs12.existsSync(file)) return null;
15196
+ const raw = fs12.readFileSync(file, "utf-8");
14930
15197
  const firstLine2 = raw.split("\n", 1)[0]?.trim();
14931
15198
  if (!firstLine2) return null;
14932
15199
  try {
@@ -14939,8 +15206,8 @@ function readMeta(file) {
14939
15206
  }
14940
15207
  }
14941
15208
  function readSession(file) {
14942
- if (!fs11.existsSync(file)) return [];
14943
- const raw = fs11.readFileSync(file, "utf-8").trim();
15209
+ if (!fs12.existsSync(file)) return [];
15210
+ const raw = fs12.readFileSync(file, "utf-8").trim();
14944
15211
  if (!raw) return [];
14945
15212
  const turns = [];
14946
15213
  for (const line of raw.split("\n")) {
@@ -14956,14 +15223,14 @@ function readSession(file) {
14956
15223
  return turns;
14957
15224
  }
14958
15225
  function appendTurn(file, turn) {
14959
- fs11.mkdirSync(path11.dirname(file), { recursive: true });
15226
+ fs12.mkdirSync(path12.dirname(file), { recursive: true });
14960
15227
  const line = JSON.stringify({
14961
15228
  role: turn.role,
14962
15229
  content: turn.content,
14963
15230
  timestamp: turn.timestamp,
14964
15231
  toolCalls: turn.toolCalls ?? []
14965
15232
  });
14966
- fs11.appendFileSync(file, `${line}
15233
+ fs12.appendFileSync(file, `${line}
14967
15234
  `);
14968
15235
  }
14969
15236
  function seedInitialMessage(file, message) {
@@ -15082,7 +15349,7 @@ function buildExecutableCatalog() {
15082
15349
  const entries = [];
15083
15350
  for (const { name, profilePath } of discovered) {
15084
15351
  try {
15085
- const raw = JSON.parse(fs12.readFileSync(profilePath, "utf-8"));
15352
+ const raw = JSON.parse(fs13.readFileSync(profilePath, "utf-8"));
15086
15353
  const describe = typeof raw.describe === "string" ? raw.describe : "";
15087
15354
  const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
15088
15355
  entries.push({ name, describe: firstSentence.trim() });
@@ -15158,6 +15425,7 @@ async function runChatTurn(opts) {
15158
15425
  verbose: opts.verbose,
15159
15426
  quiet: opts.quiet,
15160
15427
  systemPromptAppend: systemPrompt,
15428
+ ...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {},
15161
15429
  // Let the agent clone + work on OTHER repos mid-conversation (a
15162
15430
  // repo-less Brain serves many). Enabled whenever we know where repos
15163
15431
  // live; grants read access to that root via additionalDirectories.
@@ -15250,10 +15518,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
15250
15518
  var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
15251
15519
  var MAX_INDEX_BYTES = 8e3;
15252
15520
  function readMemoryIndexBlock(cwd) {
15253
- const indexPath = path12.join(cwd, MEMORY_INDEX_REL);
15521
+ const indexPath = path13.join(cwd, MEMORY_INDEX_REL);
15254
15522
  let raw;
15255
15523
  try {
15256
- raw = fs12.readFileSync(indexPath, "utf-8");
15524
+ raw = fs13.readFileSync(indexPath, "utf-8");
15257
15525
  } catch {
15258
15526
  return "";
15259
15527
  }
@@ -15271,17 +15539,17 @@ function readMemoryIndexBlock(cwd) {
15271
15539
  var CONTEXT_DIR_REL = ".kody/context";
15272
15540
  var MAX_CONTEXT_BYTES = 12e3;
15273
15541
  function readContextBlock(cwd) {
15274
- const dir = path12.join(cwd, CONTEXT_DIR_REL);
15542
+ const dir = path13.join(cwd, CONTEXT_DIR_REL);
15275
15543
  let files;
15276
15544
  try {
15277
- files = fs12.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
15545
+ files = fs13.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
15278
15546
  } catch {
15279
15547
  return "";
15280
15548
  }
15281
15549
  const sections = [];
15282
15550
  for (const file of files) {
15283
15551
  try {
15284
- const content = fs12.readFileSync(path12.join(dir, file), "utf-8").trim();
15552
+ const content = fs13.readFileSync(path13.join(dir, file), "utf-8").trim();
15285
15553
  if (content) sections.push(`### ${file.replace(/\.md$/, "")}
15286
15554
 
15287
15555
  ${content}`);
@@ -15304,10 +15572,10 @@ _\u2026 (context truncated; see \`.kody/context/\` for the full text)_` : joined
15304
15572
  var INSTRUCTIONS_REL = ".kody/instructions.md";
15305
15573
  var MAX_INSTRUCTIONS_BYTES = 8e3;
15306
15574
  function readInstructionsBlock(cwd) {
15307
- const instructionsPath = path12.join(cwd, INSTRUCTIONS_REL);
15575
+ const instructionsPath = path13.join(cwd, INSTRUCTIONS_REL);
15308
15576
  let raw;
15309
15577
  try {
15310
- raw = fs12.readFileSync(instructionsPath, "utf-8");
15578
+ raw = fs13.readFileSync(instructionsPath, "utf-8");
15311
15579
  } catch {
15312
15580
  return "";
15313
15581
  }
@@ -15329,9 +15597,9 @@ _\u2026 (instructions truncated)_` : trimmed;
15329
15597
  init_config();
15330
15598
 
15331
15599
  // src/kody-cli.ts
15332
- import { execFileSync as execFileSync26 } from "child_process";
15333
- import * as fs40 from "fs";
15334
- import * as path39 from "path";
15600
+ import { execFileSync as execFileSync27 } from "child_process";
15601
+ import * as fs42 from "fs";
15602
+ import * as path40 from "path";
15335
15603
 
15336
15604
  // src/app-auth.ts
15337
15605
  import { createSign } from "crypto";
@@ -15408,7 +15676,7 @@ init_config();
15408
15676
 
15409
15677
  // src/dispatch.ts
15410
15678
  init_config();
15411
- import * as fs13 from "fs";
15679
+ import * as fs14 from "fs";
15412
15680
 
15413
15681
  // src/cron-match.ts
15414
15682
  var FIELD_BOUNDS = [
@@ -15510,10 +15778,10 @@ function autoDispatch(opts) {
15510
15778
  }
15511
15779
  const eventName = process.env.GITHUB_EVENT_NAME;
15512
15780
  const eventPath = process.env.GITHUB_EVENT_PATH;
15513
- if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
15781
+ if (!eventName || !eventPath || !fs14.existsSync(eventPath)) return null;
15514
15782
  let event = {};
15515
15783
  try {
15516
- event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
15784
+ event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
15517
15785
  } catch {
15518
15786
  return null;
15519
15787
  }
@@ -15624,7 +15892,7 @@ function autoDispatchTyped(opts) {
15624
15892
  if (legacy) return { kind: "route", ...legacy };
15625
15893
  const eventName = process.env.GITHUB_EVENT_NAME;
15626
15894
  const eventPath = process.env.GITHUB_EVENT_PATH;
15627
- if (!eventName || !eventPath || !fs13.existsSync(eventPath)) {
15895
+ if (!eventName || !eventPath || !fs14.existsSync(eventPath)) {
15628
15896
  return { kind: "silent", reason: "no GHA event context" };
15629
15897
  }
15630
15898
  if (eventName !== "issue_comment") {
@@ -15632,7 +15900,7 @@ function autoDispatchTyped(opts) {
15632
15900
  }
15633
15901
  let event = {};
15634
15902
  try {
15635
- event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
15903
+ event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
15636
15904
  } catch {
15637
15905
  return { kind: "silent", reason: "GHA event payload unreadable" };
15638
15906
  }
@@ -15676,7 +15944,7 @@ function dispatchScheduledWatches(opts) {
15676
15944
  for (const exe of listExecutables()) {
15677
15945
  let raw;
15678
15946
  try {
15679
- raw = fs13.readFileSync(exe.profilePath, "utf-8");
15947
+ raw = fs14.readFileSync(exe.profilePath, "utf-8");
15680
15948
  } catch {
15681
15949
  continue;
15682
15950
  }
@@ -15926,14 +16194,14 @@ async function resolveAuthToken(env = process.env) {
15926
16194
  return void 0;
15927
16195
  }
15928
16196
  function detectPackageManager2(cwd) {
15929
- if (fs40.existsSync(path39.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
15930
- if (fs40.existsSync(path39.join(cwd, "yarn.lock"))) return "yarn";
15931
- if (fs40.existsSync(path39.join(cwd, "bun.lockb"))) return "bun";
16197
+ if (fs42.existsSync(path40.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
16198
+ if (fs42.existsSync(path40.join(cwd, "yarn.lock"))) return "yarn";
16199
+ if (fs42.existsSync(path40.join(cwd, "bun.lockb"))) return "bun";
15932
16200
  return "npm";
15933
16201
  }
15934
16202
  function shellOut(cmd, args, cwd, stream = true) {
15935
16203
  try {
15936
- execFileSync26(cmd, args, {
16204
+ execFileSync27(cmd, args, {
15937
16205
  cwd,
15938
16206
  stdio: stream ? "inherit" : "pipe",
15939
16207
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -15946,7 +16214,7 @@ function shellOut(cmd, args, cwd, stream = true) {
15946
16214
  }
15947
16215
  function isOnPath(bin) {
15948
16216
  try {
15949
- execFileSync26("which", [bin], { stdio: "pipe" });
16217
+ execFileSync27("which", [bin], { stdio: "pipe" });
15950
16218
  return true;
15951
16219
  } catch {
15952
16220
  return false;
@@ -15987,7 +16255,7 @@ function installLitellmIfNeeded(cwd) {
15987
16255
  } catch {
15988
16256
  }
15989
16257
  try {
15990
- execFileSync26("python3", ["-c", "import litellm"], { stdio: "pipe" });
16258
+ execFileSync27("python3", ["-c", "import litellm"], { stdio: "pipe" });
15991
16259
  process.stdout.write("\u2192 kody: litellm already installed\n");
15992
16260
  return 0;
15993
16261
  } catch {
@@ -15997,16 +16265,16 @@ function installLitellmIfNeeded(cwd) {
15997
16265
  }
15998
16266
  function configureGitIdentity(cwd) {
15999
16267
  try {
16000
- const name = execFileSync26("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
16268
+ const name = execFileSync27("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
16001
16269
  if (name) return;
16002
16270
  } catch {
16003
16271
  }
16004
16272
  try {
16005
- execFileSync26("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
16273
+ execFileSync27("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
16006
16274
  } catch {
16007
16275
  }
16008
16276
  try {
16009
- execFileSync26("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
16277
+ execFileSync27("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
16010
16278
  cwd,
16011
16279
  stdio: "pipe"
16012
16280
  });
@@ -16015,11 +16283,11 @@ function configureGitIdentity(cwd) {
16015
16283
  }
16016
16284
  function postFailureTail(issueNumber, cwd, reason) {
16017
16285
  if (!issueNumber) return;
16018
- const logPath = path39.join(cwd, ".kody", "last-run.jsonl");
16286
+ const logPath = path40.join(cwd, ".kody", "last-run.jsonl");
16019
16287
  let tail = "";
16020
16288
  try {
16021
- if (fs40.existsSync(logPath)) {
16022
- const content = fs40.readFileSync(logPath, "utf-8");
16289
+ if (fs42.existsSync(logPath)) {
16290
+ const content = fs42.readFileSync(logPath, "utf-8");
16023
16291
  tail = content.slice(-3e3);
16024
16292
  }
16025
16293
  } catch {
@@ -16044,7 +16312,7 @@ async function runCi(argv) {
16044
16312
  return 0;
16045
16313
  }
16046
16314
  const args = parseCiArgs(argv);
16047
- const cwd = args.cwd ? path39.resolve(args.cwd) : process.cwd();
16315
+ const cwd = args.cwd ? path40.resolve(args.cwd) : process.cwd();
16048
16316
  let earlyConfig;
16049
16317
  let earlyConfigError;
16050
16318
  try {
@@ -16057,9 +16325,9 @@ async function runCi(argv) {
16057
16325
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
16058
16326
  let manualWorkflowDispatch = false;
16059
16327
  let forceRunAction = null;
16060
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs40.existsSync(dispatchEventPath)) {
16328
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs42.existsSync(dispatchEventPath)) {
16061
16329
  try {
16062
- const evt = JSON.parse(fs40.readFileSync(dispatchEventPath, "utf-8"));
16330
+ const evt = JSON.parse(fs42.readFileSync(dispatchEventPath, "utf-8"));
16063
16331
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
16064
16332
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
16065
16333
  const dutyInput = String(evt?.inputs?.duty ?? evt?.inputs?.executable ?? "").trim();
@@ -16359,16 +16627,16 @@ init_litellm();
16359
16627
  init_repoWorkspace();
16360
16628
 
16361
16629
  // src/scripts/brainTurnLog.ts
16362
- import * as fs41 from "fs";
16363
- import * as path40 from "path";
16630
+ import * as fs43 from "fs";
16631
+ import * as path41 from "path";
16364
16632
  var live = /* @__PURE__ */ new Map();
16365
16633
  function eventsPath(dir, chatId) {
16366
- return path40.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
16634
+ return path41.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
16367
16635
  }
16368
16636
  function lastPersistedSeq(dir, chatId) {
16369
16637
  const p = eventsPath(dir, chatId);
16370
- if (!fs41.existsSync(p)) return 0;
16371
- const lines = fs41.readFileSync(p, "utf-8").split("\n").filter(Boolean);
16638
+ if (!fs43.existsSync(p)) return 0;
16639
+ const lines = fs43.readFileSync(p, "utf-8").split("\n").filter(Boolean);
16372
16640
  if (lines.length === 0) return 0;
16373
16641
  try {
16374
16642
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -16378,9 +16646,9 @@ function lastPersistedSeq(dir, chatId) {
16378
16646
  }
16379
16647
  function readSince(dir, chatId, since) {
16380
16648
  const p = eventsPath(dir, chatId);
16381
- if (!fs41.existsSync(p)) return [];
16649
+ if (!fs43.existsSync(p)) return [];
16382
16650
  const out = [];
16383
- for (const line of fs41.readFileSync(p, "utf-8").split("\n")) {
16651
+ for (const line of fs43.readFileSync(p, "utf-8").split("\n")) {
16384
16652
  if (!line) continue;
16385
16653
  try {
16386
16654
  const rec = JSON.parse(line);
@@ -16406,12 +16674,12 @@ function beginTurn(dir, chatId) {
16406
16674
  };
16407
16675
  live.set(chatId, state);
16408
16676
  const p = eventsPath(dir, chatId);
16409
- fs41.mkdirSync(path40.dirname(p), { recursive: true });
16677
+ fs43.mkdirSync(path41.dirname(p), { recursive: true });
16410
16678
  return (event) => {
16411
16679
  state.seq += 1;
16412
16680
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
16413
16681
  try {
16414
- fs41.appendFileSync(p, `${JSON.stringify(rec)}
16682
+ fs43.appendFileSync(p, `${JSON.stringify(rec)}
16415
16683
  `);
16416
16684
  } catch (err) {
16417
16685
  process.stderr.write(
@@ -16450,7 +16718,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
16450
16718
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
16451
16719
  };
16452
16720
  try {
16453
- fs41.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
16721
+ fs43.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
16454
16722
  `);
16455
16723
  } catch {
16456
16724
  }
@@ -16681,7 +16949,7 @@ async function handleChatTurn(req, res, chatId, opts) {
16681
16949
  const repo = strField(body, "repo");
16682
16950
  const repoToken = strField(body, "repoToken");
16683
16951
  const sessionFile = sessionFilePath(opts.cwd, chatId);
16684
- fs42.mkdirSync(path41.dirname(sessionFile), { recursive: true });
16952
+ fs44.mkdirSync(path42.dirname(sessionFile), { recursive: true });
16685
16953
  appendTurn(sessionFile, {
16686
16954
  role: "user",
16687
16955
  content: message,
@@ -16728,7 +16996,7 @@ async function handleChatTurn(req, res, chatId, opts) {
16728
16996
  function buildServer(opts) {
16729
16997
  const runTurn = opts.runTurn ?? runChatTurn;
16730
16998
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
16731
- const reposRoot = opts.reposRoot ?? path41.join(path41.dirname(path41.resolve(opts.cwd)), "repos");
16999
+ const reposRoot = opts.reposRoot ?? path42.join(path42.dirname(path42.resolve(opts.cwd)), "repos");
16732
17000
  return createServer(async (req, res) => {
16733
17001
  if (!req.method || !req.url) {
16734
17002
  sendJson(res, 400, { error: "bad request" });
@@ -17320,18 +17588,18 @@ async function loadConfigSafe() {
17320
17588
  }
17321
17589
 
17322
17590
  // src/chat-cli.ts
17323
- import { execFileSync as execFileSync29 } from "child_process";
17324
- import * as fs44 from "fs";
17325
- import * as path43 from "path";
17591
+ import { execFileSync as execFileSync30 } from "child_process";
17592
+ import * as fs46 from "fs";
17593
+ import * as path44 from "path";
17326
17594
 
17327
17595
  // src/chat/modes/interactive.ts
17328
17596
  init_issue();
17329
- import { execFileSync as execFileSync28 } from "child_process";
17330
- import * as fs43 from "fs";
17331
- import * as path42 from "path";
17597
+ import { execFileSync as execFileSync29 } from "child_process";
17598
+ import * as fs45 from "fs";
17599
+ import * as path43 from "path";
17332
17600
 
17333
17601
  // src/chat/inbox.ts
17334
- import { execFileSync as execFileSync27 } from "child_process";
17602
+ import { execFileSync as execFileSync28 } from "child_process";
17335
17603
  var DEFAULT_POLL_MS = 3e3;
17336
17604
  async function waitForNextUserMessage(opts) {
17337
17605
  const pollMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
@@ -17348,13 +17616,13 @@ async function waitForNextUserMessage(opts) {
17348
17616
  try {
17349
17617
  const branch = currentBranch(opts.cwd);
17350
17618
  if (branch) {
17351
- execFileSync27("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
17352
- execFileSync27("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
17619
+ execFileSync28("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
17620
+ execFileSync28("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
17353
17621
  cwd: opts.cwd,
17354
17622
  stdio: "pipe"
17355
17623
  });
17356
17624
  } else {
17357
- execFileSync27("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
17625
+ execFileSync28("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
17358
17626
  }
17359
17627
  } catch (err) {
17360
17628
  const msg = err instanceof Error ? err.message : String(err);
@@ -17380,7 +17648,7 @@ function sleep2(ms) {
17380
17648
  }
17381
17649
  function currentBranch(cwd) {
17382
17650
  try {
17383
- const out = execFileSync27("git", ["symbolic-ref", "--short", "HEAD"], {
17651
+ const out = execFileSync28("git", ["symbolic-ref", "--short", "HEAD"], {
17384
17652
  cwd,
17385
17653
  stdio: ["ignore", "pipe", "ignore"]
17386
17654
  });
@@ -17461,7 +17729,8 @@ async function runInteractiveMode(opts) {
17461
17729
  sink: opts.sink,
17462
17730
  verbose: opts.verbose,
17463
17731
  quiet: opts.quiet,
17464
- invokeAgent: opts.invokeAgent
17732
+ invokeAgent: opts.invokeAgent,
17733
+ ...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {}
17465
17734
  });
17466
17735
  } catch (err) {
17467
17736
  const msg = err instanceof Error ? err.message : String(err);
@@ -17487,9 +17756,9 @@ function findNextUserTurn(turns, fromIdx) {
17487
17756
  return -1;
17488
17757
  }
17489
17758
  function commitTurn(cwd, sessionId, _verbose) {
17490
- const sessionRel = path42.relative(cwd, sessionFilePath(cwd, sessionId));
17491
- const eventsRel = path42.relative(cwd, eventsFilePath(cwd, sessionId));
17492
- const rels = [sessionRel, eventsRel].filter((p) => fs43.existsSync(path42.join(cwd, p)));
17759
+ const sessionRel = path43.relative(cwd, sessionFilePath(cwd, sessionId));
17760
+ const eventsRel = path43.relative(cwd, eventsFilePath(cwd, sessionId));
17761
+ const rels = [sessionRel, eventsRel].filter((p) => fs45.existsSync(path43.join(cwd, p)));
17493
17762
  if (rels.length === 0) return;
17494
17763
  const repository = process.env.GITHUB_REPOSITORY;
17495
17764
  if (!repository) {
@@ -17501,8 +17770,8 @@ function commitTurn(cwd, sessionId, _verbose) {
17501
17770
  }
17502
17771
  const branch = defaultBranch(cwd) ?? "main";
17503
17772
  for (const rel of rels) {
17504
- const repoPath = rel.split(path42.sep).join("/");
17505
- const localText = fs43.readFileSync(path42.join(cwd, rel), "utf-8");
17773
+ const repoPath = rel.split(path43.sep).join("/");
17774
+ const localText = fs45.readFileSync(path43.join(cwd, rel), "utf-8");
17506
17775
  putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
17507
17776
  }
17508
17777
  }
@@ -17563,7 +17832,7 @@ function putJsonlViaContents(repository, branch, repoPath, localText, sessionId,
17563
17832
  }
17564
17833
  function defaultBranch(cwd) {
17565
17834
  try {
17566
- const out = execFileSync28("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], {
17835
+ const out = execFileSync29("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], {
17567
17836
  cwd,
17568
17837
  stdio: ["ignore", "pipe", "ignore"]
17569
17838
  });
@@ -17600,11 +17869,17 @@ var CHAT_HELP = `kody chat \u2014 dashboard-driven chat session
17600
17869
 
17601
17870
  Usage:
17602
17871
  kody chat [--session <id>] [--message <text>] [--model <provider/model>]
17872
+ [--reasoning-effort <off|low|medium|high>]
17603
17873
  [--dashboard-url <url>] [--cwd <path>] [--verbose|--quiet]
17604
17874
 
17605
- All inputs may also come from env: SESSION_ID, INIT_MESSAGE, MODEL, DASHBOARD_URL.
17875
+ All inputs may also come from env: SESSION_ID, INIT_MESSAGE, MODEL, REASONING_EFFORT, DASHBOARD_URL.
17606
17876
  CLI flags take precedence over env. SESSION_ID is required.
17607
17877
 
17878
+ Thinking level maps to the Claude Agent SDK's maxThinkingTokens (Anthropic
17879
+ extended thinking). Default is unset (no thinking \u2014 cheapest). Set via the
17880
+ dashboard's chat-level thinking dropdown, the REASONING_EFFORT env var,
17881
+ or this flag.
17882
+
17608
17883
  Exit codes:
17609
17884
  0 reply emitted successfully
17610
17885
  64 bad inputs (missing session, empty history)
@@ -17617,6 +17892,7 @@ function parseChatArgs(argv, env = process.env) {
17617
17892
  if (arg === "--session") result.sessionId = argv[++i];
17618
17893
  else if (arg === "--message") result.initMessage = argv[++i];
17619
17894
  else if (arg === "--model") result.model = argv[++i];
17895
+ else if (arg === "--reasoning-effort") result.reasoningEffort = parseReasoningEffort(argv[++i]) ?? void 0;
17620
17896
  else if (arg === "--dashboard-url") result.dashboardUrl = argv[++i];
17621
17897
  else if (arg === "--cwd") result.cwd = argv[++i];
17622
17898
  else if (arg === "--verbose") result.verbose = true;
@@ -17629,27 +17905,28 @@ function parseChatArgs(argv, env = process.env) {
17629
17905
  result.initMessage = result.initMessage ?? env.INIT_MESSAGE ?? void 0;
17630
17906
  result.model = result.model ?? env.MODEL ?? void 0;
17631
17907
  result.dashboardUrl = result.dashboardUrl ?? env.DASHBOARD_URL ?? void 0;
17908
+ result.reasoningEffort = result.reasoningEffort ?? parseReasoningEffort(env.REASONING_EFFORT) ?? void 0;
17632
17909
  for (const key of ["sessionId", "initMessage", "model", "dashboardUrl"]) {
17633
17910
  const v = result[key];
17634
17911
  if (typeof v === "string" && v.trim() === "") result[key] = void 0;
17635
17912
  }
17636
17913
  if (!result.sessionId && !result.errors.includes("__HELP__")) {
17637
- result.errors.push("--session <id> (or SESSION_ID env) is required");
17914
+ result.errors.push("--session <id> (or SESSION_ID env) is required)");
17638
17915
  }
17639
17916
  return result;
17640
17917
  }
17641
17918
  function commitChatFiles(cwd, sessionId, verbose) {
17642
- const sessionFile = path43.relative(cwd, sessionFilePath(cwd, sessionId));
17643
- const eventsFile = path43.relative(cwd, eventsFilePath(cwd, sessionId));
17919
+ const sessionFile = path44.relative(cwd, sessionFilePath(cwd, sessionId));
17920
+ const eventsFile = path44.relative(cwd, eventsFilePath(cwd, sessionId));
17644
17921
  const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
17645
- const tasksDir = path43.join(".kody", "tasks", safeSession);
17922
+ const tasksDir = path44.join(".kody", "tasks", safeSession);
17646
17923
  const candidatePaths = [sessionFile, eventsFile, tasksDir];
17647
- const paths = candidatePaths.filter((p) => fs44.existsSync(path43.join(cwd, p)));
17924
+ const paths = candidatePaths.filter((p) => fs46.existsSync(path44.join(cwd, p)));
17648
17925
  if (paths.length === 0) return;
17649
17926
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
17650
17927
  try {
17651
- execFileSync29("git", ["add", "-f", ...paths], opts);
17652
- execFileSync29("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
17928
+ execFileSync30("git", ["add", "-f", ...paths], opts);
17929
+ execFileSync30("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
17653
17930
  } catch (err) {
17654
17931
  const msg = err instanceof Error ? err.message : String(err);
17655
17932
  process.stderr.write(`[kody:chat] commit skipped: ${msg}
@@ -17688,7 +17965,7 @@ async function runChat(argv) {
17688
17965
  ${CHAT_HELP}`);
17689
17966
  return 64;
17690
17967
  }
17691
- const cwd = args.cwd ? path43.resolve(args.cwd) : process.cwd();
17968
+ const cwd = args.cwd ? path44.resolve(args.cwd) : process.cwd();
17692
17969
  const sessionId = args.sessionId;
17693
17970
  const unpackedSecrets = unpackAllSecrets();
17694
17971
  if (unpackedSecrets > 0) {
@@ -17699,6 +17976,7 @@ ${CHAT_HELP}`);
17699
17976
  configureGitIdentity(cwd);
17700
17977
  const config = tryLoadConfig(cwd);
17701
17978
  const modelSpec = args.model ?? config?.agent.model ?? DEFAULT_MODEL2;
17979
+ const reasoningEffort = args.reasoningEffort ?? config?.agent.reasoningEffort ?? void 0;
17702
17980
  let model;
17703
17981
  try {
17704
17982
  model = parseProviderModel(modelSpec);
@@ -17740,7 +18018,7 @@ ${CHAT_HELP}`);
17740
18018
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
17741
18019
  const meta = readMeta(sessionFile);
17742
18020
  process.stdout.write(
17743
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs44.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
18021
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs46.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
17744
18022
  `
17745
18023
  );
17746
18024
  try {
@@ -17753,7 +18031,8 @@ ${CHAT_HELP}`);
17753
18031
  sink,
17754
18032
  meta,
17755
18033
  verbose: args.verbose,
17756
- quiet: args.quiet
18034
+ quiet: args.quiet,
18035
+ ...reasoningEffort ? { reasoningEffort } : {}
17757
18036
  });
17758
18037
  return result2.exitCode;
17759
18038
  }
@@ -17765,7 +18044,8 @@ ${CHAT_HELP}`);
17765
18044
  litellmUrl: litellm?.url ?? null,
17766
18045
  sink,
17767
18046
  verbose: args.verbose,
17768
- quiet: args.quiet
18047
+ quiet: args.quiet,
18048
+ ...reasoningEffort ? { reasoningEffort } : {}
17769
18049
  });
17770
18050
  commitChatFiles(cwd, sessionId, args.verbose ?? false);
17771
18051
  return result.exitCode;
@@ -17896,8 +18176,8 @@ var FlyClient = class {
17896
18176
  get fetch() {
17897
18177
  return this.opts.fetchImpl ?? fetch;
17898
18178
  }
17899
- async call(path44, init = {}) {
17900
- const res = await this.fetch(`${FLY_API_BASE}${path44}`, {
18179
+ async call(path45, init = {}) {
18180
+ const res = await this.fetch(`${FLY_API_BASE}${path45}`, {
17901
18181
  method: init.method ?? "GET",
17902
18182
  headers: {
17903
18183
  Authorization: `Bearer ${this.opts.token}`,
@@ -17908,7 +18188,7 @@ var FlyClient = class {
17908
18188
  if (res.status === 404 && init.allow404) return null;
17909
18189
  if (!res.ok) {
17910
18190
  const text = await res.text().catch(() => "");
17911
- throw new Error(`Fly API ${res.status} on ${path44}: ${text.slice(0, 200) || res.statusText}`);
18191
+ throw new Error(`Fly API ${res.status} on ${path45}: ${text.slice(0, 200) || res.statusText}`);
17912
18192
  }
17913
18193
  if (res.status === 204) return null;
17914
18194
  const raw = await res.text();
@@ -18134,7 +18414,9 @@ var PoolManager = class {
18134
18414
  const destroyedSurplus = await this.destroySurplus(surplus);
18135
18415
  const destroyed = destroyedTracked + destroyedSurplus;
18136
18416
  if (pruned > 0 || adopted > 0 || destroyed > 0) {
18137
- this.log(`resync: pruned ${pruned} stale, adopted ${adopted}, destroyed ${destroyed} surplus (free=${this.free.length})`);
18417
+ this.log(
18418
+ `resync: pruned ${pruned} stale, adopted ${adopted}, destroyed ${destroyed} surplus (free=${this.free.length})`
18419
+ );
18138
18420
  }
18139
18421
  await this.refill();
18140
18422
  }
@@ -18631,7 +18913,7 @@ async function poolServe() {
18631
18913
 
18632
18914
  // src/servers/runner-serve.ts
18633
18915
  import { spawn as spawn8 } from "child_process";
18634
- import * as fs45 from "fs";
18916
+ import * as fs47 from "fs";
18635
18917
  import { createServer as createServer5 } from "http";
18636
18918
  var DEFAULT_PORT2 = 8080;
18637
18919
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -18711,8 +18993,8 @@ async function defaultRunJob(job) {
18711
18993
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
18712
18994
  const branch = job.ref ?? "main";
18713
18995
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
18714
- fs45.rmSync(workdir, { recursive: true, force: true });
18715
- fs45.mkdirSync(workdir, { recursive: true });
18996
+ fs47.rmSync(workdir, { recursive: true, force: true });
18997
+ fs47.mkdirSync(workdir, { recursive: true });
18716
18998
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
18717
18999
  const interactive = job.mode === "interactive";
18718
19000
  const scheduled = job.mode === "scheduled";