@kody-ade/kody-engine 0.4.223 → 0.4.225

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.
Files changed (110) hide show
  1. package/README.md +8 -7
  2. package/dist/bin/kody.js +1380 -1263
  3. package/dist/executables/run/profile.json +1 -1
  4. package/dist/executables/types.ts +3 -1
  5. package/package.json +23 -22
  6. package/dist/duties/duty-scheduler/duty.md +0 -3
  7. package/dist/duties/duty-scheduler/profile.json +0 -6
  8. package/dist/duties/duty-tick/duty.md +0 -3
  9. package/dist/duties/duty-tick/profile.json +0 -6
  10. package/dist/duties/duty-tick-scripted/duty.md +0 -3
  11. package/dist/duties/duty-tick-scripted/profile.json +0 -6
  12. package/dist/duties/fix/duty.md +0 -10
  13. package/dist/duties/fix/profile.json +0 -6
  14. package/dist/duties/fix-ci/duty.md +0 -10
  15. package/dist/duties/fix-ci/profile.json +0 -6
  16. package/dist/duties/goal-scheduler/duty.md +0 -3
  17. package/dist/duties/goal-scheduler/profile.json +0 -6
  18. package/dist/duties/goal-tick/duty.md +0 -3
  19. package/dist/duties/goal-tick/profile.json +0 -6
  20. package/dist/duties/init/duty.md +0 -10
  21. package/dist/duties/init/profile.json +0 -6
  22. package/dist/duties/job-live-verify/duty.md +0 -3
  23. package/dist/duties/job-live-verify/profile.json +0 -6
  24. package/dist/duties/merge/duty.md +0 -10
  25. package/dist/duties/merge/profile.json +0 -6
  26. package/dist/duties/plan-verify/duty.md +0 -3
  27. package/dist/duties/plan-verify/profile.json +0 -6
  28. package/dist/duties/preview-build/duty.md +0 -10
  29. package/dist/duties/preview-build/profile.json +0 -6
  30. package/dist/duties/probe-skill/duty.md +0 -3
  31. package/dist/duties/probe-skill/profile.json +0 -6
  32. package/dist/duties/qa-goal/duty.md +0 -3
  33. package/dist/duties/qa-goal/profile.json +0 -6
  34. package/dist/duties/release/duty.md +0 -10
  35. package/dist/duties/release/profile.json +0 -6
  36. package/dist/duties/release-deploy/duty.md +0 -10
  37. package/dist/duties/release-deploy/profile.json +0 -6
  38. package/dist/duties/release-prepare/duty.md +0 -10
  39. package/dist/duties/release-prepare/profile.json +0 -6
  40. package/dist/duties/release-publish/duty.md +0 -10
  41. package/dist/duties/release-publish/profile.json +0 -6
  42. package/dist/duties/resolve/duty.md +0 -10
  43. package/dist/duties/resolve/profile.json +0 -6
  44. package/dist/duties/revert/duty.md +0 -10
  45. package/dist/duties/revert/profile.json +0 -6
  46. package/dist/duties/sync/duty.md +0 -10
  47. package/dist/duties/sync/profile.json +0 -6
  48. package/dist/duties/task-job-fail-once/duty.md +0 -3
  49. package/dist/duties/task-job-fail-once/profile.json +0 -6
  50. package/dist/duties/task-job-pass-a/duty.md +0 -3
  51. package/dist/duties/task-job-pass-a/profile.json +0 -6
  52. package/dist/duties/task-job-pass-b/duty.md +0 -3
  53. package/dist/duties/task-job-pass-b/profile.json +0 -6
  54. package/dist/duties/task-jobs/duty.md +0 -3
  55. package/dist/duties/task-jobs/profile.json +0 -6
  56. package/dist/duties/worker-ask/duty.md +0 -10
  57. package/dist/duties/worker-ask/profile.json +0 -6
  58. package/dist/executables/duty-scheduler/profile.json +0 -57
  59. package/dist/executables/duty-tick/profile.json +0 -78
  60. package/dist/executables/duty-tick/prompt.md +0 -77
  61. package/dist/executables/duty-tick/prompts/locked.md +0 -50
  62. package/dist/executables/duty-tick-scripted/profile.json +0 -72
  63. package/dist/executables/fix/profile.json +0 -91
  64. package/dist/executables/fix/prompt.md +0 -90
  65. package/dist/executables/fix-ci/profile.json +0 -71
  66. package/dist/executables/fix-ci/prompt.md +0 -78
  67. package/dist/executables/goal-scheduler/profile.json +0 -53
  68. package/dist/executables/goal-scheduler/scheduler.sh +0 -82
  69. package/dist/executables/goal-tick/profile.json +0 -69
  70. package/dist/executables/init/profile.json +0 -36
  71. package/dist/executables/job-live-verify/profile.json +0 -36
  72. package/dist/executables/job-live-verify/prompt.md +0 -42
  73. package/dist/executables/merge/profile.json +0 -54
  74. package/dist/executables/plan-verify/profile.json +0 -38
  75. package/dist/executables/plan-verify/prompt.md +0 -72
  76. package/dist/executables/preview-build/profile.json +0 -61
  77. package/dist/executables/preview-health/profile.json +0 -48
  78. package/dist/executables/preview-health/tick.sh +0 -319
  79. package/dist/executables/probe-skill/profile.json +0 -35
  80. package/dist/executables/probe-skill/prompt.md +0 -31
  81. package/dist/executables/probe-skill/skills/probe-skill-marker/SKILL.md +0 -18
  82. package/dist/executables/qa-goal/profile.json +0 -65
  83. package/dist/executables/release/deploy.sh +0 -157
  84. package/dist/executables/release/prepare.sh +0 -353
  85. package/dist/executables/release/profile.json +0 -95
  86. package/dist/executables/release/publish.sh +0 -88
  87. package/dist/executables/release/release.sh +0 -183
  88. package/dist/executables/release/wait.sh +0 -88
  89. package/dist/executables/release-deploy/deploy.sh +0 -222
  90. package/dist/executables/release-deploy/profile.json +0 -53
  91. package/dist/executables/release-prepare/prepare.sh +0 -368
  92. package/dist/executables/release-prepare/profile.json +0 -70
  93. package/dist/executables/release-publish/profile.json +0 -53
  94. package/dist/executables/release-publish/publish.sh +0 -137
  95. package/dist/executables/resolve/apply-prefer.sh +0 -54
  96. package/dist/executables/resolve/profile.json +0 -107
  97. package/dist/executables/resolve/prompt.md +0 -51
  98. package/dist/executables/revert/profile.json +0 -82
  99. package/dist/executables/revert/revert.sh +0 -33
  100. package/dist/executables/sync/profile.json +0 -48
  101. package/dist/executables/task-job-fail-once/profile.json +0 -44
  102. package/dist/executables/task-job-fail-once/prompt.md +0 -1
  103. package/dist/executables/task-job-pass-a/profile.json +0 -43
  104. package/dist/executables/task-job-pass-a/prompt.md +0 -1
  105. package/dist/executables/task-job-pass-b/profile.json +0 -43
  106. package/dist/executables/task-job-pass-b/prompt.md +0 -1
  107. package/dist/executables/task-jobs/profile.json +0 -41
  108. package/dist/executables/task-jobs/prompt.md +0 -1
  109. package/dist/executables/worker-ask/profile.json +0 -70
  110. package/dist/executables/worker-ask/prompt.md +0 -60
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.223",
18
+ version: "0.4.225",
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",
@@ -205,6 +205,7 @@ function loadConfig(projectDir = process.cwd()) {
205
205
  agent: {
206
206
  model: String(agent.model),
207
207
  ...parsePerExecutable(agent.perExecutable),
208
+ ...parsePerExecutableReasoningEffort(agent.perExecutableReasoningEffort),
208
209
  ...parseAgentReasoningEffort(agent.reasoningEffort)
209
210
  },
210
211
  issueContext: parseIssueContext(raw.issueContext),
@@ -216,6 +217,7 @@ function loadConfig(projectDir = process.cwd()) {
216
217
  classify: parseClassifyConfig(raw.classify),
217
218
  release: parseReleaseConfig(raw.release),
218
219
  jobs: parseJobsConfig(raw.jobs),
220
+ company: parseCompanyConfig(raw.company),
219
221
  access: parseAccessConfig(raw.access)
220
222
  };
221
223
  }
@@ -262,6 +264,28 @@ function parseJobsConfig(raw) {
262
264
  }
263
265
  return Object.keys(out).length > 0 ? out : void 0;
264
266
  }
267
+ function parseCompanyConfig(raw) {
268
+ if (!raw || typeof raw !== "object") return void 0;
269
+ const r = raw;
270
+ const out = {};
271
+ if (r.activeDuties !== void 0) out.activeDuties = parseSlugArray(r.activeDuties, "company.activeDuties");
272
+ if (r.activeGoals !== void 0) out.activeGoals = parseSlugArray(r.activeGoals, "company.activeGoals");
273
+ return Object.keys(out).length > 0 ? out : void 0;
274
+ }
275
+ function parseSlugArray(raw, field) {
276
+ if (!Array.isArray(raw)) throw new Error(`kody.config.json: ${field} must be an array of strings`);
277
+ const out = [];
278
+ for (const value of raw) {
279
+ if (typeof value !== "string") throw new Error(`kody.config.json: ${field} entries must be strings`);
280
+ const slug = value.trim();
281
+ if (!slug) continue;
282
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(slug)) {
283
+ throw new Error(`kody.config.json: ${field} contains invalid slug "${value}"`);
284
+ }
285
+ out.push(slug);
286
+ }
287
+ return [...new Set(out)];
288
+ }
265
289
  function recordValue(raw) {
266
290
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
267
291
  }
@@ -282,6 +306,15 @@ function parsePerExecutable(raw) {
282
306
  }
283
307
  return Object.keys(out).length > 0 ? { perExecutable: out } : {};
284
308
  }
309
+ function parsePerExecutableReasoningEffort(raw) {
310
+ if (!raw || typeof raw !== "object") return {};
311
+ const out = {};
312
+ for (const [k, v] of Object.entries(raw)) {
313
+ const effort = typeof v === "string" ? parseReasoningEffort(v) : null;
314
+ if (effort) out[k] = effort;
315
+ }
316
+ return Object.keys(out).length > 0 ? { perExecutableReasoningEffort: out } : {};
317
+ }
285
318
  function parseAgentReasoningEffort(raw) {
286
319
  if (typeof raw !== "string") return {};
287
320
  return { reasoningEffort: parseReasoningEffort(raw) ?? void 0 };
@@ -562,7 +595,7 @@ var init_events = __esm({
562
595
  // src/verify.ts
563
596
  import { spawn } from "child_process";
564
597
  function runCommand(command, cwd) {
565
- return new Promise((resolve6) => {
598
+ return new Promise((resolve7) => {
566
599
  const start = Date.now();
567
600
  const child = spawn(command, {
568
601
  cwd,
@@ -591,11 +624,11 @@ function runCommand(command, cwd) {
591
624
  child.on("exit", (code) => {
592
625
  clearTimeout(timer);
593
626
  const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
594
- resolve6({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
627
+ resolve7({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
595
628
  });
596
629
  child.on("error", (err) => {
597
630
  clearTimeout(timer);
598
- resolve6({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
631
+ resolve7({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
599
632
  });
600
633
  });
601
634
  }
@@ -1450,7 +1483,7 @@ var init_dutyMcp = __esm({
1450
1483
  TRUST_STATE_BRANCH = "kody-state";
1451
1484
  THREAD_BODY_MAX = 4e3;
1452
1485
  CHECK_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "STARTUP_FAILURE", "ACTION_REQUIRED"]);
1453
- DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "goal-tick", "worker-ask", "chat"];
1486
+ DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "worker-ask", "chat"];
1454
1487
  trackMarker = (key) => `<!-- kody-track:${key} -->`;
1455
1488
  commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
1456
1489
  GATE_EXEMPT_DUTIES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
@@ -1513,7 +1546,7 @@ var init_repoWorkspace = __esm({
1513
1546
  defaultCloneRepo = (repo, token, dir) => {
1514
1547
  fs4.mkdirSync(path4.dirname(dir), { recursive: true });
1515
1548
  const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
1516
- return new Promise((resolve6, reject) => {
1549
+ return new Promise((resolve7, reject) => {
1517
1550
  const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
1518
1551
  stdio: "inherit"
1519
1552
  });
@@ -1529,7 +1562,7 @@ var init_repoWorkspace = __esm({
1529
1562
  spawnSync("git", ["-C", dir, "config", "user.email", email]);
1530
1563
  } catch {
1531
1564
  }
1532
- resolve6();
1565
+ resolve7();
1533
1566
  });
1534
1567
  child.on("error", reject);
1535
1568
  });
@@ -1614,6 +1647,10 @@ function classifySubtype(subtype) {
1614
1647
  if (lower.includes("error")) return "model_error";
1615
1648
  return "generic_failed";
1616
1649
  }
1650
+ function isClaudeLoginRequiredText(text) {
1651
+ const normalized = text.toLowerCase();
1652
+ return normalized.includes("not logged in") && normalized.includes("/login");
1653
+ }
1617
1654
  function resolveTurnTimeoutMs(opts) {
1618
1655
  if (opts.maxTurnTimeoutMs !== void 0 && opts.maxTurnTimeoutMs !== null) {
1619
1656
  return opts.maxTurnTimeoutMs > 0 ? opts.maxTurnTimeoutMs : 0;
@@ -1696,6 +1733,7 @@ async function runAgent(opts) {
1696
1733
  messageCount = 0;
1697
1734
  let sawMutatingTool = false;
1698
1735
  let sawTerminalSuccess = false;
1736
+ let sawLoginRequired = false;
1699
1737
  let noWorkSuccess = false;
1700
1738
  try {
1701
1739
  const queryOptions = {
@@ -1809,10 +1847,10 @@ async function runAgent(opts) {
1809
1847
  let timer;
1810
1848
  let next;
1811
1849
  if (turnTimeoutMs > 0) {
1812
- const timeoutPromise = new Promise((resolve6) => {
1850
+ const timeoutPromise = new Promise((resolve7) => {
1813
1851
  timer = setTimeout(() => {
1814
1852
  timedOut = true;
1815
- resolve6({ done: true, value: void 0 });
1853
+ resolve7({ done: true, value: void 0 });
1816
1854
  }, turnTimeoutMs);
1817
1855
  });
1818
1856
  next = await Promise.race([nextPromise, timeoutPromise]);
@@ -1845,8 +1883,11 @@ async function runAgent(opts) {
1845
1883
  }
1846
1884
  }
1847
1885
  const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
1848
- if (line) process.stdout.write(`${line}
1886
+ if (line) {
1887
+ if (isClaudeLoginRequiredText(line)) sawLoginRequired = true;
1888
+ process.stdout.write(`${line}
1849
1889
  `);
1890
+ }
1850
1891
  const m = msg;
1851
1892
  if (opts.onProgress) {
1852
1893
  const blocks = m.message?.content ?? [];
@@ -1919,6 +1960,7 @@ async function runAgent(opts) {
1919
1960
  outcomeKind = "ok";
1920
1961
  sawTerminalSuccess = true;
1921
1962
  const text = (typeof m.result === "string" ? m.result : "").trim();
1963
+ if (isClaudeLoginRequiredText(text)) sawLoginRequired = true;
1922
1964
  if (text) resultTexts.push(text);
1923
1965
  } else {
1924
1966
  outcome = "failed";
@@ -1948,6 +1990,11 @@ async function runAgent(opts) {
1948
1990
  );
1949
1991
  }
1950
1992
  finalText = resultTexts.join("\n\n---\n\n");
1993
+ if (outcome === "completed" && sawLoginRequired) {
1994
+ outcome = "failed";
1995
+ outcomeKind = "model_error";
1996
+ errorMessage = "Claude Code reported it is not logged in; refusing to mark agent run successful";
1997
+ }
1951
1998
  if (outcome === "completed" && !sawMutatingTool) {
1952
1999
  const backendDead = opts.isBackendHealthy ? !await opts.isBackendHealthy() : false;
1953
2000
  const zeroOutput = tokens.output === 0 && finalText === "";
@@ -2011,6 +2058,107 @@ var init_agent = __esm({
2011
2058
  }
2012
2059
  });
2013
2060
 
2061
+ // src/companyStore.ts
2062
+ import { execFileSync as execFileSync2 } from "child_process";
2063
+ import * as crypto2 from "crypto";
2064
+ import * as fs6 from "fs";
2065
+ import * as os2 from "os";
2066
+ import * as path6 from "path";
2067
+ function getCompanyStoreRoot() {
2068
+ const store = resolveCompanyStore();
2069
+ if (!store) return null;
2070
+ const key = `${store.repo}#${store.ref}`;
2071
+ if (memo?.key === key) return memo.root;
2072
+ const root = fetchCompanyStore(store.repo, store.ref);
2073
+ memo = { key, root };
2074
+ return root;
2075
+ }
2076
+ function getCompanyStoreAssetRoot(kind) {
2077
+ const root = getCompanyStoreRoot();
2078
+ if (!root) return null;
2079
+ return path6.join(root, ".kody", kind);
2080
+ }
2081
+ function resolveCompanyStore() {
2082
+ const envStore = process.env[STORE_ENV]?.trim();
2083
+ if (!envStore && process.env.VITEST) return null;
2084
+ const repo = envStore || DEFAULT_COMPANY_STORE;
2085
+ if (repo === "0" || repo === "false" || repo === "off") return null;
2086
+ const ref = process.env[REF_ENV]?.trim() || DEFAULT_COMPANY_STORE_REF;
2087
+ if (!ref) return null;
2088
+ return { repo, ref };
2089
+ }
2090
+ function fetchCompanyStore(repo, ref) {
2091
+ const localRoot = localStoreRoot(repo);
2092
+ if (localRoot) return localRoot;
2093
+ const url = repoToGitUrl(repo);
2094
+ const cacheDir = path6.join(cacheRoot(), cacheKey(repo, ref));
2095
+ try {
2096
+ if (isGitHubShorthand(repo)) setupGithubGitAuth();
2097
+ fs6.mkdirSync(path6.dirname(cacheDir), { recursive: true });
2098
+ if (!fs6.existsSync(path6.join(cacheDir, ".git"))) {
2099
+ fs6.rmSync(cacheDir, { recursive: true, force: true });
2100
+ runGit(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
2101
+ }
2102
+ runGit(["-C", cacheDir, "remote", "set-url", "origin", url]);
2103
+ runGit(["-C", cacheDir, "fetch", "--depth=1", "origin", ref]);
2104
+ runGit(["-C", cacheDir, "checkout", "--detach", "FETCH_HEAD"]);
2105
+ return cacheDir;
2106
+ } catch (err) {
2107
+ const msg = err instanceof Error ? err.message : String(err);
2108
+ process.stderr.write(`[company-store] failed to fetch ${repo}#${ref}: ${msg}
2109
+ `);
2110
+ return null;
2111
+ }
2112
+ }
2113
+ function localStoreRoot(repo) {
2114
+ if (!path6.isAbsolute(repo) && !repo.startsWith(".")) return null;
2115
+ const root = path6.resolve(repo);
2116
+ if (!fs6.existsSync(path6.join(root, ".kody"))) return null;
2117
+ return root;
2118
+ }
2119
+ function repoToGitUrl(repo) {
2120
+ if (/^(https?:|ssh:|git@|file:)/.test(repo)) return repo;
2121
+ if (isGitHubShorthand(repo)) {
2122
+ return `https://github.com/${repo}.git`;
2123
+ }
2124
+ return repo;
2125
+ }
2126
+ function isGitHubShorthand(repo) {
2127
+ return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo);
2128
+ }
2129
+ function cacheRoot() {
2130
+ return process.env[CACHE_ENV]?.trim() || path6.join(os2.homedir(), ".cache", "kody", "company-store");
2131
+ }
2132
+ function cacheKey(repo, ref) {
2133
+ return crypto2.createHash("sha256").update(`${repo}#${ref}`).digest("hex").slice(0, 24);
2134
+ }
2135
+ function runGit(args) {
2136
+ execFileSync2("git", args, { stdio: ["ignore", "ignore", "pipe"] });
2137
+ }
2138
+ function setupGithubGitAuth() {
2139
+ const token = process.env.KODY_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_PAT;
2140
+ if (!token) return;
2141
+ try {
2142
+ execFileSync2("gh", ["auth", "setup-git", "--hostname", "github.com"], {
2143
+ env: { ...process.env, GH_TOKEN: token },
2144
+ stdio: ["ignore", "ignore", "pipe"]
2145
+ });
2146
+ } catch {
2147
+ }
2148
+ }
2149
+ var DEFAULT_COMPANY_STORE, DEFAULT_COMPANY_STORE_REF, STORE_ENV, REF_ENV, CACHE_ENV, memo;
2150
+ var init_companyStore = __esm({
2151
+ "src/companyStore.ts"() {
2152
+ "use strict";
2153
+ DEFAULT_COMPANY_STORE = "aharonyaircohen/kody-company-store";
2154
+ DEFAULT_COMPANY_STORE_REF = "stable";
2155
+ STORE_ENV = "KODY_COMPANY_STORE";
2156
+ REF_ENV = "KODY_COMPANY_STORE_REF";
2157
+ CACHE_ENV = "KODY_COMPANY_STORE_CACHE";
2158
+ memo = null;
2159
+ }
2160
+ });
2161
+
2014
2162
  // src/scripts/scheduleEvery.ts
2015
2163
  function isScheduleEvery(value) {
2016
2164
  return typeof value === "string" && SCHEDULE_EVERY_VALUES.includes(value);
@@ -2062,30 +2210,30 @@ var init_scheduleEvery = __esm({
2062
2210
  });
2063
2211
 
2064
2212
  // src/dutyFolders.ts
2065
- import * as fs6 from "fs";
2066
- import * as path6 from "path";
2213
+ import * as fs7 from "fs";
2214
+ import * as path7 from "path";
2067
2215
  function listDutyFolderSlugs(absDir) {
2068
- if (!fs6.existsSync(absDir)) return [];
2216
+ if (!fs7.existsSync(absDir)) return [];
2069
2217
  let entries;
2070
2218
  try {
2071
- entries = fs6.readdirSync(absDir, { withFileTypes: true });
2219
+ entries = fs7.readdirSync(absDir, { withFileTypes: true });
2072
2220
  } catch {
2073
2221
  return [];
2074
2222
  }
2075
- 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();
2223
+ 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();
2076
2224
  }
2077
2225
  function isDutyFolder(dir) {
2078
- return fs6.existsSync(path6.join(dir, DUTY_PROFILE_FILE)) && fs6.existsSync(path6.join(dir, DUTY_BODY_FILE));
2226
+ return fs7.existsSync(path7.join(dir, DUTY_PROFILE_FILE)) && fs7.existsSync(path7.join(dir, DUTY_BODY_FILE));
2079
2227
  }
2080
2228
  function readDutyFolder(root, slug) {
2081
- const dir = path6.join(root, slug);
2082
- const profilePath = path6.join(dir, DUTY_PROFILE_FILE);
2083
- const bodyPath = path6.join(dir, DUTY_BODY_FILE);
2084
- if (!fs6.existsSync(profilePath) || !fs6.statSync(profilePath).isFile()) return null;
2085
- if (!fs6.existsSync(bodyPath) || !fs6.statSync(bodyPath).isFile()) return null;
2229
+ const dir = path7.join(root, slug);
2230
+ const profilePath = path7.join(dir, DUTY_PROFILE_FILE);
2231
+ const bodyPath = path7.join(dir, DUTY_BODY_FILE);
2232
+ if (!fs7.existsSync(profilePath) || !fs7.statSync(profilePath).isFile()) return null;
2233
+ if (!fs7.existsSync(bodyPath) || !fs7.statSync(bodyPath).isFile()) return null;
2086
2234
  try {
2087
- const rawProfile = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
2088
- const rawBody = fs6.readFileSync(bodyPath, "utf-8");
2235
+ const rawProfile = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
2236
+ const rawBody = fs7.readFileSync(bodyPath, "utf-8");
2089
2237
  const { title, body } = parseDutyBody(rawBody, slug);
2090
2238
  return {
2091
2239
  slug,
@@ -2164,70 +2312,76 @@ var init_dutyFolders = __esm({
2164
2312
  });
2165
2313
 
2166
2314
  // src/registry.ts
2167
- import * as fs7 from "fs";
2168
- import * as path7 from "path";
2315
+ import * as fs8 from "fs";
2316
+ import * as path8 from "path";
2169
2317
  function getExecutablesRoot() {
2170
- const here = path7.dirname(new URL(import.meta.url).pathname);
2318
+ const here = path8.dirname(new URL(import.meta.url).pathname);
2171
2319
  const candidates = [
2172
- path7.join(here, "executables"),
2320
+ path8.join(here, "executables"),
2173
2321
  // dev: src/
2174
- path7.join(here, "..", "executables"),
2322
+ path8.join(here, "..", "executables"),
2175
2323
  // built: dist/bin → dist/executables
2176
- path7.join(here, "..", "src", "executables")
2324
+ path8.join(here, "..", "src", "executables")
2177
2325
  // fallback
2178
2326
  ];
2179
2327
  for (const c of candidates) {
2180
- if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
2328
+ if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
2181
2329
  }
2182
2330
  return candidates[0];
2183
2331
  }
2184
2332
  function getProjectExecutablesRoot() {
2185
- return path7.join(process.cwd(), ".kody", "executables");
2333
+ return path8.join(process.cwd(), ".kody", "executables");
2186
2334
  }
2187
2335
  function getProjectDutiesRoot() {
2188
- return path7.join(process.cwd(), ".kody", "duties");
2336
+ return path8.join(process.cwd(), ".kody", "duties");
2337
+ }
2338
+ function getCompanyStoreExecutablesRoot() {
2339
+ return getCompanyStoreAssetRoot("executables");
2340
+ }
2341
+ function getCompanyStoreDutiesRoot() {
2342
+ return getCompanyStoreAssetRoot("duties");
2189
2343
  }
2190
2344
  function getBuiltinJobsRoot() {
2191
- const here = path7.dirname(new URL(import.meta.url).pathname);
2345
+ const here = path8.dirname(new URL(import.meta.url).pathname);
2192
2346
  const candidates = [
2193
- path7.join(here, "jobs"),
2347
+ path8.join(here, "jobs"),
2194
2348
  // dev: src/
2195
- path7.join(here, "..", "jobs"),
2349
+ path8.join(here, "..", "jobs"),
2196
2350
  // built: dist/bin → dist/jobs
2197
- path7.join(here, "..", "src", "jobs")
2351
+ path8.join(here, "..", "src", "jobs")
2198
2352
  // fallback
2199
2353
  ];
2200
2354
  for (const c of candidates) {
2201
- if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
2355
+ if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
2202
2356
  }
2203
2357
  return candidates[0];
2204
2358
  }
2205
2359
  function getBuiltinDutiesRoot() {
2206
- const here = path7.dirname(new URL(import.meta.url).pathname);
2360
+ const here = path8.dirname(new URL(import.meta.url).pathname);
2207
2361
  const candidates = [
2208
- path7.join(here, "duties"),
2362
+ path8.join(here, "duties"),
2209
2363
  // dev: src/
2210
- path7.join(here, "..", "duties"),
2364
+ path8.join(here, "..", "duties"),
2211
2365
  // built: dist/bin → dist/duties
2212
- path7.join(here, "..", "src", "duties")
2366
+ path8.join(here, "..", "src", "duties")
2213
2367
  // fallback
2214
2368
  ];
2215
2369
  for (const c of candidates) {
2216
- if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
2370
+ if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
2217
2371
  }
2218
2372
  return candidates[0];
2219
2373
  }
2220
2374
  function listBuiltinJobs(root = getBuiltinJobsRoot()) {
2221
- if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
2375
+ if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
2222
2376
  const out = [];
2223
- for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
2377
+ for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
2224
2378
  if (ent.name.startsWith("_") || ent.name.startsWith(".")) continue;
2225
- const full = path7.join(root, ent.name);
2379
+ const full = path8.join(root, ent.name);
2226
2380
  if (ent.isDirectory()) {
2227
- const profilePath = path7.join(full, DUTY_PROFILE_FILE);
2228
- const bodyPath = path7.join(full, DUTY_BODY_FILE);
2229
- if (!fs7.existsSync(profilePath) || !fs7.statSync(profilePath).isFile()) continue;
2230
- if (!fs7.existsSync(bodyPath) || !fs7.statSync(bodyPath).isFile()) continue;
2381
+ const profilePath = path8.join(full, DUTY_PROFILE_FILE);
2382
+ const bodyPath = path8.join(full, DUTY_BODY_FILE);
2383
+ if (!fs8.existsSync(profilePath) || !fs8.statSync(profilePath).isFile()) continue;
2384
+ if (!fs8.existsSync(bodyPath) || !fs8.statSync(bodyPath).isFile()) continue;
2231
2385
  out.push({ slug: ent.name, dir: full, profilePath, bodyPath });
2232
2386
  }
2233
2387
  }
@@ -2235,20 +2389,25 @@ function listBuiltinJobs(root = getBuiltinJobsRoot()) {
2235
2389
  return out;
2236
2390
  }
2237
2391
  function getExecutableRoots() {
2238
- return [getProjectExecutablesRoot(), getExecutablesRoot()];
2392
+ const storeRoot = getCompanyStoreExecutablesRoot();
2393
+ return [getProjectExecutablesRoot(), ...storeRoot ? [storeRoot] : [], getExecutablesRoot()];
2394
+ }
2395
+ function getDutyRoots(projectDutiesRoot = getProjectDutiesRoot()) {
2396
+ const storeRoot = getCompanyStoreDutiesRoot();
2397
+ return [projectDutiesRoot, ...storeRoot ? [storeRoot] : [], getBuiltinDutiesRoot()];
2239
2398
  }
2240
2399
  function listExecutables(roots = getExecutableRoots()) {
2241
2400
  const rootList = typeof roots === "string" ? [roots] : roots;
2242
2401
  const seen = /* @__PURE__ */ new Set();
2243
2402
  const out = [];
2244
2403
  for (const root of rootList) {
2245
- if (!fs7.existsSync(root)) continue;
2246
- const entries = fs7.readdirSync(root, { withFileTypes: true });
2404
+ if (!fs8.existsSync(root)) continue;
2405
+ const entries = fs8.readdirSync(root, { withFileTypes: true });
2247
2406
  for (const ent of entries) {
2248
2407
  if (!ent.isDirectory()) continue;
2249
2408
  if (seen.has(ent.name)) continue;
2250
- const profilePath = path7.join(root, ent.name, DUTY_PROFILE_FILE);
2251
- if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
2409
+ const profilePath = path8.join(root, ent.name, DUTY_PROFILE_FILE);
2410
+ if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
2252
2411
  out.push({ name: ent.name, profilePath });
2253
2412
  seen.add(ent.name);
2254
2413
  }
@@ -2260,8 +2419,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
2260
2419
  if (!isSafeName(name)) return null;
2261
2420
  const rootList = typeof roots === "string" ? [roots] : roots;
2262
2421
  for (const root of rootList) {
2263
- const profilePath = path7.join(root, name, "profile.json");
2264
- if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
2422
+ const profilePath = path8.join(root, name, "profile.json");
2423
+ if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
2265
2424
  return profilePath;
2266
2425
  }
2267
2426
  }
@@ -2276,8 +2435,14 @@ function listDutyActions(projectDutiesRoot = getProjectDutiesRoot()) {
2276
2435
  seen.add(action.action);
2277
2436
  out.push(action);
2278
2437
  };
2279
- for (const action of listProjectFolderDutyActions(projectDutiesRoot)) add(action);
2280
- for (const action of listBuiltinDutyActions()) add(action);
2438
+ const roots = getDutyRoots(projectDutiesRoot);
2439
+ for (const action of listFolderDutyActions(roots[0], "project-folder")) add(action);
2440
+ if (roots.length === 3) {
2441
+ for (const action of listFolderDutyActions(roots[1], "company-store")) add(action);
2442
+ for (const action of listBuiltinDutyActions(roots[2])) add(action);
2443
+ } else {
2444
+ for (const action of listBuiltinDutyActions(roots[1])) add(action);
2445
+ }
2281
2446
  return out.sort((a, b) => a.action.localeCompare(b.action));
2282
2447
  }
2283
2448
  function resolveDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
@@ -2287,11 +2452,19 @@ function resolveDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
2287
2452
  function hasDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
2288
2453
  return resolveDutyAction(action, projectDutiesRoot) !== null;
2289
2454
  }
2455
+ function resolveDutyFolder(slug, projectDutiesRoot = getProjectDutiesRoot()) {
2456
+ if (!isSafeName(slug)) return null;
2457
+ for (const root of getDutyRoots(projectDutiesRoot)) {
2458
+ const duty = readDutyFolder(root, slug);
2459
+ if (duty) return duty;
2460
+ }
2461
+ return null;
2462
+ }
2290
2463
  function isSafeName(name) {
2291
2464
  return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
2292
2465
  }
2293
- function listProjectFolderDutyActions(root) {
2294
- if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
2466
+ function listFolderDutyActions(root, source) {
2467
+ if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
2295
2468
  const out = [];
2296
2469
  for (const slug of listDutyFolderSlugs(root)) {
2297
2470
  if (!isSafeName(slug)) continue;
@@ -2304,7 +2477,7 @@ function listProjectFolderDutyActions(root) {
2304
2477
  duty: slug,
2305
2478
  executable,
2306
2479
  cliArgs: duty.config.executable ? {} : { duty: slug },
2307
- source: "project-folder",
2480
+ source,
2308
2481
  describe: duty.config.describe ?? duty.title,
2309
2482
  profilePath: duty.profilePath,
2310
2483
  bodyPath: duty.bodyPath
@@ -2313,7 +2486,7 @@ function listProjectFolderDutyActions(root) {
2313
2486
  return out.sort((a, b) => a.action.localeCompare(b.action));
2314
2487
  }
2315
2488
  function listBuiltinDutyActions(root = getBuiltinDutiesRoot()) {
2316
- if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
2489
+ if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
2317
2490
  const out = [];
2318
2491
  for (const slug of listDutyFolderSlugs(root)) {
2319
2492
  if (!isSafeName(slug)) continue;
@@ -2338,7 +2511,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
2338
2511
  const profilePath = resolveExecutable(name, roots);
2339
2512
  if (!profilePath) return null;
2340
2513
  try {
2341
- const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
2514
+ const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
2342
2515
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
2343
2516
  return raw.inputs;
2344
2517
  } catch {
@@ -2373,26 +2546,27 @@ function parseGenericFlags(argv) {
2373
2546
  var init_registry = __esm({
2374
2547
  "src/registry.ts"() {
2375
2548
  "use strict";
2549
+ init_companyStore();
2376
2550
  init_dutyFolders();
2377
2551
  }
2378
2552
  });
2379
2553
 
2380
2554
  // src/task-artifacts.ts
2381
- import fs8 from "fs";
2382
- import path8 from "path";
2555
+ import fs9 from "fs";
2556
+ import path9 from "path";
2383
2557
  function prepareTaskArtifactsDir(cwd, taskId) {
2384
2558
  const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
2385
- const relDir = path8.join(".kody", "tasks", safeId);
2386
- const absDir = path8.join(cwd, relDir);
2387
- fs8.mkdirSync(absDir, { recursive: true });
2559
+ const relDir = path9.join(".kody", "tasks", safeId);
2560
+ const absDir = path9.join(cwd, relDir);
2561
+ fs9.mkdirSync(absDir, { recursive: true });
2388
2562
  return { taskId: safeId, absDir, relDir };
2389
2563
  }
2390
2564
  function verifyTaskArtifacts(absDir) {
2391
2565
  const missing = [];
2392
2566
  for (const name of TASK_ARTIFACT_FILES) {
2393
- const full = path8.join(absDir, name);
2567
+ const full = path9.join(absDir, name);
2394
2568
  try {
2395
- const stat = fs8.statSync(full);
2569
+ const stat = fs9.statSync(full);
2396
2570
  if (!stat.isFile() || stat.size === 0) missing.push(name);
2397
2571
  } catch {
2398
2572
  missing.push(name);
@@ -2467,8 +2641,8 @@ var init_task_artifacts = __esm({
2467
2641
  });
2468
2642
 
2469
2643
  // src/gha.ts
2470
- import { execFileSync as execFileSync2 } from "child_process";
2471
- import * as fs14 from "fs";
2644
+ import { execFileSync as execFileSync3 } from "child_process";
2645
+ import * as fs15 from "fs";
2472
2646
  function getRunUrl() {
2473
2647
  const server = process.env.GITHUB_SERVER_URL;
2474
2648
  const repo = process.env.GITHUB_REPOSITORY;
@@ -2479,10 +2653,10 @@ function getRunUrl() {
2479
2653
  function reactToTriggerComment(cwd) {
2480
2654
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
2481
2655
  const eventPath = process.env.GITHUB_EVENT_PATH;
2482
- if (!eventPath || !fs14.existsSync(eventPath)) return;
2656
+ if (!eventPath || !fs15.existsSync(eventPath)) return;
2483
2657
  let event = null;
2484
2658
  try {
2485
- event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
2659
+ event = JSON.parse(fs15.readFileSync(eventPath, "utf-8"));
2486
2660
  } catch {
2487
2661
  return;
2488
2662
  }
@@ -2510,7 +2684,7 @@ function reactToTriggerComment(cwd) {
2510
2684
  for (let attempt = 0; attempt < 3; attempt++) {
2511
2685
  if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
2512
2686
  try {
2513
- execFileSync2("gh", args, opts);
2687
+ execFileSync3("gh", args, opts);
2514
2688
  return;
2515
2689
  } catch (err) {
2516
2690
  lastErr = err;
@@ -2523,7 +2697,7 @@ function reactToTriggerComment(cwd) {
2523
2697
  }
2524
2698
  function sleepMs(ms) {
2525
2699
  try {
2526
- execFileSync2("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
2700
+ execFileSync3("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
2527
2701
  } catch {
2528
2702
  }
2529
2703
  }
@@ -2693,31 +2867,31 @@ var init_lifecycles = __esm({
2693
2867
  });
2694
2868
 
2695
2869
  // src/scripts/buildSyntheticPlugin.ts
2696
- import * as fs15 from "fs";
2697
- import * as os2 from "os";
2698
- import * as path13 from "path";
2870
+ import * as fs16 from "fs";
2871
+ import * as os3 from "os";
2872
+ import * as path14 from "path";
2699
2873
  function getPluginsCatalogRoot() {
2700
- const here = path13.dirname(new URL(import.meta.url).pathname);
2874
+ const here = path14.dirname(new URL(import.meta.url).pathname);
2701
2875
  const candidates = [
2702
- path13.join(here, "..", "plugins"),
2876
+ path14.join(here, "..", "plugins"),
2703
2877
  // dev: src/scripts → src/plugins
2704
- path13.join(here, "..", "..", "plugins"),
2878
+ path14.join(here, "..", "..", "plugins"),
2705
2879
  // built: dist/scripts → dist/plugins
2706
- path13.join(here, "..", "..", "src", "plugins")
2880
+ path14.join(here, "..", "..", "src", "plugins")
2707
2881
  // fallback
2708
2882
  ];
2709
2883
  for (const c of candidates) {
2710
- if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
2884
+ if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
2711
2885
  }
2712
2886
  return candidates[0];
2713
2887
  }
2714
2888
  function copyDir(src, dst) {
2715
- fs15.mkdirSync(dst, { recursive: true });
2716
- for (const ent of fs15.readdirSync(src, { withFileTypes: true })) {
2717
- const s = path13.join(src, ent.name);
2718
- const d = path13.join(dst, ent.name);
2889
+ fs16.mkdirSync(dst, { recursive: true });
2890
+ for (const ent of fs16.readdirSync(src, { withFileTypes: true })) {
2891
+ const s = path14.join(src, ent.name);
2892
+ const d = path14.join(dst, ent.name);
2719
2893
  if (ent.isDirectory()) copyDir(s, d);
2720
- else if (ent.isFile()) fs15.copyFileSync(s, d);
2894
+ else if (ent.isFile()) fs16.copyFileSync(s, d);
2721
2895
  }
2722
2896
  }
2723
2897
  var buildSyntheticPlugin;
@@ -2730,45 +2904,45 @@ var init_buildSyntheticPlugin = __esm({
2730
2904
  if (!needsSynthetic) return;
2731
2905
  const catalog = getPluginsCatalogRoot();
2732
2906
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2733
- const root = path13.join(os2.tmpdir(), `kody-synth-${runId}`);
2734
- fs15.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
2907
+ const root = path14.join(os3.tmpdir(), `kody-synth-${runId}`);
2908
+ fs16.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
2735
2909
  const resolvePart = (bucket, entry) => {
2736
- const local = path13.join(profile.dir, bucket, entry);
2737
- if (fs15.existsSync(local)) return local;
2738
- const central = path13.join(catalog, bucket, entry);
2739
- if (fs15.existsSync(central)) return central;
2910
+ const local = path14.join(profile.dir, bucket, entry);
2911
+ if (fs16.existsSync(local)) return local;
2912
+ const central = path14.join(catalog, bucket, entry);
2913
+ if (fs16.existsSync(central)) return central;
2740
2914
  throw new Error(
2741
2915
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
2742
2916
  );
2743
2917
  };
2744
2918
  if (cc.skills.length > 0) {
2745
- const dst = path13.join(root, "skills");
2746
- fs15.mkdirSync(dst, { recursive: true });
2919
+ const dst = path14.join(root, "skills");
2920
+ fs16.mkdirSync(dst, { recursive: true });
2747
2921
  for (const name of cc.skills) {
2748
- copyDir(resolvePart("skills", name), path13.join(dst, name));
2922
+ copyDir(resolvePart("skills", name), path14.join(dst, name));
2749
2923
  }
2750
2924
  }
2751
2925
  if (cc.commands.length > 0) {
2752
- const dst = path13.join(root, "commands");
2753
- fs15.mkdirSync(dst, { recursive: true });
2926
+ const dst = path14.join(root, "commands");
2927
+ fs16.mkdirSync(dst, { recursive: true });
2754
2928
  for (const name of cc.commands) {
2755
- fs15.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
2929
+ fs16.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
2756
2930
  }
2757
2931
  }
2758
2932
  if (cc.hooks.length > 0) {
2759
- const dst = path13.join(root, "hooks");
2760
- fs15.mkdirSync(dst, { recursive: true });
2933
+ const dst = path14.join(root, "hooks");
2934
+ fs16.mkdirSync(dst, { recursive: true });
2761
2935
  const merged = { hooks: {} };
2762
2936
  for (const name of cc.hooks) {
2763
2937
  const src = resolvePart("hooks", `${name}.json`);
2764
- const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
2938
+ const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
2765
2939
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
2766
2940
  if (!Array.isArray(entries)) continue;
2767
2941
  if (!merged.hooks[event]) merged.hooks[event] = [];
2768
2942
  merged.hooks[event].push(...entries);
2769
2943
  }
2770
2944
  }
2771
- fs15.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2945
+ fs16.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2772
2946
  `);
2773
2947
  }
2774
2948
  const manifest = {
@@ -2778,7 +2952,7 @@ var init_buildSyntheticPlugin = __esm({
2778
2952
  };
2779
2953
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
2780
2954
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
2781
- fs15.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2955
+ fs16.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2782
2956
  `);
2783
2957
  ctx.data.syntheticPluginPath = root;
2784
2958
  };
@@ -2786,8 +2960,8 @@ var init_buildSyntheticPlugin = __esm({
2786
2960
  });
2787
2961
 
2788
2962
  // src/subagents.ts
2789
- import * as fs16 from "fs";
2790
- import * as path14 from "path";
2963
+ import * as fs17 from "fs";
2964
+ import * as path15 from "path";
2791
2965
  function splitFrontmatter(raw) {
2792
2966
  const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
2793
2967
  if (!match) return { fm: {}, body: raw.trim() };
@@ -2800,10 +2974,10 @@ function splitFrontmatter(raw) {
2800
2974
  return { fm, body: (match[2] ?? "").trim() };
2801
2975
  }
2802
2976
  function resolveAgentFile(profileDir, name) {
2803
- const local = path14.join(profileDir, "agents", `${name}.md`);
2804
- if (fs16.existsSync(local)) return local;
2805
- const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
2806
- if (fs16.existsSync(central)) return central;
2977
+ const local = path15.join(profileDir, "agents", `${name}.md`);
2978
+ if (fs17.existsSync(local)) return local;
2979
+ const central = path15.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
2980
+ if (fs17.existsSync(central)) return central;
2807
2981
  throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
2808
2982
  }
2809
2983
  function captureSubagentTemplates(profile) {
@@ -2812,7 +2986,7 @@ function captureSubagentTemplates(profile) {
2812
2986
  const out = {};
2813
2987
  for (const name of names) {
2814
2988
  try {
2815
- out[name] = fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2989
+ out[name] = fs17.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2816
2990
  } catch {
2817
2991
  }
2818
2992
  }
@@ -2823,7 +2997,7 @@ function loadSubagents(profile) {
2823
2997
  if (!names || names.length === 0) return void 0;
2824
2998
  const agents = {};
2825
2999
  for (const name of names) {
2826
- const raw = profile.subagentTemplates?.[name] ?? fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
3000
+ const raw = profile.subagentTemplates?.[name] ?? fs17.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2827
3001
  const { fm, body } = splitFrontmatter(raw);
2828
3002
  if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
2829
3003
  const def = {
@@ -2847,15 +3021,15 @@ var init_subagents = __esm({
2847
3021
  });
2848
3022
 
2849
3023
  // src/profile.ts
2850
- import * as fs17 from "fs";
2851
- import * as path15 from "path";
3024
+ import * as fs18 from "fs";
3025
+ import * as path16 from "path";
2852
3026
  function loadProfile(profilePath) {
2853
- if (!fs17.existsSync(profilePath)) {
3027
+ if (!fs18.existsSync(profilePath)) {
2854
3028
  throw new ProfileError(profilePath, "file not found");
2855
3029
  }
2856
3030
  let raw;
2857
3031
  try {
2858
- raw = JSON.parse(fs17.readFileSync(profilePath, "utf-8"));
3032
+ raw = JSON.parse(fs18.readFileSync(profilePath, "utf-8"));
2859
3033
  } catch (err) {
2860
3034
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
2861
3035
  }
@@ -2866,7 +3040,7 @@ function loadProfile(profilePath) {
2866
3040
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
2867
3041
  if (unknownKeys.length > 0) {
2868
3042
  process.stderr.write(
2869
- `[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
3043
+ `[kody profile] ${path16.basename(path16.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
2870
3044
  `
2871
3045
  );
2872
3046
  }
@@ -2955,8 +3129,8 @@ function loadProfile(profilePath) {
2955
3129
  // Phase 5 in-process handoff opt-in. Default false; containers
2956
3130
  // flip to true after end-to-end verification.
2957
3131
  preloadContext: r.preloadContext === true,
2958
- dir: path15.dirname(profilePath),
2959
- promptTemplates: readPromptTemplates(path15.dirname(profilePath))
3132
+ dir: path16.dirname(profilePath),
3133
+ promptTemplates: readPromptTemplates(path16.dirname(profilePath))
2960
3134
  };
2961
3135
  if (lifecycle) {
2962
3136
  applyLifecycle(profile, profilePath);
@@ -2988,16 +3162,16 @@ function readPromptTemplates(dir) {
2988
3162
  const out = {};
2989
3163
  const read = (p) => {
2990
3164
  try {
2991
- out[p] = fs17.readFileSync(p, "utf-8");
3165
+ out[p] = fs18.readFileSync(p, "utf-8");
2992
3166
  } catch {
2993
3167
  }
2994
3168
  };
2995
- read(path15.join(dir, "prompt.md"));
2996
- read(path15.join(dir, "duty.md"));
3169
+ read(path16.join(dir, "prompt.md"));
3170
+ read(path16.join(dir, "duty.md"));
2997
3171
  try {
2998
- const promptsDir = path15.join(dir, "prompts");
2999
- for (const ent of fs17.readdirSync(promptsDir)) {
3000
- if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
3172
+ const promptsDir = path16.join(dir, "prompts");
3173
+ for (const ent of fs18.readdirSync(promptsDir)) {
3174
+ if (ent.endsWith(".md")) read(path16.join(promptsDir, ent));
3001
3175
  }
3002
3176
  } catch {
3003
3177
  }
@@ -3072,6 +3246,7 @@ function parseClaudeCode(p, raw) {
3072
3246
  permissionMode,
3073
3247
  maxTurns: typeof r.maxTurns === "number" ? r.maxTurns : null,
3074
3248
  maxThinkingTokens: typeof r.maxThinkingTokens === "number" ? r.maxThinkingTokens : null,
3249
+ reasoningEffort: typeof r.reasoningEffort === "string" ? parseReasoningEffort(r.reasoningEffort) : null,
3075
3250
  maxTurnTimeoutSec: typeof r.maxTurnTimeoutSec === "number" ? r.maxTurnTimeoutSec : null,
3076
3251
  systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
3077
3252
  cacheable: r.cacheable === true,
@@ -3255,6 +3430,7 @@ var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHIL
3255
3430
  var init_profile = __esm({
3256
3431
  "src/profile.ts"() {
3257
3432
  "use strict";
3433
+ init_config();
3258
3434
  init_dutyMcp();
3259
3435
  init_lifecycles();
3260
3436
  init_profile_error();
@@ -3304,7 +3480,7 @@ var init_profile = __esm({
3304
3480
  });
3305
3481
 
3306
3482
  // src/state.ts
3307
- import { execFileSync as execFileSync3 } from "child_process";
3483
+ import { execFileSync as execFileSync4 } from "child_process";
3308
3484
  function emptyState() {
3309
3485
  return {
3310
3486
  schemaVersion: 1,
@@ -3327,7 +3503,7 @@ function ghToken2() {
3327
3503
  function gh2(args, input, cwd) {
3328
3504
  const token = ghToken2();
3329
3505
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
3330
- return execFileSync3("gh", args, {
3506
+ return execFileSync4("gh", args, {
3331
3507
  encoding: "utf-8",
3332
3508
  timeout: API_TIMEOUT_MS2,
3333
3509
  cwd,
@@ -3674,16 +3850,16 @@ var init_state = __esm({
3674
3850
  });
3675
3851
 
3676
3852
  // src/prompt.ts
3677
- import * as fs18 from "fs";
3678
- import * as path16 from "path";
3853
+ import * as fs19 from "fs";
3854
+ import * as path17 from "path";
3679
3855
  function loadProjectConventions(projectDir) {
3680
3856
  const out = [];
3681
3857
  for (const rel of CONVENTION_FILES) {
3682
- const abs = path16.join(projectDir, rel);
3683
- if (!fs18.existsSync(abs)) continue;
3858
+ const abs = path17.join(projectDir, rel);
3859
+ if (!fs19.existsSync(abs)) continue;
3684
3860
  let content;
3685
3861
  try {
3686
- content = fs18.readFileSync(abs, "utf-8");
3862
+ content = fs19.readFileSync(abs, "utf-8");
3687
3863
  } catch {
3688
3864
  continue;
3689
3865
  }
@@ -3918,28 +4094,28 @@ var loadMemoryContext_exports = {};
3918
4094
  __export(loadMemoryContext_exports, {
3919
4095
  loadMemoryContext: () => loadMemoryContext
3920
4096
  });
3921
- import * as fs19 from "fs";
3922
- import * as path17 from "path";
4097
+ import * as fs20 from "fs";
4098
+ import * as path18 from "path";
3923
4099
  function collectPages(memoryAbs) {
3924
4100
  const out = [];
3925
4101
  walkMd(memoryAbs, (file) => {
3926
4102
  let stat;
3927
4103
  try {
3928
- stat = fs19.statSync(file);
4104
+ stat = fs20.statSync(file);
3929
4105
  } catch {
3930
4106
  return;
3931
4107
  }
3932
4108
  let raw;
3933
4109
  try {
3934
- raw = fs19.readFileSync(file, "utf-8");
4110
+ raw = fs20.readFileSync(file, "utf-8");
3935
4111
  } catch {
3936
4112
  return;
3937
4113
  }
3938
4114
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
3939
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path17.basename(file, ".md");
4115
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path18.basename(file, ".md");
3940
4116
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
3941
4117
  out.push({
3942
- relPath: path17.relative(memoryAbs, file),
4118
+ relPath: path18.relative(memoryAbs, file),
3943
4119
  title,
3944
4120
  updated,
3945
4121
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
@@ -4007,16 +4183,16 @@ function walkMd(root, visit) {
4007
4183
  const dir = stack.pop();
4008
4184
  let names;
4009
4185
  try {
4010
- names = fs19.readdirSync(dir);
4186
+ names = fs20.readdirSync(dir);
4011
4187
  } catch {
4012
4188
  continue;
4013
4189
  }
4014
4190
  for (const name of names) {
4015
4191
  if (name.startsWith(".")) continue;
4016
- const full = path17.join(dir, name);
4192
+ const full = path18.join(dir, name);
4017
4193
  let stat;
4018
4194
  try {
4019
- stat = fs19.statSync(full);
4195
+ stat = fs20.statSync(full);
4020
4196
  } catch {
4021
4197
  continue;
4022
4198
  }
@@ -4039,8 +4215,8 @@ var init_loadMemoryContext = __esm({
4039
4215
  TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
4040
4216
  loadMemoryContext = async (ctx) => {
4041
4217
  if (typeof ctx.data.memoryContext === "string") return;
4042
- const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
4043
- if (!fs19.existsSync(memoryAbs)) {
4218
+ const memoryAbs = path18.join(ctx.cwd, MEMORY_DIR_RELATIVE);
4219
+ if (!fs20.existsSync(memoryAbs)) {
4044
4220
  ctx.data.memoryContext = "";
4045
4221
  return;
4046
4222
  }
@@ -4083,12 +4259,12 @@ var init_loadCoverageRules = __esm({
4083
4259
  });
4084
4260
 
4085
4261
  // src/container.ts
4086
- import { execFileSync as execFileSync4 } from "child_process";
4087
- import * as fs20 from "fs";
4262
+ import { execFileSync as execFileSync5 } from "child_process";
4263
+ import * as fs21 from "fs";
4088
4264
  function getProfileInputsForChild(profileName, _cwd) {
4089
4265
  try {
4090
4266
  const profilePath = resolveProfilePath(profileName);
4091
- if (!fs20.existsSync(profilePath)) return null;
4267
+ if (!fs21.existsSync(profilePath)) return null;
4092
4268
  return loadProfile(profilePath).inputs;
4093
4269
  } catch {
4094
4270
  return null;
@@ -4322,7 +4498,7 @@ async function runContainerLoop(profile, ctx, input) {
4322
4498
  }
4323
4499
  function resetWorkingTree(cwd) {
4324
4500
  try {
4325
- execFileSync4("git", ["reset", "--hard", "HEAD"], {
4501
+ execFileSync5("git", ["reset", "--hard", "HEAD"], {
4326
4502
  cwd,
4327
4503
  stdio: ["ignore", "pipe", "pipe"],
4328
4504
  timeout: 3e4
@@ -4421,6 +4597,91 @@ here \u2014 never substitute it for the truth.`;
4421
4597
  }
4422
4598
  });
4423
4599
 
4600
+ // src/dutyReport.ts
4601
+ function parseDutyReportsFromText(text) {
4602
+ const reports = [];
4603
+ for (const match of text.matchAll(REPORT_LINE)) {
4604
+ const raw = match[1]?.trim();
4605
+ if (!raw) continue;
4606
+ try {
4607
+ const parsed = parseDutyReport(JSON.parse(raw));
4608
+ if (parsed) reports.push(parsed);
4609
+ } catch {
4610
+ }
4611
+ }
4612
+ return reports;
4613
+ }
4614
+ function parseDutyReport(raw) {
4615
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4616
+ const obj = raw;
4617
+ const target = parseTarget(obj.target);
4618
+ if (!target) return null;
4619
+ const evidence = parseBooleanRecord(obj.evidence);
4620
+ const facts = parseFacts(obj.facts);
4621
+ if (!evidence && !facts) return null;
4622
+ return {
4623
+ target,
4624
+ ...evidence ? { evidence } : {},
4625
+ ...facts ? { facts } : {}
4626
+ };
4627
+ }
4628
+ function applyDutyReportToGoalState(state, report) {
4629
+ const priorFacts = parseFacts(state.extra.facts) ?? {};
4630
+ const nextFacts = { ...priorFacts };
4631
+ for (const [key, value] of Object.entries(report.facts ?? {})) {
4632
+ if (CONTROL_FACT_KEYS.has(key)) continue;
4633
+ nextFacts[key] = value;
4634
+ }
4635
+ for (const [key, value] of Object.entries(report.evidence ?? {})) {
4636
+ nextFacts[key] = value;
4637
+ }
4638
+ const pending = nextFacts.pendingEvidence;
4639
+ if (typeof pending === "string" && Object.hasOwn(report.evidence ?? {}, pending)) {
4640
+ delete nextFacts.pendingEvidence;
4641
+ }
4642
+ return {
4643
+ ...state,
4644
+ extra: {
4645
+ ...state.extra,
4646
+ facts: nextFacts
4647
+ }
4648
+ };
4649
+ }
4650
+ function parseTarget(raw) {
4651
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4652
+ const target = raw;
4653
+ if (target.type !== "goal" && target.type !== "task" && target.type !== "duty") return null;
4654
+ if (typeof target.id !== "string" || target.id.trim().length === 0) return null;
4655
+ return { type: target.type, id: target.id.trim() };
4656
+ }
4657
+ function parseBooleanRecord(raw) {
4658
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4659
+ const out = {};
4660
+ for (const [key, value] of Object.entries(raw)) {
4661
+ if (typeof key !== "string" || key.length === 0 || typeof value !== "boolean") return null;
4662
+ out[key] = value;
4663
+ }
4664
+ return out;
4665
+ }
4666
+ function parseFacts(raw) {
4667
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4668
+ const out = {};
4669
+ for (const [key, value] of Object.entries(raw)) {
4670
+ if (typeof key !== "string" || key.length === 0) return null;
4671
+ if (CONTROL_FACT_KEYS.has(key)) continue;
4672
+ out[key] = value;
4673
+ }
4674
+ return out;
4675
+ }
4676
+ var REPORT_LINE, CONTROL_FACT_KEYS;
4677
+ var init_dutyReport = __esm({
4678
+ "src/dutyReport.ts"() {
4679
+ "use strict";
4680
+ REPORT_LINE = /^KODY_DUTY_REPORT=(.+)$/gm;
4681
+ CONTROL_FACT_KEYS = /* @__PURE__ */ new Set(["blockers", "destination", "duties", "route", "stage", "state"]);
4682
+ }
4683
+ });
4684
+
4424
4685
  // src/lifecycleLabels.ts
4425
4686
  function groupOf(label) {
4426
4687
  const idx = label.indexOf(":");
@@ -4549,10 +4810,10 @@ var init_lifecycleLabels = __esm({
4549
4810
  });
4550
4811
 
4551
4812
  // src/litellm.ts
4552
- import { execFileSync as execFileSync5, spawn as spawn3 } from "child_process";
4553
- import * as fs21 from "fs";
4554
- import * as os3 from "os";
4555
- import * as path18 from "path";
4813
+ import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
4814
+ import * as fs22 from "fs";
4815
+ import * as os4 from "os";
4816
+ import * as path19 from "path";
4556
4817
  async function checkLitellmHealth(url) {
4557
4818
  try {
4558
4819
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -4582,7 +4843,7 @@ function generateLitellmConfigYaml(model) {
4582
4843
  }
4583
4844
  function litellmImportable() {
4584
4845
  try {
4585
- execFileSync5("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
4846
+ execFileSync6("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
4586
4847
  return true;
4587
4848
  } catch {
4588
4849
  return false;
@@ -4590,7 +4851,7 @@ function litellmImportable() {
4590
4851
  }
4591
4852
  function locateLitellmScript() {
4592
4853
  try {
4593
- const out = execFileSync5(
4854
+ const out = execFileSync6(
4594
4855
  "python3",
4595
4856
  [
4596
4857
  "-c",
@@ -4605,7 +4866,7 @@ function locateLitellmScript() {
4605
4866
  }
4606
4867
  function resolveLitellmCommand() {
4607
4868
  try {
4608
- execFileSync5("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
4869
+ execFileSync6("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
4609
4870
  return "litellm";
4610
4871
  } catch {
4611
4872
  if (!litellmImportable()) {
@@ -4613,7 +4874,7 @@ function resolveLitellmCommand() {
4613
4874
  let installed = false;
4614
4875
  for (const pip of ["pip", "pip3"]) {
4615
4876
  try {
4616
- execFileSync5(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
4877
+ execFileSync6(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
4617
4878
  installed = true;
4618
4879
  break;
4619
4880
  } catch {
@@ -4637,13 +4898,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4637
4898
  let child;
4638
4899
  let logPath;
4639
4900
  const spawnProxy = () => {
4640
- const configPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
4641
- fs21.writeFileSync(configPath, generateLitellmConfigYaml(model));
4901
+ const configPath = path19.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
4902
+ fs22.writeFileSync(configPath, generateLitellmConfigYaml(model));
4642
4903
  const args = ["--config", configPath, "--port", port];
4643
- const nextLogPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
4644
- const outFd = fs21.openSync(nextLogPath, "w");
4904
+ const nextLogPath = path19.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
4905
+ const outFd = fs22.openSync(nextLogPath, "w");
4645
4906
  child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
4646
- fs21.closeSync(outFd);
4907
+ fs22.closeSync(outFd);
4647
4908
  logPath = nextLogPath;
4648
4909
  };
4649
4910
  const waitForHealth = async () => {
@@ -4657,7 +4918,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4657
4918
  const readLogTail = () => {
4658
4919
  if (!logPath) return "";
4659
4920
  try {
4660
- return fs21.readFileSync(logPath, "utf-8").slice(-2e3);
4921
+ return fs22.readFileSync(logPath, "utf-8").slice(-2e3);
4661
4922
  } catch {
4662
4923
  return "";
4663
4924
  }
@@ -4709,10 +4970,10 @@ ${tail}`
4709
4970
  return { url, kill: killChild, isHealthy, ensureHealthy };
4710
4971
  }
4711
4972
  function readDotenvApiKeys(projectDir) {
4712
- const dotenvPath = path18.join(projectDir, ".env");
4713
- if (!fs21.existsSync(dotenvPath)) return {};
4973
+ const dotenvPath = path19.join(projectDir, ".env");
4974
+ if (!fs22.existsSync(dotenvPath)) return {};
4714
4975
  const result = {};
4715
- for (const rawLine of fs21.readFileSync(dotenvPath, "utf-8").split("\n")) {
4976
+ for (const rawLine of fs22.readFileSync(dotenvPath, "utf-8").split("\n")) {
4716
4977
  const line = rawLine.trim();
4717
4978
  if (!line || line.startsWith("#")) continue;
4718
4979
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -4744,14 +5005,14 @@ var init_litellm = __esm({
4744
5005
  });
4745
5006
 
4746
5007
  // src/pushWithRetry.ts
4747
- import { execFileSync as execFileSync6 } from "child_process";
5008
+ import { execFileSync as execFileSync7 } from "child_process";
4748
5009
  function sleepSync(ms) {
4749
5010
  if (ms <= 0) return;
4750
5011
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
4751
5012
  }
4752
- function runGit(args, cwd) {
5013
+ function runGit2(args, cwd) {
4753
5014
  try {
4754
- const stdout = execFileSync6("git", args, {
5015
+ const stdout = execFileSync7("git", args, {
4755
5016
  cwd,
4756
5017
  encoding: "utf-8",
4757
5018
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4767,7 +5028,7 @@ function runGit(args, cwd) {
4767
5028
  }
4768
5029
  function resolveBranch(cwd, explicit) {
4769
5030
  if (explicit?.trim()) return explicit.trim();
4770
- const r = runGit(["symbolic-ref", "--short", "HEAD"], cwd);
5031
+ const r = runGit2(["symbolic-ref", "--short", "HEAD"], cwd);
4771
5032
  return r.ok ? r.stdout.trim() : "";
4772
5033
  }
4773
5034
  function pushWithRetry(opts = {}) {
@@ -4781,14 +5042,14 @@ function pushWithRetry(opts = {}) {
4781
5042
  const pushArgs = opts.setUpstream ? ["push", "-u", "origin", `HEAD:${branch}`] : ["push", "origin", "HEAD"];
4782
5043
  let lastError = "";
4783
5044
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
4784
- const push = runGit(pushArgs, cwd);
5045
+ const push = runGit2(pushArgs, cwd);
4785
5046
  if (push.ok) return { ok: true, attempts: attempt };
4786
5047
  lastError = push.stderr || push.stdout || "(no error detail)";
4787
5048
  if (!NON_FAST_FORWARD_RE.test(lastError)) {
4788
5049
  return { ok: false, reason: `push failed (not retryable): ${lastError.trim().slice(-400)}`, attempts: attempt };
4789
5050
  }
4790
5051
  if (attempt === maxRetries) break;
4791
- const fetch2 = runGit(["fetch", "origin", branch], cwd);
5052
+ const fetch2 = runGit2(["fetch", "origin", branch], cwd);
4792
5053
  if (!fetch2.ok) {
4793
5054
  return {
4794
5055
  ok: false,
@@ -4796,9 +5057,9 @@ function pushWithRetry(opts = {}) {
4796
5057
  attempts: attempt
4797
5058
  };
4798
5059
  }
4799
- const rebase = runGit(["rebase", "--rebase-merges", `origin/${branch}`], cwd);
5060
+ const rebase = runGit2(["rebase", "--rebase-merges", `origin/${branch}`], cwd);
4800
5061
  if (!rebase.ok) {
4801
- runGit(["rebase", "--abort"], cwd);
5062
+ runGit2(["rebase", "--abort"], cwd);
4802
5063
  return {
4803
5064
  ok: false,
4804
5065
  reason: `rebase onto origin/${branch} failed (conflict?): ${(rebase.stderr || rebase.stdout).trim().slice(-400)}`,
@@ -4826,12 +5087,12 @@ var init_pushWithRetry = __esm({
4826
5087
  });
4827
5088
 
4828
5089
  // src/commit.ts
4829
- import { execFileSync as execFileSync7 } from "child_process";
4830
- import * as fs22 from "fs";
4831
- import * as path19 from "path";
5090
+ import { execFileSync as execFileSync8 } from "child_process";
5091
+ import * as fs23 from "fs";
5092
+ import * as path20 from "path";
4832
5093
  function git(args, cwd) {
4833
5094
  try {
4834
- return execFileSync7("git", args, {
5095
+ return execFileSync8("git", args, {
4835
5096
  encoding: "utf-8",
4836
5097
  timeout: 12e4,
4837
5098
  cwd,
@@ -4866,18 +5127,18 @@ function ensureGitIdentity(cwd) {
4866
5127
  }
4867
5128
  function abortUnfinishedGitOps(cwd) {
4868
5129
  const aborted = [];
4869
- const gitDir = path19.join(cwd ?? process.cwd(), ".git");
4870
- if (!fs22.existsSync(gitDir)) return aborted;
4871
- if (fs22.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
5130
+ const gitDir = path20.join(cwd ?? process.cwd(), ".git");
5131
+ if (!fs23.existsSync(gitDir)) return aborted;
5132
+ if (fs23.existsSync(path20.join(gitDir, "MERGE_HEAD"))) {
4872
5133
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
4873
5134
  }
4874
- if (fs22.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
5135
+ if (fs23.existsSync(path20.join(gitDir, "CHERRY_PICK_HEAD"))) {
4875
5136
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
4876
5137
  }
4877
- if (fs22.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
5138
+ if (fs23.existsSync(path20.join(gitDir, "REVERT_HEAD"))) {
4878
5139
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
4879
5140
  }
4880
- if (fs22.existsSync(path19.join(gitDir, "rebase-merge")) || fs22.existsSync(path19.join(gitDir, "rebase-apply"))) {
5141
+ if (fs23.existsSync(path20.join(gitDir, "rebase-merge")) || fs23.existsSync(path20.join(gitDir, "rebase-apply"))) {
4881
5142
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
4882
5143
  }
4883
5144
  try {
@@ -4898,7 +5159,7 @@ function isForbiddenPath(p) {
4898
5159
  return false;
4899
5160
  }
4900
5161
  function listChangedFiles(cwd) {
4901
- const raw = execFileSync7("git", ["status", "--porcelain=v1", "-z"], {
5162
+ const raw = execFileSync8("git", ["status", "--porcelain=v1", "-z"], {
4902
5163
  encoding: "utf-8",
4903
5164
  cwd,
4904
5165
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4910,7 +5171,7 @@ function listChangedFiles(cwd) {
4910
5171
  }
4911
5172
  function listFilesInCommit(ref = "HEAD", cwd) {
4912
5173
  try {
4913
- const raw = execFileSync7("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
5174
+ const raw = execFileSync8("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
4914
5175
  encoding: "utf-8",
4915
5176
  cwd,
4916
5177
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4933,7 +5194,7 @@ function normalizeCommitMessage(raw) {
4933
5194
  function commitAndPush(branch, agentMessage, cwd) {
4934
5195
  const allChanged = listChangedFiles(cwd);
4935
5196
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
4936
- const mergeHeadExists = fs22.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
5197
+ const mergeHeadExists = fs23.existsSync(path20.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
4937
5198
  if (allowedFiles.length === 0 && !mergeHeadExists) {
4938
5199
  return { committed: false, pushed: false, sha: "", message: "" };
4939
5200
  }
@@ -5089,10 +5350,10 @@ var init_saveTaskState = __esm({
5089
5350
  });
5090
5351
 
5091
5352
  // src/scripts/advanceFlow.ts
5092
- import { execFileSync as execFileSync8 } from "child_process";
5353
+ import { execFileSync as execFileSync9 } from "child_process";
5093
5354
  function ghComment(issueNumber, body, cwd, label) {
5094
5355
  try {
5095
- execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5356
+ execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5096
5357
  timeout: API_TIMEOUT_MS3,
5097
5358
  cwd,
5098
5359
  stdio: ["ignore", "pipe", "pipe"]
@@ -5168,6 +5429,291 @@ var init_advanceFlow = __esm({
5168
5429
  }
5169
5430
  });
5170
5431
 
5432
+ // src/goal/manager.ts
5433
+ function evidenceSatisfied(goal, evidence) {
5434
+ return goal.facts[evidence] === true;
5435
+ }
5436
+ function firstMissingEvidence(goal) {
5437
+ return goal.destination.evidence.find((evidence) => !evidenceSatisfied(goal, evidence));
5438
+ }
5439
+ function pushBlocker(goal, reason) {
5440
+ if (!goal.blockers.includes(reason)) goal.blockers.push(reason);
5441
+ }
5442
+ function isSimpleGoal(goal) {
5443
+ return goal.type === SIMPLE_GOAL_TYPE;
5444
+ }
5445
+ function applySimpleGoalTaskSummary(goal, summary) {
5446
+ goal.facts.simpleAttachedTaskCount = summary.total;
5447
+ goal.facts.simpleOpenTaskCount = summary.open;
5448
+ goal.facts[SIMPLE_GOAL_EVIDENCE] = summary.total > 0 && summary.open === 0;
5449
+ }
5450
+ function isFactReference(value) {
5451
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5452
+ const record = value;
5453
+ return Object.keys(record).length === 1 && typeof record.fact === "string" && record.fact.length > 0;
5454
+ }
5455
+ function isCliArgValue(value) {
5456
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
5457
+ }
5458
+ function resolveRouteArgs(goal, step) {
5459
+ const cliArgs = {};
5460
+ for (const [name, value] of Object.entries(step.args ?? {})) {
5461
+ if (!isFactReference(value)) {
5462
+ cliArgs[name] = value;
5463
+ continue;
5464
+ }
5465
+ const factValue = goal.facts[value.fact];
5466
+ if (factValue === void 0 || factValue === null) {
5467
+ return { ok: false, reason: `route arg ${name} needs missing fact ${value.fact}` };
5468
+ }
5469
+ if (!isCliArgValue(factValue)) {
5470
+ return { ok: false, reason: `route arg ${name} needs scalar fact ${value.fact}` };
5471
+ }
5472
+ cliArgs[name] = factValue;
5473
+ }
5474
+ return { ok: true, cliArgs };
5475
+ }
5476
+ function planManagedGoalTick(goal) {
5477
+ goal.blockers = [];
5478
+ const missing = firstMissingEvidence(goal);
5479
+ if (!missing) {
5480
+ goal.stage = "done";
5481
+ delete goal.facts.pendingEvidence;
5482
+ return { kind: "done" };
5483
+ }
5484
+ const pending = goal.facts.pendingEvidence;
5485
+ if (pending === missing) {
5486
+ const stage = typeof goal.stage === "string" ? goal.stage : "waiting";
5487
+ return { kind: "wait", evidence: missing, stage, reason: `waiting for evidence: ${missing}` };
5488
+ }
5489
+ const step = goal.route.find((candidate) => candidate.evidence === missing);
5490
+ if (!step) {
5491
+ if (isSimpleGoal(goal) && missing === SIMPLE_GOAL_EVIDENCE) {
5492
+ goal.stage = "waiting";
5493
+ delete goal.facts.pendingEvidence;
5494
+ const total = typeof goal.facts.simpleAttachedTaskCount === "number" ? goal.facts.simpleAttachedTaskCount : 0;
5495
+ const open = typeof goal.facts.simpleOpenTaskCount === "number" ? goal.facts.simpleOpenTaskCount : 0;
5496
+ return {
5497
+ kind: "wait",
5498
+ evidence: missing,
5499
+ stage: "waiting",
5500
+ reason: total === 0 ? "waiting for labelled tasks" : `waiting for ${open} open labelled task(s)`
5501
+ };
5502
+ }
5503
+ const reason = `no route step for evidence: ${missing}`;
5504
+ goal.stage = "blocked";
5505
+ pushBlocker(goal, reason);
5506
+ return { kind: "blocked", evidence: missing, stage: "blocked", reason };
5507
+ }
5508
+ if (!goal.duties.includes(step.duty)) {
5509
+ const reason = `route duty ${step.duty} is not attached to this goal`;
5510
+ goal.stage = "blocked";
5511
+ pushBlocker(goal, reason);
5512
+ return { kind: "blocked", evidence: missing, stage: step.stage, reason };
5513
+ }
5514
+ const resolved = resolveRouteArgs(goal, step);
5515
+ if (!resolved.ok) {
5516
+ goal.stage = "blocked";
5517
+ pushBlocker(goal, resolved.reason);
5518
+ return { kind: "blocked", evidence: missing, stage: step.stage, reason: resolved.reason };
5519
+ }
5520
+ goal.stage = step.stage;
5521
+ goal.facts.pendingEvidence = missing;
5522
+ return {
5523
+ kind: "dispatch",
5524
+ evidence: missing,
5525
+ stage: step.stage,
5526
+ duty: step.duty,
5527
+ executable: step.executable,
5528
+ cliArgs: resolved.cliArgs
5529
+ };
5530
+ }
5531
+ function asStringArray(value) {
5532
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return null;
5533
+ return [...value];
5534
+ }
5535
+ function asRecord(value) {
5536
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
5537
+ return { ...value };
5538
+ }
5539
+ function asRoute(value) {
5540
+ if (!Array.isArray(value)) return null;
5541
+ const route = [];
5542
+ for (const item of value) {
5543
+ if (!item || typeof item !== "object" || Array.isArray(item)) return null;
5544
+ const raw = item;
5545
+ if (typeof raw.evidence !== "string" || typeof raw.stage !== "string" || typeof raw.duty !== "string") {
5546
+ return null;
5547
+ }
5548
+ const args = raw.args === void 0 ? void 0 : asRecord(raw.args);
5549
+ if (raw.args !== void 0 && !args) return null;
5550
+ route.push({
5551
+ evidence: raw.evidence,
5552
+ stage: raw.stage,
5553
+ duty: raw.duty,
5554
+ executable: typeof raw.executable === "string" ? raw.executable : void 0,
5555
+ args: args ?? void 0
5556
+ });
5557
+ }
5558
+ return route;
5559
+ }
5560
+ function managedGoalFromState(state) {
5561
+ const extra = state.extra;
5562
+ const destination = asRecord(extra.destination);
5563
+ const evidence = asStringArray(destination?.evidence);
5564
+ const duties = asStringArray(extra.duties);
5565
+ const route = asRoute(extra.route);
5566
+ const facts = asRecord(extra.facts);
5567
+ const blockers = asStringArray(extra.blockers);
5568
+ if (typeof extra.type !== "string" || !destination || typeof destination.outcome !== "string" || !evidence || !duties || !route || !facts || !blockers) {
5569
+ return null;
5570
+ }
5571
+ return {
5572
+ type: extra.type,
5573
+ destination: { outcome: destination.outcome, evidence },
5574
+ duties,
5575
+ route,
5576
+ stage: typeof extra.stage === "string" ? extra.stage : void 0,
5577
+ facts,
5578
+ blockers
5579
+ };
5580
+ }
5581
+ function writeManagedGoalToState(state, goal) {
5582
+ return {
5583
+ ...state,
5584
+ extra: {
5585
+ ...state.extra,
5586
+ type: goal.type,
5587
+ destination: goal.destination,
5588
+ duties: goal.duties,
5589
+ route: goal.route,
5590
+ stage: goal.stage,
5591
+ facts: goal.facts,
5592
+ blockers: goal.blockers
5593
+ }
5594
+ };
5595
+ }
5596
+ var SIMPLE_GOAL_TYPE, SIMPLE_GOAL_EVIDENCE;
5597
+ var init_manager = __esm({
5598
+ "src/goal/manager.ts"() {
5599
+ "use strict";
5600
+ SIMPLE_GOAL_TYPE = "simple";
5601
+ SIMPLE_GOAL_EVIDENCE = "labelledTasksComplete";
5602
+ }
5603
+ });
5604
+
5605
+ // src/goal/state.ts
5606
+ import * as fs24 from "fs";
5607
+ import * as path21 from "path";
5608
+ function parseGoalState(filePath, raw) {
5609
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
5610
+ throw new GoalStateError(filePath, "must be JSON object");
5611
+ }
5612
+ const r = raw;
5613
+ const stateValue = r.state;
5614
+ if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
5615
+ throw new GoalStateError(
5616
+ filePath,
5617
+ `"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
5618
+ );
5619
+ }
5620
+ const parsed = {
5621
+ state: stateValue,
5622
+ extra: {}
5623
+ };
5624
+ for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
5625
+ const v = r[ts];
5626
+ if (typeof v === "string" && v.length > 0) parsed[ts] = v;
5627
+ }
5628
+ const known = /* @__PURE__ */ new Set(["state", "updatedAt", "createdAt", "startedAt"]);
5629
+ for (const [k, v] of Object.entries(r)) {
5630
+ if (!known.has(k)) parsed.extra[k] = v;
5631
+ }
5632
+ return parsed;
5633
+ }
5634
+ function serializeGoalState(s) {
5635
+ const obj = { ...s.extra, state: s.state };
5636
+ if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
5637
+ if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
5638
+ if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
5639
+ return `${JSON.stringify(obj, null, 2)}
5640
+ `;
5641
+ }
5642
+ function nowIso() {
5643
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
5644
+ }
5645
+ var VALID_STATES, GoalStateError;
5646
+ var init_state2 = __esm({
5647
+ "src/goal/state.ts"() {
5648
+ "use strict";
5649
+ VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
5650
+ GoalStateError = class extends Error {
5651
+ constructor(path45, message) {
5652
+ super(`Invalid goal state at ${path45}:
5653
+ ${message}`);
5654
+ this.path = path45;
5655
+ this.name = "GoalStateError";
5656
+ }
5657
+ path;
5658
+ };
5659
+ }
5660
+ });
5661
+
5662
+ // src/scripts/advanceManagedGoal.ts
5663
+ function readSimpleGoalTaskSummary(goalId, cwd) {
5664
+ const raw = gh(
5665
+ ["issue", "list", "--state", "all", "--label", `goal:${goalId}`, "--limit", "1000", "--json", "number,state"],
5666
+ { cwd }
5667
+ );
5668
+ const issues = JSON.parse(raw);
5669
+ const total = issues.length;
5670
+ const open = issues.filter((issue) => String(issue.state ?? "").toLowerCase() === "open").length;
5671
+ return { total, open };
5672
+ }
5673
+ var advanceManagedGoal;
5674
+ var init_advanceManagedGoal = __esm({
5675
+ "src/scripts/advanceManagedGoal.ts"() {
5676
+ "use strict";
5677
+ init_manager();
5678
+ init_state2();
5679
+ init_issue();
5680
+ advanceManagedGoal = async (ctx) => {
5681
+ ctx.skipAgent = true;
5682
+ const goal = ctx.data.goal;
5683
+ if (!goal?.raw) {
5684
+ ctx.output.exitCode = 1;
5685
+ ctx.output.reason = "advanceManagedGoal requires loadGoalState first";
5686
+ return;
5687
+ }
5688
+ ctx.data.goalOriginalStateText = serializeGoalState(goal.raw);
5689
+ const managed = managedGoalFromState(goal.raw);
5690
+ if (!managed) {
5691
+ ctx.output.reason = "goal has no managed-goal contract; nothing to advance";
5692
+ return;
5693
+ }
5694
+ if (isSimpleGoal(managed)) {
5695
+ applySimpleGoalTaskSummary(managed, readSimpleGoalTaskSummary(goal.id, ctx.cwd));
5696
+ }
5697
+ const decision = planManagedGoalTick(managed);
5698
+ ctx.data.managedGoalDecision = decision;
5699
+ if (decision.kind === "done") {
5700
+ goal.state = "done";
5701
+ }
5702
+ goal.raw = writeManagedGoalToState({ ...goal.raw, state: goal.state }, managed);
5703
+ if (decision.kind === "blocked" || decision.kind === "wait" || decision.kind === "idle" || decision.kind === "done") {
5704
+ ctx.output.reason = decision.kind === "done" ? "managed goal complete" : decision.reason;
5705
+ return;
5706
+ }
5707
+ ctx.output.nextDispatch = {
5708
+ duty: decision.duty,
5709
+ executable: decision.executable,
5710
+ cliArgs: decision.cliArgs
5711
+ };
5712
+ ctx.output.reason = `dispatch ${decision.duty} for ${decision.evidence}`;
5713
+ };
5714
+ }
5715
+ });
5716
+
5171
5717
  // src/stateBranch.ts
5172
5718
  function is404(err) {
5173
5719
  const msg = err instanceof Error ? err.message : String(err);
@@ -5285,14 +5831,126 @@ var init_appendCompanyActivity = __esm({
5285
5831
  }
5286
5832
  });
5287
5833
 
5288
- // src/coverage.ts
5289
- import { execFileSync as execFileSync9 } from "child_process";
5290
- function patternToRegex(pattern) {
5291
- let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
5292
- s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
5293
- s = s.replace(/§S/g, "(?:.*/)?").replace(/§A/g, ".*");
5294
- return new RegExp(`^${s}$`);
5295
- }
5834
+ // src/goal/stateStore.ts
5835
+ function statePath(goalId) {
5836
+ return `.kody/goals/instances/${goalId}/state.json`;
5837
+ }
5838
+ function is4042(err) {
5839
+ const msg = err instanceof Error ? err.message : String(err);
5840
+ return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
5841
+ }
5842
+ function fetchGoalState(owner, repo, goalId, cwd) {
5843
+ const filePath = statePath(goalId);
5844
+ let raw;
5845
+ try {
5846
+ raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
5847
+ } catch (err) {
5848
+ if (is4042(err)) return null;
5849
+ throw err;
5850
+ }
5851
+ const o = JSON.parse(raw);
5852
+ if (!o.content) return null;
5853
+ const decoded = Buffer.from(o.content, "base64").toString("utf-8");
5854
+ return parseGoalState(filePath, JSON.parse(decoded));
5855
+ }
5856
+ function putGoalState(owner, repo, goalId, state, message, cwd) {
5857
+ ensureStateBranch(owner, repo, cwd);
5858
+ const filePath = statePath(goalId);
5859
+ const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
5860
+ for (let attempt = 1; attempt <= 3; attempt++) {
5861
+ let sha;
5862
+ try {
5863
+ const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
5864
+ const o = JSON.parse(cur);
5865
+ if (o.sha) sha = o.sha;
5866
+ } catch (err) {
5867
+ if (!is4042(err)) throw err;
5868
+ }
5869
+ const payload = { message, content, branch: STATE_BRANCH };
5870
+ if (sha) payload.sha = sha;
5871
+ try {
5872
+ gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
5873
+ cwd,
5874
+ input: JSON.stringify(payload)
5875
+ });
5876
+ return;
5877
+ } catch (err) {
5878
+ const msg = err instanceof Error ? err.message : String(err);
5879
+ const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
5880
+ if (!conflict || attempt === 3) throw err;
5881
+ }
5882
+ }
5883
+ }
5884
+ var init_stateStore = __esm({
5885
+ "src/goal/stateStore.ts"() {
5886
+ "use strict";
5887
+ init_issue();
5888
+ init_stateBranch();
5889
+ init_state2();
5890
+ }
5891
+ });
5892
+
5893
+ // src/scripts/applyDutyReports.ts
5894
+ function collectReports(raw, agentResult) {
5895
+ const out = [];
5896
+ if (Array.isArray(raw)) {
5897
+ for (const item of raw) {
5898
+ if (isDutyReport(item)) out.push(item);
5899
+ }
5900
+ }
5901
+ if (agentResult?.finalText) out.push(...parseDutyReportsFromText(agentResult.finalText));
5902
+ return out;
5903
+ }
5904
+ function isDutyReport(raw) {
5905
+ return !!raw && typeof raw === "object" && !Array.isArray(raw) && !!raw.target && (raw.target.type === "goal" || raw.target.type === "task" || raw.target.type === "duty") && typeof raw.target.id === "string";
5906
+ }
5907
+ function describeMessage(report) {
5908
+ const keys = [
5909
+ ...Object.keys(report.evidence ?? {}).map((key) => `evidence:${key}`),
5910
+ ...Object.keys(report.facts ?? {}).map((key) => `fact:${key}`)
5911
+ ];
5912
+ return `chore(goals): apply duty report for ${report.target.id}${keys.length ? ` (${keys.join(", ")})` : ""}`;
5913
+ }
5914
+ var applyDutyReports;
5915
+ var init_applyDutyReports = __esm({
5916
+ "src/scripts/applyDutyReports.ts"() {
5917
+ "use strict";
5918
+ init_dutyReport();
5919
+ init_state2();
5920
+ init_stateStore();
5921
+ applyDutyReports = async (ctx, _profile, agentResult) => {
5922
+ const reports = collectReports(ctx.data.dutyReports, agentResult);
5923
+ if (reports.length === 0) return;
5924
+ const owner = ctx.config.github?.owner;
5925
+ const repo = ctx.config.github?.repo;
5926
+ if (!owner || !repo) {
5927
+ process.stderr.write("[kody duty-report] missing github owner/repo; cannot apply reports\n");
5928
+ return;
5929
+ }
5930
+ const goalReports = reports.filter((report) => report.target.type === "goal");
5931
+ for (const report of goalReports) {
5932
+ const prior = fetchGoalState(owner, repo, report.target.id, ctx.cwd);
5933
+ if (!prior) {
5934
+ process.stderr.write(`[kody duty-report] goal ${report.target.id} missing on kody-state; report skipped
5935
+ `);
5936
+ continue;
5937
+ }
5938
+ const next = applyDutyReportToGoalState(prior, report);
5939
+ if (serializeGoalState(next) === serializeGoalState(prior)) continue;
5940
+ putGoalState(owner, repo, report.target.id, { ...next, updatedAt: nowIso() }, describeMessage(report), ctx.cwd);
5941
+ }
5942
+ };
5943
+ }
5944
+ });
5945
+
5946
+ // src/coverage.ts
5947
+ import { execFileSync as execFileSync10 } from "child_process";
5948
+ function patternToRegex(pattern) {
5949
+ let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
5950
+ s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
5951
+ s = s.replace(/§S/g, "(?:.*/)?").replace(/§A/g, ".*");
5952
+ return new RegExp(`^${s}$`);
5953
+ }
5296
5954
  function renderSiblingPath(file, requireSibling) {
5297
5955
  const lastSlash = file.lastIndexOf("/");
5298
5956
  const dir = lastSlash === -1 ? "" : file.slice(0, lastSlash + 1);
@@ -5304,7 +5962,7 @@ function renderSiblingPath(file, requireSibling) {
5304
5962
  }
5305
5963
  function safeGit(args, cwd) {
5306
5964
  try {
5307
- return execFileSync9("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
5965
+ return execFileSync10("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
5308
5966
  } catch {
5309
5967
  return "";
5310
5968
  }
@@ -5451,11 +6109,11 @@ var init_classifyByLabel = __esm({
5451
6109
  });
5452
6110
 
5453
6111
  // src/scripts/commitAndPush.ts
5454
- import * as fs23 from "fs";
5455
- import * as path20 from "path";
6112
+ import * as fs25 from "fs";
6113
+ import * as path22 from "path";
5456
6114
  function sentinelPathForStage(cwd, profileName) {
5457
6115
  const runId = resolveRunId();
5458
- return path20.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
6116
+ return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
5459
6117
  }
5460
6118
  var DEFAULT_COMMIT_MESSAGE, commitAndPush2;
5461
6119
  var init_commitAndPush = __esm({
@@ -5472,9 +6130,9 @@ var init_commitAndPush = __esm({
5472
6130
  }
5473
6131
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
5474
6132
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
5475
- if (sentinel && fs23.existsSync(sentinel)) {
6133
+ if (sentinel && fs25.existsSync(sentinel)) {
5476
6134
  try {
5477
- const replay = JSON.parse(fs23.readFileSync(sentinel, "utf-8"));
6135
+ const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
5478
6136
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
5479
6137
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
5480
6138
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -5527,8 +6185,8 @@ var init_commitAndPush = __esm({
5527
6185
  const result = ctx.data.commitResult;
5528
6186
  if (sentinel && result?.committed) {
5529
6187
  try {
5530
- fs23.mkdirSync(path20.dirname(sentinel), { recursive: true });
5531
- fs23.writeFileSync(
6188
+ fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
6189
+ fs25.writeFileSync(
5532
6190
  sentinel,
5533
6191
  JSON.stringify(
5534
6192
  {
@@ -5549,148 +6207,17 @@ var init_commitAndPush = __esm({
5549
6207
  }
5550
6208
  });
5551
6209
 
5552
- // src/goal/state.ts
5553
- import * as fs24 from "fs";
5554
- import * as path21 from "path";
5555
- function parseGoalState(filePath, raw) {
5556
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
5557
- throw new GoalStateError(filePath, "must be a JSON object");
5558
- }
5559
- const r = raw;
5560
- const stateValue = r.state;
5561
- if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
5562
- throw new GoalStateError(
5563
- filePath,
5564
- `"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
5565
- );
5566
- }
5567
- const parsed = {
5568
- state: stateValue,
5569
- extra: {}
5570
- };
5571
- if (typeof r.mergeApproved === "boolean") {
5572
- parsed.mergeApproved = r.mergeApproved;
5573
- }
5574
- if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
5575
- parsed.lastDispatchedIssue = r.lastDispatchedIssue;
5576
- }
5577
- for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
5578
- const v = r[ts];
5579
- if (typeof v === "string" && v.length > 0) parsed[ts] = v;
5580
- }
5581
- const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
5582
- for (const [k, v] of Object.entries(r)) {
5583
- if (!known.has(k)) parsed.extra[k] = v;
5584
- }
5585
- return parsed;
5586
- }
5587
- function serializeGoalState(s) {
5588
- const obj = { ...s.extra, state: s.state };
5589
- if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
5590
- if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
5591
- if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
5592
- if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
5593
- if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
5594
- return `${JSON.stringify(obj, null, 2)}
5595
- `;
5596
- }
5597
- function nowIso() {
5598
- return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
5599
- }
5600
- var VALID_STATES, GoalStateError;
5601
- var init_state2 = __esm({
5602
- "src/goal/state.ts"() {
5603
- "use strict";
5604
- VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
5605
- GoalStateError = class extends Error {
5606
- constructor(path45, message) {
5607
- super(`Invalid goal state at ${path45}:
5608
- ${message}`);
5609
- this.path = path45;
5610
- this.name = "GoalStateError";
5611
- }
5612
- path;
5613
- };
5614
- }
5615
- });
5616
-
5617
- // src/goal/stateStore.ts
5618
- function statePath(goalId) {
5619
- return `.kody/goals/${goalId}/state.json`;
5620
- }
5621
- function is4042(err) {
5622
- const msg = err instanceof Error ? err.message : String(err);
5623
- return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
5624
- }
5625
- function fetchGoalState(owner, repo, goalId, cwd) {
5626
- const filePath = statePath(goalId);
5627
- let raw;
5628
- try {
5629
- raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
5630
- } catch (err) {
5631
- if (is4042(err)) return null;
5632
- throw err;
5633
- }
5634
- const o = JSON.parse(raw);
5635
- if (!o.content) return null;
5636
- const decoded = Buffer.from(o.content, "base64").toString("utf-8");
5637
- return parseGoalState(filePath, JSON.parse(decoded));
5638
- }
5639
- function putGoalState(owner, repo, goalId, state, message, cwd) {
5640
- ensureStateBranch(owner, repo, cwd);
5641
- const filePath = statePath(goalId);
5642
- const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
5643
- for (let attempt = 1; attempt <= 3; attempt++) {
5644
- let sha;
5645
- try {
5646
- const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
5647
- const o = JSON.parse(cur);
5648
- if (o.sha) sha = o.sha;
5649
- } catch (err) {
5650
- if (!is4042(err)) throw err;
5651
- }
5652
- const payload = { message, content, branch: STATE_BRANCH };
5653
- if (sha) payload.sha = sha;
5654
- try {
5655
- gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
5656
- cwd,
5657
- input: JSON.stringify(payload)
5658
- });
5659
- return;
5660
- } catch (err) {
5661
- const msg = err instanceof Error ? err.message : String(err);
5662
- const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
5663
- if (!conflict || attempt === 3) throw err;
5664
- }
5665
- }
5666
- }
5667
- var init_stateStore = __esm({
5668
- "src/goal/stateStore.ts"() {
5669
- "use strict";
5670
- init_issue();
5671
- init_stateBranch();
5672
- init_state2();
5673
- }
5674
- });
5675
-
5676
6210
  // src/scripts/commitGoalState.ts
5677
6211
  function describeCommitMessage(goal) {
5678
- if (goal.state === "closed") return `chore(goals): abandon ${goal.id} (cleanup complete)`;
5679
- if (goal.state === "awaiting-merge") return `chore(goals): park ${goal.id} awaiting merge`;
5680
- if (goal.state === "done") return `chore(goals): mark ${goal.id} done`;
5681
- if (goal.lastDispatchedIssue !== void 0) {
5682
- return `chore(goals): dispatched #${goal.lastDispatchedIssue} for ${goal.id}`;
5683
- }
5684
- if (goal.phase === "in-flight") {
5685
- return `chore(goals): tick ${goal.id} (waiting for in-flight task)`;
5686
- }
5687
- return `chore(goals): tick ${goal.id} (idle)`;
6212
+ if (goal.state === "done") return `chore(goals): complete ${goal.id}`;
6213
+ return `chore(goals): update ${goal.id}`;
5688
6214
  }
5689
- var commitGoalState, STATE_BRANCH_LABEL;
6215
+ var STATE_BRANCH_LABEL, commitGoalState;
5690
6216
  var init_commitGoalState = __esm({
5691
6217
  "src/scripts/commitGoalState.ts"() {
5692
6218
  "use strict";
5693
6219
  init_stateStore();
6220
+ STATE_BRANCH_LABEL = "kody-state";
5694
6221
  commitGoalState = async (ctx) => {
5695
6222
  const goal = ctx.data.goal;
5696
6223
  if (!goal) return;
@@ -5700,7 +6227,7 @@ var init_commitGoalState = __esm({
5700
6227
  const owner = ctx.config.github?.owner;
5701
6228
  const repo = ctx.config.github?.repo;
5702
6229
  if (!owner || !repo) {
5703
- process.stderr.write(`[goal-tick] commitGoalState: missing github owner/repo; cannot persist ${goal.id}
6230
+ process.stderr.write(`[goal-manager] commitGoalState: missing github owner/repo; cannot persist ${goal.id}
5704
6231
  `);
5705
6232
  return;
5706
6233
  }
@@ -5708,18 +6235,17 @@ var init_commitGoalState = __esm({
5708
6235
  putGoalState(owner, repo, goal.id, updated, describeCommitMessage(goal), ctx.cwd);
5709
6236
  } catch (err) {
5710
6237
  process.stderr.write(
5711
- `[goal-tick] commitGoalState: persist to ${STATE_BRANCH_LABEL} failed (${err instanceof Error ? err.message : String(err)}); will retry next tick
6238
+ `[goal-manager] commitGoalState: persist to ${STATE_BRANCH_LABEL} failed (${err instanceof Error ? err.message : String(err)}); will retry next tick
5712
6239
  `
5713
6240
  );
5714
6241
  }
5715
6242
  };
5716
- STATE_BRANCH_LABEL = "kody-state";
5717
6243
  }
5718
6244
  });
5719
6245
 
5720
6246
  // src/scripts/composePrompt.ts
5721
- import * as fs25 from "fs";
5722
- import * as path22 from "path";
6247
+ import * as fs26 from "fs";
6248
+ import * as path23 from "path";
5723
6249
  function fenceUntrusted(value) {
5724
6250
  if (value.trim().length === 0) return value;
5725
6251
  const safe = value.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
@@ -5840,10 +6366,10 @@ var init_composePrompt = __esm({
5840
6366
  const explicit = ctx.data.promptTemplate;
5841
6367
  const mode = ctx.args.mode;
5842
6368
  const candidates = [
5843
- explicit ? path22.join(profile.dir, explicit) : null,
5844
- mode ? path22.join(profile.dir, "prompts", `${mode}.md`) : null,
5845
- path22.join(profile.dir, "prompt.md"),
5846
- path22.join(profile.dir, "duty.md")
6369
+ explicit ? path23.join(profile.dir, explicit) : null,
6370
+ mode ? path23.join(profile.dir, "prompts", `${mode}.md`) : null,
6371
+ path23.join(profile.dir, "prompt.md"),
6372
+ path23.join(profile.dir, "duty.md")
5847
6373
  ].filter(Boolean);
5848
6374
  let templatePath = "";
5849
6375
  let template = "";
@@ -5856,7 +6382,7 @@ var init_composePrompt = __esm({
5856
6382
  break;
5857
6383
  }
5858
6384
  try {
5859
- template = fs25.readFileSync(c, "utf-8");
6385
+ template = fs26.readFileSync(c, "utf-8");
5860
6386
  templatePath = c;
5861
6387
  break;
5862
6388
  } catch (err) {
@@ -5867,7 +6393,7 @@ var init_composePrompt = __esm({
5867
6393
  if (!templatePath) {
5868
6394
  let dirState;
5869
6395
  try {
5870
- dirState = `dir contents: [${fs25.readdirSync(profile.dir).join(", ")}]`;
6396
+ dirState = `dir contents: [${fs26.readdirSync(profile.dir).join(", ")}]`;
5871
6397
  } catch (err) {
5872
6398
  dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
5873
6399
  }
@@ -6168,6 +6694,25 @@ function createTaskIssue(finding, goalId, manifestNumber, cwd) {
6168
6694
  if (!m) throw new Error(`gh issue create returned unexpected output: ${out}`);
6169
6695
  return { number: Number(m[1]), url };
6170
6696
  }
6697
+ function buildQaManagedGoal(goalId, verdict, opened, failed) {
6698
+ return {
6699
+ type: "qa",
6700
+ destination: {
6701
+ outcome: `QA findings filed for ${goalId}`,
6702
+ evidence: ["qaFindingsFiled"]
6703
+ },
6704
+ duties: ["qa-goal"],
6705
+ route: [],
6706
+ stage: "filed",
6707
+ facts: {
6708
+ qaFindingsFiled: true,
6709
+ verdict,
6710
+ findingsOpened: opened,
6711
+ findingsFailed: failed
6712
+ },
6713
+ blockers: failed > 0 ? [`${failed} QA finding issue(s) failed to open`] : []
6714
+ };
6715
+ }
6171
6716
  async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
6172
6717
  const { markdown, data, jsonError } = splitReport(finalText);
6173
6718
  const verdict = detectVerdict(markdown);
@@ -6271,7 +6816,13 @@ ${markdown}`, ctx.cwd);
6271
6816
  }
6272
6817
  }
6273
6818
  const now = nowIso();
6274
- const goalState = { state: "active", startedAt: now, updatedAt: now, extra: { version: 1 } };
6819
+ const managedGoal = buildQaManagedGoal(goalId, verdict, opened.length, failed.length);
6820
+ const goalState = {
6821
+ state: "active",
6822
+ startedAt: now,
6823
+ updatedAt: now,
6824
+ extra: { version: 1, ...managedGoal }
6825
+ };
6275
6826
  try {
6276
6827
  putGoalState(
6277
6828
  ctx.config.github.owner,
@@ -6384,279 +6935,6 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
6384
6935
  }
6385
6936
  });
6386
6937
 
6387
- // src/goal/labels.ts
6388
- function goalLabel(goalId) {
6389
- return `goal:${goalId}`;
6390
- }
6391
- var QA_GATE_LABEL;
6392
- var init_labels = __esm({
6393
- "src/goal/labels.ts"() {
6394
- "use strict";
6395
- QA_GATE_LABEL = "kody:qa-gate";
6396
- }
6397
- });
6398
-
6399
- // src/goal/operations.ts
6400
- function fail(err) {
6401
- if (err instanceof Error) {
6402
- const lines = err.message.split("\n").filter(Boolean);
6403
- return { ok: false, error: lines[0] ?? err.message };
6404
- }
6405
- return { ok: false, error: String(err) };
6406
- }
6407
- function listGoalIssues(goalId, cwd) {
6408
- try {
6409
- const out = gh(
6410
- [
6411
- "api",
6412
- `repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
6413
- "--jq",
6414
- `[.[] | select(.pull_request == null) | {number, state: (.state | ascii_upcase), isQaGate: ([.labels[].name] | any(. == "${QA_GATE_LABEL}"))}]`
6415
- ],
6416
- { cwd }
6417
- );
6418
- return { ok: true, value: JSON.parse(out) };
6419
- } catch (err) {
6420
- return fail(err);
6421
- }
6422
- }
6423
- function listOpenPrs(cwd) {
6424
- try {
6425
- const out = gh(
6426
- ["pr", "list", "--state", "open", "--limit", "200", "--json", "number,url,isDraft,headRefName,baseRefName,body"],
6427
- { cwd }
6428
- );
6429
- return { ok: true, value: JSON.parse(out) };
6430
- } catch (err) {
6431
- return fail(err);
6432
- }
6433
- }
6434
- function pairIssuesWithPrs(issues, openPrs) {
6435
- const prByIssue = /* @__PURE__ */ new Map();
6436
- for (const pr of openPrs) claimPrForIssue(pr, prByIssue);
6437
- return issues.map((i) => {
6438
- const pr = prByIssue.get(i.number);
6439
- let prState = "absent";
6440
- if (pr) prState = pr.isDraft ? "draft" : "ready";
6441
- return { number: i.number, state: i.state, prState, isQaGate: i.isQaGate };
6442
- });
6443
- }
6444
- function claimPrForIssue(pr, prByIssue) {
6445
- for (const issueNum of extractClosesIssues(pr.body)) {
6446
- if (!prByIssue.has(issueNum)) {
6447
- prByIssue.set(issueNum, pr);
6448
- return;
6449
- }
6450
- }
6451
- const headMatch = pr.headRefName.match(/^(\d+)-/);
6452
- if (headMatch) {
6453
- const n = Number.parseInt(headMatch[1], 10);
6454
- if (Number.isFinite(n) && !prByIssue.has(n)) prByIssue.set(n, pr);
6455
- }
6456
- }
6457
- function extractClosesIssues(body) {
6458
- const out = [];
6459
- const re = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/gi;
6460
- for (let m = re.exec(body); m !== null; m = re.exec(body)) {
6461
- const n = Number.parseInt(m[1], 10);
6462
- if (Number.isFinite(n)) out.push(n);
6463
- }
6464
- return out;
6465
- }
6466
- function pickLeafPr(prs) {
6467
- if (prs.length === 0) return void 0;
6468
- const bases = new Set(prs.map((p) => p.baseRefName));
6469
- const leaves = prs.filter((p) => !bases.has(p.headRefName));
6470
- return leaves.sort((a, b) => b.number - a.number)[0];
6471
- }
6472
- function commentOnIssue(issueNumber, body, cwd) {
6473
- try {
6474
- gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
6475
- return { ok: true };
6476
- } catch (err) {
6477
- return fail(err);
6478
- }
6479
- }
6480
- function dispatchTaskRun(issueNumber, base, cwd) {
6481
- try {
6482
- gh(
6483
- [
6484
- "workflow",
6485
- "run",
6486
- GOAL_TASK_WORKFLOW,
6487
- "-f",
6488
- `issue_number=${issueNumber}`,
6489
- "-f",
6490
- "duty=classify",
6491
- "-f",
6492
- `base=${base}`
6493
- ],
6494
- { cwd }
6495
- );
6496
- return { ok: true };
6497
- } catch (err) {
6498
- return fail(err);
6499
- }
6500
- }
6501
- function closeIssue(issueNumber, options, cwd) {
6502
- try {
6503
- if (options.comment) {
6504
- gh(["issue", "comment", String(issueNumber), "--body", options.comment], { cwd });
6505
- }
6506
- const args = ["issue", "close", String(issueNumber)];
6507
- if (options.reason) args.push("--reason", options.reason);
6508
- gh(args, { cwd });
6509
- return { ok: true };
6510
- } catch (err) {
6511
- return fail(err);
6512
- }
6513
- }
6514
- function closePr(prNumber, comment, cwd) {
6515
- try {
6516
- gh(["pr", "close", String(prNumber), "--comment", comment], { cwd });
6517
- return { ok: true };
6518
- } catch (err) {
6519
- return fail(err);
6520
- }
6521
- }
6522
- function mergePrSquash(prNumber, cwd) {
6523
- try {
6524
- gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
6525
- return { ok: true };
6526
- } catch (err) {
6527
- return fail(err);
6528
- }
6529
- }
6530
- function editPrBase(prNumber, baseBranch, cwd) {
6531
- try {
6532
- gh(["api", "--method", "PATCH", `repos/{owner}/{repo}/pulls/${prNumber}`, "-f", `base=${baseBranch}`], { cwd });
6533
- return { ok: true };
6534
- } catch (err) {
6535
- return fail(err);
6536
- }
6537
- }
6538
- function branchContains(leafHead, candidateHead, cwd) {
6539
- if (leafHead === candidateHead) return { ok: true, value: true };
6540
- try {
6541
- const out = gh(
6542
- [
6543
- "api",
6544
- `repos/{owner}/{repo}/compare/${encodeURIComponent(leafHead)}...${encodeURIComponent(candidateHead)}`,
6545
- "--jq",
6546
- ".ahead_by"
6547
- ],
6548
- { cwd }
6549
- );
6550
- return { ok: true, value: Number.parseInt(out.trim(), 10) === 0 };
6551
- } catch (err) {
6552
- return fail(err);
6553
- }
6554
- }
6555
- function markPrReady(prNumber, cwd) {
6556
- try {
6557
- gh(["pr", "ready", String(prNumber)], { cwd });
6558
- return { ok: true };
6559
- } catch (err) {
6560
- return fail(err);
6561
- }
6562
- }
6563
- var GOAL_TASK_WORKFLOW;
6564
- var init_operations = __esm({
6565
- "src/goal/operations.ts"() {
6566
- "use strict";
6567
- init_issue();
6568
- init_labels();
6569
- GOAL_TASK_WORKFLOW = "kody.yml";
6570
- }
6571
- });
6572
-
6573
- // src/goal/phase.ts
6574
- function derivePhase(snap) {
6575
- if (!snap.lifecycleState) return "missing";
6576
- if (snap.lifecycleState === "abandoned") return "abandoned";
6577
- if (snap.lifecycleState === "closed" || snap.lifecycleState === "done") return "terminal";
6578
- if (snap.lifecycleState === "awaiting-merge") return "awaiting-merge";
6579
- const tasks = snap.childTasks.filter((t) => !t.isQaGate);
6580
- const qaGateOpen = snap.childTasks.some((t) => t.isQaGate && t.state === "OPEN");
6581
- const hasInFlight = tasks.some((t) => t.state === "OPEN" && t.prState === "draft");
6582
- if (hasInFlight) return "in-flight";
6583
- if (tasks.length === 0) return "idle";
6584
- const dispatchable = tasks.some((t) => t.state === "OPEN" && t.prState === "absent");
6585
- if (dispatchable) return "ready-to-dispatch";
6586
- const allDone = tasks.every((t) => t.state === "CLOSED" || t.prState === "ready");
6587
- if (allDone) return qaGateOpen ? "idle" : "all-done";
6588
- return "idle";
6589
- }
6590
- function pickNextDispatchable(snap) {
6591
- return snap.childTasks.filter((t) => !t.isQaGate && t.state === "OPEN" && t.prState === "absent").sort((a, b) => a.number - b.number)[0];
6592
- }
6593
- var init_phase = __esm({
6594
- "src/goal/phase.ts"() {
6595
- "use strict";
6596
- }
6597
- });
6598
-
6599
- // src/scripts/deriveGoalPhase.ts
6600
- function filterGoalTaskPrs(prs, taskIssueNumbers) {
6601
- const taskSet = new Set(taskIssueNumbers);
6602
- return prs.filter((pr) => {
6603
- for (const n of extractClosesIssues(pr.body)) {
6604
- if (taskSet.has(n)) return true;
6605
- }
6606
- const headMatch = pr.headRefName.match(/^(\d+)-/);
6607
- if (headMatch) {
6608
- const n = Number.parseInt(headMatch[1], 10);
6609
- if (Number.isFinite(n) && taskSet.has(n)) return true;
6610
- }
6611
- return false;
6612
- });
6613
- }
6614
- var deriveGoalPhase;
6615
- var init_deriveGoalPhase = __esm({
6616
- "src/scripts/deriveGoalPhase.ts"() {
6617
- "use strict";
6618
- init_operations();
6619
- init_phase();
6620
- deriveGoalPhase = async (ctx) => {
6621
- const goal = ctx.data.goal;
6622
- if (!goal) return;
6623
- const issues = listGoalIssues(goal.id, ctx.cwd);
6624
- if (!issues.ok) {
6625
- process.stderr.write(`[goal-tick] deriveGoalPhase: list issues failed: ${issues.error}
6626
- `);
6627
- goal.childTasks = [];
6628
- goal.openTaskPrs = [];
6629
- goal.phase = "idle";
6630
- return;
6631
- }
6632
- const rawIssues = issues.value ?? [];
6633
- const allPrs = listOpenPrs(ctx.cwd);
6634
- if (!allPrs.ok) {
6635
- process.stderr.write(`[goal-tick] deriveGoalPhase: list PRs failed: ${allPrs.error}
6636
- `);
6637
- goal.childTasks = rawIssues.map((i) => ({ ...i, prState: "absent" }));
6638
- goal.openTaskPrs = [];
6639
- goal.phase = "idle";
6640
- return;
6641
- }
6642
- const taskPrs = filterGoalTaskPrs(
6643
- allPrs.value ?? [],
6644
- rawIssues.map((i) => i.number)
6645
- );
6646
- goal.openTaskPrs = taskPrs;
6647
- goal.leafPr = pickLeafPr(taskPrs);
6648
- goal.childTasks = pairIssuesWithPrs(rawIssues, taskPrs);
6649
- goal.phase = derivePhase({
6650
- lifecycleState: goal.state,
6651
- childTasks: goal.childTasks
6652
- });
6653
- process.stdout.write(
6654
- `[goal-tick] phase=${goal.phase} goal=${goal.id} tasks=${rawIssues.length} stack=${taskPrs.length}` + (goal.leafPr ? ` leaf=#${goal.leafPr.number}` : "") + "\n"
6655
- );
6656
- };
6657
- }
6658
- });
6659
-
6660
6938
  // src/scripts/deriveQaScopeFromIssue.ts
6661
6939
  var TITLE_PATTERN, deriveQaScopeFromIssue;
6662
6940
  var init_deriveQaScopeFromIssue = __esm({
@@ -6693,20 +6971,20 @@ var init_deriveQaScopeFromIssue = __esm({
6693
6971
  });
6694
6972
 
6695
6973
  // src/scripts/diagMcp.ts
6696
- import { execFileSync as execFileSync10 } from "child_process";
6697
- import * as fs26 from "fs";
6698
- import * as os4 from "os";
6699
- import * as path23 from "path";
6974
+ import { execFileSync as execFileSync11 } from "child_process";
6975
+ import * as fs27 from "fs";
6976
+ import * as os5 from "os";
6977
+ import * as path24 from "path";
6700
6978
  var diagMcp;
6701
6979
  var init_diagMcp = __esm({
6702
6980
  "src/scripts/diagMcp.ts"() {
6703
6981
  "use strict";
6704
6982
  diagMcp = async (_ctx) => {
6705
- const home = os4.homedir();
6706
- const cacheDir = path23.join(home, ".cache", "ms-playwright");
6983
+ const home = os5.homedir();
6984
+ const cacheDir = path24.join(home, ".cache", "ms-playwright");
6707
6985
  let entries = [];
6708
6986
  try {
6709
- entries = fs26.readdirSync(cacheDir);
6987
+ entries = fs27.readdirSync(cacheDir);
6710
6988
  } catch {
6711
6989
  }
6712
6990
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -6717,7 +6995,7 @@ var init_diagMcp = __esm({
6717
6995
  process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
6718
6996
  `);
6719
6997
  try {
6720
- const v = execFileSync10("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
6998
+ const v = execFileSync11("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
6721
6999
  stdio: "pipe",
6722
7000
  timeout: 6e4,
6723
7001
  encoding: "utf8"
@@ -6734,13 +7012,13 @@ var init_diagMcp = __esm({
6734
7012
  });
6735
7013
 
6736
7014
  // src/scripts/frameworkDetectors.ts
6737
- import * as fs27 from "fs";
6738
- import * as path24 from "path";
7015
+ import * as fs28 from "fs";
7016
+ import * as path25 from "path";
6739
7017
  function detectFrameworks(cwd) {
6740
7018
  const out = [];
6741
7019
  let deps = {};
6742
7020
  try {
6743
- const pkg = JSON.parse(fs27.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
7021
+ const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
6744
7022
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
6745
7023
  } catch {
6746
7024
  return out;
@@ -6777,25 +7055,25 @@ function detectFrameworks(cwd) {
6777
7055
  }
6778
7056
  function findFile(cwd, candidates) {
6779
7057
  for (const c of candidates) {
6780
- if (fs27.existsSync(path24.join(cwd, c))) return c;
7058
+ if (fs28.existsSync(path25.join(cwd, c))) return c;
6781
7059
  }
6782
7060
  return null;
6783
7061
  }
6784
7062
  function discoverPayloadCollections(cwd) {
6785
7063
  const out = [];
6786
7064
  for (const dir of COLLECTION_DIRS) {
6787
- const full = path24.join(cwd, dir);
6788
- if (!fs27.existsSync(full)) continue;
7065
+ const full = path25.join(cwd, dir);
7066
+ if (!fs28.existsSync(full)) continue;
6789
7067
  let files;
6790
7068
  try {
6791
- files = fs27.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7069
+ files = fs28.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6792
7070
  } catch {
6793
7071
  continue;
6794
7072
  }
6795
7073
  for (const file of files) {
6796
7074
  try {
6797
- const filePath = path24.join(full, file);
6798
- const content = fs27.readFileSync(filePath, "utf-8").slice(0, 1e4);
7075
+ const filePath = path25.join(full, file);
7076
+ const content = fs28.readFileSync(filePath, "utf-8").slice(0, 1e4);
6799
7077
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
6800
7078
  if (!slugMatch) continue;
6801
7079
  const slug = slugMatch[1];
@@ -6809,7 +7087,7 @@ function discoverPayloadCollections(cwd) {
6809
7087
  out.push({
6810
7088
  name,
6811
7089
  slug,
6812
- filePath: path24.relative(cwd, filePath),
7090
+ filePath: path25.relative(cwd, filePath),
6813
7091
  fields: fields.slice(0, 20),
6814
7092
  hasAdmin
6815
7093
  });
@@ -6822,28 +7100,28 @@ function discoverPayloadCollections(cwd) {
6822
7100
  function discoverAdminComponents(cwd, collections) {
6823
7101
  const out = [];
6824
7102
  for (const dir of ADMIN_COMPONENT_DIRS) {
6825
- const full = path24.join(cwd, dir);
6826
- if (!fs27.existsSync(full)) continue;
7103
+ const full = path25.join(cwd, dir);
7104
+ if (!fs28.existsSync(full)) continue;
6827
7105
  let entries;
6828
7106
  try {
6829
- entries = fs27.readdirSync(full, { withFileTypes: true });
7107
+ entries = fs28.readdirSync(full, { withFileTypes: true });
6830
7108
  } catch {
6831
7109
  continue;
6832
7110
  }
6833
7111
  for (const entry of entries) {
6834
- const entryPath = path24.join(full, entry.name);
7112
+ const entryPath = path25.join(full, entry.name);
6835
7113
  let name;
6836
7114
  let filePath;
6837
7115
  if (entry.isDirectory()) {
6838
7116
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
6839
- (f) => fs27.existsSync(path24.join(entryPath, f))
7117
+ (f) => fs28.existsSync(path25.join(entryPath, f))
6840
7118
  );
6841
7119
  if (!indexFile) continue;
6842
7120
  name = entry.name;
6843
- filePath = path24.relative(cwd, path24.join(entryPath, indexFile));
7121
+ filePath = path25.relative(cwd, path25.join(entryPath, indexFile));
6844
7122
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
6845
7123
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
6846
- filePath = path24.relative(cwd, entryPath);
7124
+ filePath = path25.relative(cwd, entryPath);
6847
7125
  } else {
6848
7126
  continue;
6849
7127
  }
@@ -6851,7 +7129,7 @@ function discoverAdminComponents(cwd, collections) {
6851
7129
  if (collections) {
6852
7130
  for (const col of collections) {
6853
7131
  try {
6854
- const colContent = fs27.readFileSync(path24.join(cwd, col.filePath), "utf-8");
7132
+ const colContent = fs28.readFileSync(path25.join(cwd, col.filePath), "utf-8");
6855
7133
  if (colContent.includes(name)) {
6856
7134
  usedInCollection = col.slug;
6857
7135
  break;
@@ -6869,8 +7147,8 @@ function scanApiRoutes(cwd) {
6869
7147
  const out = [];
6870
7148
  const appDirs = ["src/app", "app"];
6871
7149
  for (const appDir of appDirs) {
6872
- const apiDir = path24.join(cwd, appDir, "api");
6873
- if (!fs27.existsSync(apiDir)) continue;
7150
+ const apiDir = path25.join(cwd, appDir, "api");
7151
+ if (!fs28.existsSync(apiDir)) continue;
6874
7152
  walkApiRoutes(apiDir, "/api", cwd, out);
6875
7153
  break;
6876
7154
  }
@@ -6879,14 +7157,14 @@ function scanApiRoutes(cwd) {
6879
7157
  function walkApiRoutes(dir, prefix, cwd, out) {
6880
7158
  let entries;
6881
7159
  try {
6882
- entries = fs27.readdirSync(dir, { withFileTypes: true });
7160
+ entries = fs28.readdirSync(dir, { withFileTypes: true });
6883
7161
  } catch {
6884
7162
  return;
6885
7163
  }
6886
7164
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
6887
7165
  if (routeFile) {
6888
7166
  try {
6889
- const content = fs27.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
7167
+ const content = fs28.readFileSync(path25.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
6890
7168
  const methods = HTTP_METHODS.filter(
6891
7169
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
6892
7170
  );
@@ -6894,7 +7172,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6894
7172
  out.push({
6895
7173
  path: prefix,
6896
7174
  methods,
6897
- filePath: path24.relative(cwd, path24.join(dir, routeFile.name))
7175
+ filePath: path25.relative(cwd, path25.join(dir, routeFile.name))
6898
7176
  });
6899
7177
  }
6900
7178
  } catch {
@@ -6905,7 +7183,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6905
7183
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6906
7184
  let segment = entry.name;
6907
7185
  if (segment.startsWith("(") && segment.endsWith(")")) {
6908
- walkApiRoutes(path24.join(dir, entry.name), prefix, cwd, out);
7186
+ walkApiRoutes(path25.join(dir, entry.name), prefix, cwd, out);
6909
7187
  continue;
6910
7188
  }
6911
7189
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -6913,16 +7191,16 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6913
7191
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
6914
7192
  segment = `:${segment.slice(1, -1)}`;
6915
7193
  }
6916
- walkApiRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
7194
+ walkApiRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
6917
7195
  }
6918
7196
  }
6919
7197
  function scanEnvVars(cwd) {
6920
7198
  const candidates = [".env.example", ".env.local.example", ".env.template"];
6921
7199
  for (const envFile of candidates) {
6922
- const envPath = path24.join(cwd, envFile);
6923
- if (!fs27.existsSync(envPath)) continue;
7200
+ const envPath = path25.join(cwd, envFile);
7201
+ if (!fs28.existsSync(envPath)) continue;
6924
7202
  try {
6925
- const content = fs27.readFileSync(envPath, "utf-8");
7203
+ const content = fs28.readFileSync(envPath, "utf-8");
6926
7204
  const vars = [];
6927
7205
  for (const line of content.split("\n")) {
6928
7206
  const trimmed = line.trim();
@@ -6967,8 +7245,8 @@ var init_frameworkDetectors = __esm({
6967
7245
  });
6968
7246
 
6969
7247
  // src/scripts/discoverQaContext.ts
6970
- import * as fs28 from "fs";
6971
- import * as path25 from "path";
7248
+ import * as fs29 from "fs";
7249
+ import * as path26 from "path";
6972
7250
  function runQaDiscovery(cwd) {
6973
7251
  const out = {
6974
7252
  routes: [],
@@ -6999,9 +7277,9 @@ function runQaDiscovery(cwd) {
6999
7277
  }
7000
7278
  function detectDevServer(cwd, out) {
7001
7279
  try {
7002
- const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
7280
+ const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
7003
7281
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
7004
- 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";
7282
+ 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";
7005
7283
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
7006
7284
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
7007
7285
  else if (allDeps.vite) out.devPort = 5173;
@@ -7011,8 +7289,8 @@ function detectDevServer(cwd, out) {
7011
7289
  function scanFrontendRoutes(cwd, out) {
7012
7290
  const appDirs = ["src/app", "app"];
7013
7291
  for (const appDir of appDirs) {
7014
- const full = path25.join(cwd, appDir);
7015
- if (!fs28.existsSync(full)) continue;
7292
+ const full = path26.join(cwd, appDir);
7293
+ if (!fs29.existsSync(full)) continue;
7016
7294
  walkFrontendRoutes(full, "", out);
7017
7295
  break;
7018
7296
  }
@@ -7020,7 +7298,7 @@ function scanFrontendRoutes(cwd, out) {
7020
7298
  function walkFrontendRoutes(dir, prefix, out) {
7021
7299
  let entries;
7022
7300
  try {
7023
- entries = fs28.readdirSync(dir, { withFileTypes: true });
7301
+ entries = fs29.readdirSync(dir, { withFileTypes: true });
7024
7302
  } catch {
7025
7303
  return;
7026
7304
  }
@@ -7037,7 +7315,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7037
7315
  if (entry.name === "node_modules" || entry.name === ".next") continue;
7038
7316
  let segment = entry.name;
7039
7317
  if (segment.startsWith("(") && segment.endsWith(")")) {
7040
- walkFrontendRoutes(path25.join(dir, entry.name), prefix, out);
7318
+ walkFrontendRoutes(path26.join(dir, entry.name), prefix, out);
7041
7319
  continue;
7042
7320
  }
7043
7321
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -7045,7 +7323,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7045
7323
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
7046
7324
  segment = `:${segment.slice(1, -1)}`;
7047
7325
  }
7048
- walkFrontendRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, out);
7326
+ walkFrontendRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, out);
7049
7327
  }
7050
7328
  }
7051
7329
  function detectAuthFiles(cwd, out) {
@@ -7062,23 +7340,23 @@ function detectAuthFiles(cwd, out) {
7062
7340
  "src/app/api/oauth"
7063
7341
  ];
7064
7342
  for (const c of candidates) {
7065
- if (fs28.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
7343
+ if (fs29.existsSync(path26.join(cwd, c))) out.authFiles.push(c);
7066
7344
  }
7067
7345
  }
7068
7346
  function detectRoles(cwd, out) {
7069
7347
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
7070
7348
  for (const rp of rolePaths) {
7071
- const dir = path25.join(cwd, rp);
7072
- if (!fs28.existsSync(dir)) continue;
7349
+ const dir = path26.join(cwd, rp);
7350
+ if (!fs29.existsSync(dir)) continue;
7073
7351
  let files;
7074
7352
  try {
7075
- files = fs28.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7353
+ files = fs29.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7076
7354
  } catch {
7077
7355
  continue;
7078
7356
  }
7079
7357
  for (const f of files) {
7080
7358
  try {
7081
- const content = fs28.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
7359
+ const content = fs29.readFileSync(path26.join(dir, f), "utf-8").slice(0, 5e3);
7082
7360
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
7083
7361
  if (roleMatches) {
7084
7362
  for (const m of roleMatches) {
@@ -7221,7 +7499,7 @@ var init_dispatch = __esm({
7221
7499
  });
7222
7500
 
7223
7501
  // src/scripts/dispatchClassified.ts
7224
- import { execFileSync as execFileSync11 } from "child_process";
7502
+ import { execFileSync as execFileSync12 } from "child_process";
7225
7503
  var API_TIMEOUT_MS4, VALID_CLASSES2, dispatchClassified;
7226
7504
  var init_dispatchClassified = __esm({
7227
7505
  "src/scripts/dispatchClassified.ts"() {
@@ -7251,7 +7529,7 @@ ${stateBody}`;
7251
7529
  try {
7252
7530
  const existing = findStateComment("issue", issueNumber, ctx.cwd);
7253
7531
  if (existing) {
7254
- execFileSync11(
7532
+ execFileSync12(
7255
7533
  "gh",
7256
7534
  ["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"],
7257
7535
  {
@@ -7262,7 +7540,7 @@ ${stateBody}`;
7262
7540
  }
7263
7541
  );
7264
7542
  } else {
7265
- execFileSync11("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7543
+ execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7266
7544
  cwd: ctx.cwd,
7267
7545
  timeout: API_TIMEOUT_MS4,
7268
7546
  stdio: ["ignore", "pipe", "pipe"]
@@ -7504,8 +7782,8 @@ var init_contentsApiBackend = __esm({
7504
7782
  });
7505
7783
 
7506
7784
  // src/scripts/jobState/localFileBackend.ts
7507
- import * as fs29 from "fs";
7508
- import * as path26 from "path";
7785
+ import * as fs30 from "fs";
7786
+ import * as path27 from "path";
7509
7787
  function sanitizeKey(s) {
7510
7788
  return s.replace(/[^A-Za-z0-9._-]/g, "-");
7511
7789
  }
@@ -7561,7 +7839,7 @@ var init_localFileBackend = __esm({
7561
7839
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
7562
7840
  this.cwd = opts.cwd;
7563
7841
  this.jobsDir = opts.jobsDir;
7564
- this.absDir = path26.join(opts.cwd, opts.jobsDir);
7842
+ this.absDir = path27.join(opts.cwd, opts.jobsDir);
7565
7843
  this.owner = opts.owner;
7566
7844
  this.repo = opts.repo;
7567
7845
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -7576,7 +7854,7 @@ var init_localFileBackend = __esm({
7576
7854
  `);
7577
7855
  return;
7578
7856
  }
7579
- fs29.mkdirSync(this.absDir, { recursive: true });
7857
+ fs30.mkdirSync(this.absDir, { recursive: true });
7580
7858
  const prefix = this.cacheKeyPrefix();
7581
7859
  const probeKey = `${prefix}probe-${Date.now()}`;
7582
7860
  try {
@@ -7605,7 +7883,7 @@ var init_localFileBackend = __esm({
7605
7883
  `);
7606
7884
  return;
7607
7885
  }
7608
- if (!fs29.existsSync(this.absDir)) {
7886
+ if (!fs30.existsSync(this.absDir)) {
7609
7887
  return;
7610
7888
  }
7611
7889
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -7621,11 +7899,11 @@ var init_localFileBackend = __esm({
7621
7899
  }
7622
7900
  load(slug) {
7623
7901
  const relPath = stateFilePath(this.jobsDir, slug);
7624
- const absPath = path26.join(this.cwd, relPath);
7625
- if (!fs29.existsSync(absPath)) {
7902
+ const absPath = path27.join(this.cwd, relPath);
7903
+ if (!fs30.existsSync(absPath)) {
7626
7904
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
7627
7905
  }
7628
- const raw = fs29.readFileSync(absPath, "utf-8");
7906
+ const raw = fs30.readFileSync(absPath, "utf-8");
7629
7907
  let parsed;
7630
7908
  try {
7631
7909
  parsed = JSON.parse(raw);
@@ -7642,13 +7920,13 @@ var init_localFileBackend = __esm({
7642
7920
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
7643
7921
  return false;
7644
7922
  }
7645
- const absPath = path26.join(this.cwd, loaded.path);
7646
- fs29.mkdirSync(path26.dirname(absPath), { recursive: true });
7923
+ const absPath = path27.join(this.cwd, loaded.path);
7924
+ fs30.mkdirSync(path27.dirname(absPath), { recursive: true });
7647
7925
  const body = `${JSON.stringify(next, null, 2)}
7648
7926
  `;
7649
7927
  const tmpPath = `${absPath}.${process.pid}.tmp`;
7650
- fs29.writeFileSync(tmpPath, body, "utf-8");
7651
- fs29.renameSync(tmpPath, absPath);
7928
+ fs30.writeFileSync(tmpPath, body, "utf-8");
7929
+ fs30.renameSync(tmpPath, absPath);
7652
7930
  return true;
7653
7931
  }
7654
7932
  cacheKeyPrefix() {
@@ -7823,7 +8101,7 @@ var init_planTaskJobs = __esm({
7823
8101
  });
7824
8102
 
7825
8103
  // src/scripts/dispatchDutyFileTicks.ts
7826
- import * as path27 from "path";
8104
+ import * as path28 from "path";
7827
8105
  async function decideShouldFire(every, slug, backend, now) {
7828
8106
  if (!every) return { skip: false, reason: "no schedule (every cron tick)" };
7829
8107
  if (every === "manual") {
@@ -7870,6 +8148,24 @@ function parseDutyFilter(raw) {
7870
8148
  function filterSlugs(slugs, onlyDuty) {
7871
8149
  return onlyDuty ? slugs.filter((slug) => slug === onlyDuty) : slugs;
7872
8150
  }
8151
+ function listActivatedDutySlugs(projectRoot, storeRoot, activeStoreDuties) {
8152
+ const seen = /* @__PURE__ */ new Set();
8153
+ const out = [];
8154
+ for (const slug of listDutyFolderSlugs(projectRoot)) {
8155
+ if (seen.has(slug)) continue;
8156
+ seen.add(slug);
8157
+ out.push(slug);
8158
+ }
8159
+ const active = new Set(activeStoreDuties ?? []);
8160
+ if (storeRoot && active.size > 0) {
8161
+ for (const slug of listDutyFolderSlugs(storeRoot)) {
8162
+ if (!active.has(slug) || seen.has(slug)) continue;
8163
+ seen.add(slug);
8164
+ out.push(slug);
8165
+ }
8166
+ }
8167
+ return out.sort();
8168
+ }
7873
8169
  function createDutyTaskIssue(opts) {
7874
8170
  const title = `Duty ${opts.slug} - multi-executable task`;
7875
8171
  const body = buildDutyTaskIssueBody(opts.slug, opts.body, opts.config);
@@ -7920,6 +8216,7 @@ var init_dispatchDutyFileTicks = __esm({
7920
8216
  init_dutyFolders();
7921
8217
  init_issue();
7922
8218
  init_job();
8219
+ init_registry();
7923
8220
  init_jobState();
7924
8221
  init_planTaskJobs();
7925
8222
  init_scheduleEvery();
@@ -7938,8 +8235,12 @@ var init_dispatchDutyFileTicks = __esm({
7938
8235
  }
7939
8236
  try {
7940
8237
  const onlyDuty = parseDutyFilter(ctx.args.duty);
7941
- const jobsPath = path27.join(ctx.cwd, jobsDir);
7942
- const slugs = filterSlugs(listDutyFolderSlugs(jobsPath), onlyDuty);
8238
+ const jobsPath = path28.join(ctx.cwd, jobsDir);
8239
+ const storeJobsPath = getCompanyStoreDutiesRoot();
8240
+ const slugs = filterSlugs(
8241
+ listActivatedDutySlugs(jobsPath, storeJobsPath, ctx.config.company?.activeDuties),
8242
+ onlyDuty
8243
+ );
7943
8244
  ctx.data.jobSlugCount = slugs.length;
7944
8245
  if (slugs.length === 0) {
7945
8246
  const filter = onlyDuty ? ` matching ${onlyDuty}` : "";
@@ -7953,7 +8254,7 @@ var init_dispatchDutyFileTicks = __esm({
7953
8254
  const results = [];
7954
8255
  const now = Date.now();
7955
8256
  for (const slug of slugs) {
7956
- const duty = readDutyFolder(jobsPath, slug);
8257
+ const duty = resolveDutyFolder(slug, jobsPath);
7957
8258
  if (!duty) {
7958
8259
  process.stderr.write(`[jobs] \u23ED skip ${slug}: duty folder is missing profile.json or duty.md
7959
8260
  `);
@@ -8130,38 +8431,6 @@ var init_dispatchDutyTicks = __esm({
8130
8431
  }
8131
8432
  });
8132
8433
 
8133
- // src/scripts/dispatchNextTask.ts
8134
- var dispatchNextTask;
8135
- var init_dispatchNextTask = __esm({
8136
- "src/scripts/dispatchNextTask.ts"() {
8137
- "use strict";
8138
- init_operations();
8139
- init_phase();
8140
- dispatchNextTask = async (ctx) => {
8141
- const goal = ctx.data.goal;
8142
- if (!goal?.childTasks) return;
8143
- const next = pickNextDispatchable({
8144
- lifecycleState: goal.state,
8145
- childTasks: goal.childTasks
8146
- });
8147
- if (!next) {
8148
- process.stdout.write("[goal-tick] no dispatchable task \u2014 idle\n");
8149
- return;
8150
- }
8151
- const base = goal.leafPr?.headRefName ?? goal.defaultBranch;
8152
- process.stdout.write(`[goal-tick] dispatching #${next.number} via workflow_dispatch (classify, --base ${base})
8153
- `);
8154
- const res = dispatchTaskRun(next.number, base, ctx.cwd);
8155
- if (!res.ok) {
8156
- process.stderr.write(`[goal-tick] dispatchNextTask: workflow_dispatch on #${next.number} failed: ${res.error}
8157
- `);
8158
- return;
8159
- }
8160
- goal.lastDispatchedIssue = next.number;
8161
- };
8162
- }
8163
- });
8164
-
8165
8434
  // src/scripts/dispatchNextTaskJob.ts
8166
8435
  function taskJobToJob(job, issueArg) {
8167
8436
  const target = typeof job.target === "number" ? job.target : typeof issueArg === "number" ? issueArg : void 0;
@@ -8206,7 +8475,7 @@ var init_dispatchNextTaskJob = __esm({
8206
8475
  });
8207
8476
 
8208
8477
  // src/pr.ts
8209
- import { execFileSync as execFileSync12 } from "child_process";
8478
+ import { execFileSync as execFileSync13 } from "child_process";
8210
8479
  function prMergeStatus(prNumber, cwd) {
8211
8480
  try {
8212
8481
  const out = gh(["pr", "view", String(prNumber), "--json", "mergeable,mergeStateStatus"], { cwd });
@@ -8327,7 +8596,7 @@ function isAlreadyExistsError(err) {
8327
8596
  return ALREADY_EXISTS_RE.test(msg);
8328
8597
  }
8329
8598
  function git2(args, cwd) {
8330
- return execFileSync12("git", args, {
8599
+ return execFileSync13("git", args, {
8331
8600
  encoding: "utf-8",
8332
8601
  timeout: 3e4,
8333
8602
  cwd,
@@ -8528,140 +8797,13 @@ var init_failOnceTaskJob = __esm({
8528
8797
  const state = ctx.data.taskState;
8529
8798
  const runs = state?.jobs?.[jobKey]?.runs ?? [];
8530
8799
  const hasFailedBefore = runs.some((run) => run.status === "failed");
8531
- if (!hasFailedBefore) {
8532
- ctx.output.exitCode = 1;
8533
- ctx.output.reason = "intentional first-attempt failure for task job live test";
8534
- return;
8535
- }
8536
- ctx.output.exitCode = 0;
8537
- ctx.output.reason = "task job live test succeeded after prior failure";
8538
- };
8539
- }
8540
- });
8541
-
8542
- // src/scripts/finalizeGoal.ts
8543
- function prIssueNumbers(pr) {
8544
- const nums = new Set(extractClosesIssues(pr.body));
8545
- const headMatch = pr.headRefName.match(/^(\d+)-/);
8546
- if (headMatch) {
8547
- const n = Number.parseInt(headMatch[1], 10);
8548
- if (Number.isFinite(n)) nums.add(n);
8549
- }
8550
- return [...nums];
8551
- }
8552
- var finalizeGoal;
8553
- var init_finalizeGoal = __esm({
8554
- "src/scripts/finalizeGoal.ts"() {
8555
- "use strict";
8556
- init_operations();
8557
- finalizeGoal = async (ctx) => {
8558
- const goal = ctx.data.goal;
8559
- if (!goal) return;
8560
- process.stdout.write(`[goal-tick] all task(s) done \u2014 preparing deliverable PR for goal ${goal.id}
8561
- `);
8562
- const leaf = goal.leafPr;
8563
- if (!leaf) {
8564
- process.stdout.write(`[goal-tick] no leaf PR \u2014 marking goal done without a deliverable PR
8565
- `);
8566
- goal.state = "done";
8567
- return;
8568
- }
8569
- if (leaf.baseRefName !== goal.defaultBranch) {
8570
- process.stdout.write(
8571
- `[goal-tick] retargeting leaf PR #${leaf.number} base ${leaf.baseRefName} \u2192 ${goal.defaultBranch}
8572
- `
8573
- );
8574
- const retarget = editPrBase(leaf.number, goal.defaultBranch, ctx.cwd);
8575
- if (!retarget.ok) {
8576
- process.stderr.write(`[goal-tick] finalizeGoal: editPrBase #${leaf.number} failed: ${retarget.error}
8577
- `);
8578
- return;
8579
- }
8580
- }
8581
- if (leaf.isDraft) {
8582
- process.stdout.write(`[goal-tick] promoting draft leaf PR #${leaf.number} \u2192 ready-for-review
8583
- `);
8584
- const ready = markPrReady(leaf.number, ctx.cwd);
8585
- if (!ready.ok) {
8586
- process.stderr.write(`[goal-tick] finalizeGoal: markPrReady #${leaf.number} failed: ${ready.error}
8587
- `);
8588
- return;
8589
- }
8590
- }
8591
- process.stdout.write(
8592
- `[goal-tick] leaf PR #${leaf.number} is the deliverable (cumulative goal diff vs ${goal.defaultBranch}) \u2014 left open for human merge
8593
- `
8594
- );
8595
- const uncarriedIssues = /* @__PURE__ */ new Set();
8596
- const others = (goal.openTaskPrs ?? []).filter((p) => p.number !== leaf.number);
8597
- for (const pr of others) {
8598
- const contained = branchContains(leaf.headRefName, pr.headRefName, ctx.cwd);
8599
- if (!contained.ok || contained.value !== true) {
8600
- const why = contained.ok ? `commits on \`${pr.headRefName}\` are NOT reachable from the deliverable leaf \`${leaf.headRefName}\`` : `could not verify containment (${contained.error})`;
8601
- process.stderr.write(
8602
- `[goal-tick] finalizeGoal: NOT closing PR #${pr.number} \u2014 ${why}; leaving it open (broken stack)
8603
- `
8604
- );
8605
- for (const n of prIssueNumbers(pr)) uncarriedIssues.add(n);
8606
- commentOnIssue(
8607
- pr.number,
8608
- `\u26A0\uFE0F _Stacked-PR finalize: this PR's commits are **not** carried by the goal's deliverable PR #${leaf.number} (the stack chain was broken). Leaving this PR open so its work isn't lost \u2014 review and land it manually._`,
8609
- ctx.cwd
8610
- );
8611
- continue;
8612
- }
8613
- process.stdout.write(`[goal-tick] closing intermediate stacked PR #${pr.number} (carried by deliverable leaf)
8614
- `);
8615
- const closed = closePr(
8616
- pr.number,
8617
- `_Stacked-PR finalize: this PR's content is carried by the goal's single deliverable PR #${leaf.number} (open against \`${goal.defaultBranch}\`, awaiting human merge)._`,
8618
- ctx.cwd
8619
- );
8620
- if (!closed.ok) {
8621
- process.stderr.write(`[goal-tick] finalizeGoal: closePr #${pr.number} failed: ${closed.error}
8622
- `);
8623
- }
8624
- }
8625
- const leafIssues = new Set(prIssueNumbers(leaf));
8626
- const openIssues = (goal.childTasks ?? []).filter((t) => t.state === "OPEN");
8627
- for (const t of openIssues) {
8628
- if (uncarriedIssues.has(t.number)) {
8629
- process.stderr.write(
8630
- `[goal-tick] finalizeGoal: NOT closing task issue #${t.number} \u2014 its PR's work is not carried by the deliverable (broken stack)
8631
- `
8632
- );
8633
- continue;
8634
- }
8635
- if (leafIssues.has(t.number)) {
8636
- process.stdout.write(
8637
- `[goal-tick] leaving leaf task issue #${t.number} OPEN as the review anchor for deliverable PR #${leaf.number}
8638
- `
8639
- );
8640
- commentOnIssue(
8641
- t.number,
8642
- `_Goal \`${goal.id}\` finalized \u2014 this task's PR #${leaf.number} (open against \`${goal.defaultBranch}\`) is the goal's single deliverable and carries every task's changes. This issue stays open as the review anchor; merge PR #${leaf.number} to ship._`,
8643
- ctx.cwd
8644
- );
8645
- continue;
8646
- }
8647
- process.stdout.write(
8648
- `[goal-tick] closing task issue #${t.number} (goal finalized \u2014 carried by PR #${leaf.number})
8649
- `
8650
- );
8651
- const closed = closeIssue(
8652
- t.number,
8653
- {
8654
- comment: `_Goal \`${goal.id}\` finalized \u2014 its single deliverable PR #${leaf.number} (open against \`${goal.defaultBranch}\`) carries this task's changes. Merge that PR to ship._`,
8655
- reason: "completed"
8656
- },
8657
- ctx.cwd
8658
- );
8659
- if (!closed.ok) {
8660
- process.stderr.write(`[goal-tick] finalizeGoal: closeIssue #${t.number} failed: ${closed.error}
8661
- `);
8662
- }
8800
+ if (!hasFailedBefore) {
8801
+ ctx.output.exitCode = 1;
8802
+ ctx.output.reason = "intentional first-attempt failure for task job live test";
8803
+ return;
8663
8804
  }
8664
- goal.state = "done";
8805
+ ctx.output.exitCode = 0;
8806
+ ctx.output.reason = "task job live test succeeded after prior failure";
8665
8807
  };
8666
8808
  }
8667
8809
  });
@@ -8721,7 +8863,7 @@ var init_finalizeTerminal = __esm({
8721
8863
  });
8722
8864
 
8723
8865
  // src/scripts/finishFlow.ts
8724
- import { execFileSync as execFileSync13 } from "child_process";
8866
+ import { execFileSync as execFileSync14 } from "child_process";
8725
8867
  var TERMINAL_PHASE, API_TIMEOUT_MS5, STATUS_ICON, finishFlow;
8726
8868
  var init_finishFlow = __esm({
8727
8869
  "src/scripts/finishFlow.ts"() {
@@ -8768,7 +8910,7 @@ var init_finishFlow = __esm({
8768
8910
  **PR:** ${state.core.prUrl}` : "";
8769
8911
  const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
8770
8912
  try {
8771
- execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", body], {
8913
+ execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
8772
8914
  timeout: API_TIMEOUT_MS5,
8773
8915
  cwd: ctx.cwd,
8774
8916
  stdio: ["ignore", "pipe", "pipe"]
@@ -8800,9 +8942,9 @@ var init_finishFlow = __esm({
8800
8942
  });
8801
8943
 
8802
8944
  // src/branch.ts
8803
- import { execFileSync as execFileSync14 } from "child_process";
8945
+ import { execFileSync as execFileSync15 } from "child_process";
8804
8946
  function git3(args, cwd) {
8805
- return execFileSync14("git", args, {
8947
+ return execFileSync15("git", args, {
8806
8948
  encoding: "utf-8",
8807
8949
  timeout: 3e4,
8808
8950
  cwd,
@@ -8819,11 +8961,11 @@ function getCurrentBranch(cwd) {
8819
8961
  }
8820
8962
  function resetWorkingTree2(cwd) {
8821
8963
  try {
8822
- execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8964
+ execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8823
8965
  } catch {
8824
8966
  }
8825
8967
  try {
8826
- execFileSync14("git", ["clean", "-fd", "-e", ".kody"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8968
+ execFileSync15("git", ["clean", "-fd", "-e", ".kody"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8827
8969
  } catch {
8828
8970
  }
8829
8971
  }
@@ -8835,11 +8977,11 @@ function checkoutPrBranch(prNumber, cwd) {
8835
8977
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
8836
8978
  };
8837
8979
  try {
8838
- execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8980
+ execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
8839
8981
  } catch {
8840
8982
  }
8841
8983
  try {
8842
- execFileSync14("git", ["clean", "-fd", "-e", ".kody"], {
8984
+ execFileSync15("git", ["clean", "-fd", "-e", ".kody"], {
8843
8985
  cwd,
8844
8986
  env,
8845
8987
  stdio: ["ignore", "pipe", "pipe"],
@@ -8847,7 +8989,7 @@ function checkoutPrBranch(prNumber, cwd) {
8847
8989
  });
8848
8990
  } catch {
8849
8991
  }
8850
- execFileSync14("gh", ["pr", "checkout", String(prNumber)], {
8992
+ execFileSync15("gh", ["pr", "checkout", String(prNumber)], {
8851
8993
  cwd,
8852
8994
  env,
8853
8995
  stdio: ["ignore", "pipe", "pipe"],
@@ -8880,7 +9022,7 @@ function mergeBase(baseBranch, cwd) {
8880
9022
  }
8881
9023
  function restoreKodyAssets(cwd) {
8882
9024
  try {
8883
- execFileSync14("git", ["checkout", "HEAD", "--", ".kody"], {
9025
+ execFileSync15("git", ["checkout", "HEAD", "--", ".kody"], {
8884
9026
  cwd,
8885
9027
  stdio: ["ignore", "pipe", "pipe"],
8886
9028
  timeout: 3e4
@@ -8994,14 +9136,14 @@ var init_branch = __esm({
8994
9136
  });
8995
9137
 
8996
9138
  // src/workflow.ts
8997
- import { execFileSync as execFileSync15 } from "child_process";
9139
+ import { execFileSync as execFileSync16 } from "child_process";
8998
9140
  function ghToken3() {
8999
9141
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
9000
9142
  }
9001
9143
  function gh3(args, cwd) {
9002
9144
  const token = ghToken3();
9003
9145
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
9004
- return execFileSync15("gh", args, {
9146
+ return execFileSync16("gh", args, {
9005
9147
  encoding: "utf-8",
9006
9148
  timeout: GH_TIMEOUT_MS,
9007
9149
  cwd,
@@ -9215,66 +9357,14 @@ var init_fixFlow = __esm({
9215
9357
  }
9216
9358
  });
9217
9359
 
9218
- // src/scripts/handleAbandonedGoal.ts
9219
- var handleAbandonedGoal;
9220
- var init_handleAbandonedGoal = __esm({
9221
- "src/scripts/handleAbandonedGoal.ts"() {
9222
- "use strict";
9223
- init_operations();
9224
- handleAbandonedGoal = async (ctx) => {
9225
- const goal = ctx.data.goal;
9226
- if (!goal || goal.state !== "abandoned") return;
9227
- process.stdout.write(`[goal-tick] ${goal.id} is abandoned \u2014 running cleanup
9228
- `);
9229
- const issues = listGoalIssues(goal.id, ctx.cwd);
9230
- if (!issues.ok) {
9231
- process.stderr.write(`[goal-tick] handleAbandonedGoal: list issues failed: ${issues.error}
9232
- `);
9233
- } else {
9234
- const issueNumbers = /* @__PURE__ */ new Set();
9235
- for (const i of issues.value ?? []) {
9236
- if (i.state !== "OPEN") continue;
9237
- issueNumbers.add(i.number);
9238
- const r = closeIssue(
9239
- i.number,
9240
- {
9241
- comment: "_Goal abandoned \u2014 closing this task without dispatch._",
9242
- reason: "not planned"
9243
- },
9244
- ctx.cwd
9245
- );
9246
- if (!r.ok) {
9247
- process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close #${i.number}: ${r.error}
9248
- `);
9249
- }
9250
- }
9251
- const prs = listOpenPrs(ctx.cwd);
9252
- if (prs.ok) {
9253
- for (const pr of prs.value ?? []) {
9254
- const headMatch = pr.headRefName.match(/^(\d+)-/);
9255
- const headIssue = headMatch ? Number.parseInt(headMatch[1], 10) : NaN;
9256
- if (!Number.isFinite(headIssue) || !issueNumbers.has(headIssue)) continue;
9257
- const r = closePr(pr.number, "_Goal abandoned \u2014 closing stacked PR._", ctx.cwd);
9258
- if (!r.ok) {
9259
- process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close PR #${pr.number}: ${r.error}
9260
- `);
9261
- }
9262
- }
9263
- }
9264
- }
9265
- goal.state = "closed";
9266
- };
9267
- }
9268
- });
9269
-
9270
9360
  // src/scripts/initFlow.ts
9271
- import { execFileSync as execFileSync16 } from "child_process";
9272
- import * as fs30 from "fs";
9273
- import * as path28 from "path";
9361
+ import { execFileSync as execFileSync17 } from "child_process";
9362
+ import * as fs31 from "fs";
9363
+ import * as path29 from "path";
9274
9364
  function detectPackageManager(cwd) {
9275
- if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9276
- if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
9277
- if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
9365
+ if (fs31.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9366
+ if (fs31.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
9367
+ if (fs31.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
9278
9368
  return "npm";
9279
9369
  }
9280
9370
  function qualityCommandsFor(pm) {
@@ -9294,7 +9384,7 @@ function schemaUrlFromPkg() {
9294
9384
  function detectOwnerRepo(cwd) {
9295
9385
  let url;
9296
9386
  try {
9297
- url = execFileSync16("git", ["remote", "get-url", "origin"], {
9387
+ url = execFileSync17("git", ["remote", "get-url", "origin"], {
9298
9388
  cwd,
9299
9389
  encoding: "utf-8",
9300
9390
  stdio: ["ignore", "pipe", "pipe"]
@@ -9322,7 +9412,7 @@ function makeConfig(pm, ownerRepo, defaultBranch2) {
9322
9412
  }
9323
9413
  function defaultBranchFromGit(cwd) {
9324
9414
  try {
9325
- const ref = execFileSync16("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
9415
+ const ref = execFileSync17("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
9326
9416
  cwd,
9327
9417
  encoding: "utf-8",
9328
9418
  stdio: ["ignore", "pipe", "pipe"]
@@ -9330,7 +9420,7 @@ function defaultBranchFromGit(cwd) {
9330
9420
  return ref.replace("refs/remotes/origin/", "");
9331
9421
  } catch {
9332
9422
  try {
9333
- return execFileSync16("git", ["branch", "--show-current"], {
9423
+ return execFileSync17("git", ["branch", "--show-current"], {
9334
9424
  cwd,
9335
9425
  encoding: "utf-8",
9336
9426
  stdio: ["ignore", "pipe", "pipe"]
@@ -9346,51 +9436,51 @@ function performInit(cwd, force) {
9346
9436
  const pm = detectPackageManager(cwd);
9347
9437
  const ownerRepo = detectOwnerRepo(cwd);
9348
9438
  const defaultBranch2 = defaultBranchFromGit(cwd);
9349
- const configPath = path28.join(cwd, "kody.config.json");
9350
- if (fs30.existsSync(configPath) && !force) {
9439
+ const configPath = path29.join(cwd, "kody.config.json");
9440
+ if (fs31.existsSync(configPath) && !force) {
9351
9441
  skipped.push("kody.config.json");
9352
9442
  } else {
9353
9443
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
9354
- fs30.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9444
+ fs31.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9355
9445
  `);
9356
9446
  wrote.push("kody.config.json");
9357
9447
  }
9358
- const workflowDir = path28.join(cwd, ".github", "workflows");
9359
- const workflowPath = path28.join(workflowDir, "kody.yml");
9360
- if (fs30.existsSync(workflowPath) && !force) {
9448
+ const workflowDir = path29.join(cwd, ".github", "workflows");
9449
+ const workflowPath = path29.join(workflowDir, "kody.yml");
9450
+ if (fs31.existsSync(workflowPath) && !force) {
9361
9451
  skipped.push(".github/workflows/kody.yml");
9362
9452
  } else {
9363
- fs30.mkdirSync(workflowDir, { recursive: true });
9364
- fs30.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9453
+ fs31.mkdirSync(workflowDir, { recursive: true });
9454
+ fs31.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9365
9455
  wrote.push(".github/workflows/kody.yml");
9366
9456
  }
9367
9457
  const builtinJobs = listBuiltinJobs();
9368
9458
  if (builtinJobs.length > 0) {
9369
- const jobsDir = path28.join(cwd, ".kody", "duties");
9370
- fs30.mkdirSync(jobsDir, { recursive: true });
9459
+ const jobsDir = path29.join(cwd, ".kody", "duties");
9460
+ fs31.mkdirSync(jobsDir, { recursive: true });
9371
9461
  for (const job of builtinJobs) {
9372
- const targetDir = path28.join(jobsDir, job.slug);
9373
- const relProfile = path28.join(".kody", "duties", job.slug, "profile.json");
9374
- const relBody = path28.join(".kody", "duties", job.slug, "duty.md");
9375
- if (fs30.existsSync(targetDir) && fs30.existsSync(path28.join(targetDir, "profile.json")) && !force) {
9462
+ const targetDir = path29.join(jobsDir, job.slug);
9463
+ const relProfile = path29.join(".kody", "duties", job.slug, "profile.json");
9464
+ const relBody = path29.join(".kody", "duties", job.slug, "duty.md");
9465
+ if (fs31.existsSync(targetDir) && fs31.existsSync(path29.join(targetDir, "profile.json")) && !force) {
9376
9466
  skipped.push(relProfile);
9377
9467
  skipped.push(relBody);
9378
9468
  continue;
9379
9469
  }
9380
- fs30.mkdirSync(targetDir, { recursive: true });
9381
- fs30.writeFileSync(path28.join(targetDir, "profile.json"), fs30.readFileSync(job.profilePath, "utf-8"));
9382
- fs30.writeFileSync(path28.join(targetDir, "duty.md"), fs30.readFileSync(job.bodyPath, "utf-8"));
9470
+ fs31.mkdirSync(targetDir, { recursive: true });
9471
+ fs31.writeFileSync(path29.join(targetDir, "profile.json"), fs31.readFileSync(job.profilePath, "utf-8"));
9472
+ fs31.writeFileSync(path29.join(targetDir, "duty.md"), fs31.readFileSync(job.bodyPath, "utf-8"));
9383
9473
  wrote.push(relProfile);
9384
9474
  wrote.push(relBody);
9385
9475
  }
9386
9476
  }
9387
- const staffDir = path28.join(cwd, ".kody", "staff");
9388
- const staffPath = path28.join(staffDir, "kody.md");
9389
- if (fs30.existsSync(staffPath) && !force) {
9477
+ const staffDir = path29.join(cwd, ".kody", "staff");
9478
+ const staffPath = path29.join(staffDir, "kody.md");
9479
+ if (fs31.existsSync(staffPath) && !force) {
9390
9480
  skipped.push(".kody/staff/kody.md");
9391
9481
  } else {
9392
- fs30.mkdirSync(staffDir, { recursive: true });
9393
- fs30.writeFileSync(staffPath, DEFAULT_STAFF_PERSONA);
9482
+ fs31.mkdirSync(staffDir, { recursive: true });
9483
+ fs31.writeFileSync(staffPath, DEFAULT_STAFF_PERSONA);
9394
9484
  wrote.push(".kody/staff/kody.md");
9395
9485
  }
9396
9486
  for (const exe of listExecutables()) {
@@ -9401,12 +9491,12 @@ function performInit(cwd, force) {
9401
9491
  continue;
9402
9492
  }
9403
9493
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
9404
- const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
9405
- if (fs30.existsSync(target) && !force) {
9494
+ const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
9495
+ if (fs31.existsSync(target) && !force) {
9406
9496
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
9407
9497
  continue;
9408
9498
  }
9409
- fs30.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9499
+ fs31.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9410
9500
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
9411
9501
  }
9412
9502
  let labels;
@@ -9419,7 +9509,7 @@ function performInit(cwd, force) {
9419
9509
  }
9420
9510
  function renderScheduledWorkflow(name, cron) {
9421
9511
  return `# Scheduled kody duty: ${name}
9422
- # Generated by \`kody init\`. Regenerate with \`kody init --force\`.
9512
+ # Generated by \`kody-engine init\`. Regenerate with \`kody-engine init --force\`.
9423
9513
  # Edit the cron below or the duty's implementation profile.json#schedule.
9424
9514
 
9425
9515
  name: kody ${name}
@@ -9482,6 +9572,16 @@ on:
9482
9572
  description: "GitHub issue number"
9483
9573
  required: true
9484
9574
  type: string
9575
+ duty:
9576
+ description: "Duty action to run (default: run)"
9577
+ required: false
9578
+ type: string
9579
+ default: ""
9580
+ executable:
9581
+ description: "Legacy alias for duty action"
9582
+ required: false
9583
+ type: string
9584
+ default: ""
9485
9585
  issue_comment:
9486
9586
  types: [created]
9487
9587
 
@@ -9527,7 +9627,7 @@ When a duty writes a report or dispatches work, keep the output factual and conc
9527
9627
  const force = ctx.args.force === true;
9528
9628
  const cwd = ctx.cwd;
9529
9629
  const { wrote, skipped, labels } = performInit(cwd, force);
9530
- process.stdout.write("\u2192 kody init\n");
9630
+ process.stdout.write("\u2192 kody-engine init\n");
9531
9631
  for (const f of wrote) process.stdout.write(` wrote ${f}
9532
9632
  `);
9533
9633
  for (const f of skipped) process.stdout.write(` skipped ${f} (already exists; pass --force to overwrite)
@@ -9630,7 +9730,7 @@ var init_loadGoalState = __esm({
9630
9730
  try {
9631
9731
  const state = fetchGoalState(owner, repo, goalId, ctx.cwd);
9632
9732
  if (!state) {
9633
- process.stdout.write(`[goal-tick] no goal state for ${goalId} on ${owner}/${repo} \u2014 nothing to tick
9733
+ process.stdout.write(`[goal-manager] no goal state for ${goalId} on ${owner}/${repo}; nothing to tick
9634
9734
  `);
9635
9735
  ctx.skipAgent = true;
9636
9736
  ctx.output.exitCode = 0;
@@ -9640,17 +9740,11 @@ var init_loadGoalState = __esm({
9640
9740
  ctx.data.goal = {
9641
9741
  id: goalId,
9642
9742
  state: state.state,
9643
- lastDispatchedIssue: state.lastDispatchedIssue,
9644
- // Cache the full parsed object so saveGoalState can preserve `extra`.
9645
9743
  raw: state,
9646
- // `phase`, `childTasks`, `openTaskPrs`, `leafPr` are populated by
9647
- // deriveGoalPhase later in the chain. Initialize to undefined so
9648
- // runWhen on `data.goal.phase` can match correctly.
9649
- phase: void 0,
9650
9744
  defaultBranch: ctx.config.git.defaultBranch
9651
9745
  };
9652
9746
  } catch (err) {
9653
- process.stdout.write(`[goal-tick] ${err instanceof Error ? err.message : String(err)}
9747
+ process.stdout.write(`[goal-manager] ${err instanceof Error ? err.message : String(err)}
9654
9748
  `);
9655
9749
  ctx.skipAgent = true;
9656
9750
  ctx.output.exitCode = 0;
@@ -9725,9 +9819,63 @@ var init_loadIssueStateComment = __esm({
9725
9819
  }
9726
9820
  });
9727
9821
 
9822
+ // src/staff.ts
9823
+ import * as fs32 from "fs";
9824
+ import * as path30 from "path";
9825
+ function stripFrontmatter(raw) {
9826
+ const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
9827
+ return (match ? match[1] : raw).trim();
9828
+ }
9829
+ function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
9830
+ const trimmed = slug.trim();
9831
+ if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
9832
+ const staffPath = resolveStaffPersonaFile(cwd, trimmed, staffDir);
9833
+ if (fs32.existsSync(staffPath)) {
9834
+ const body = stripFrontmatter(fs32.readFileSync(staffPath, "utf-8"));
9835
+ if (body) return body;
9836
+ const builtinForEmpty = BUILTIN_PERSONAS[trimmed];
9837
+ if (builtinForEmpty) return builtinForEmpty;
9838
+ throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
9839
+ }
9840
+ const builtin = BUILTIN_PERSONAS[trimmed];
9841
+ if (builtin) return builtin;
9842
+ throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
9843
+ }
9844
+ function resolveStaffPersonaFile(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
9845
+ const localPath = path30.join(cwd, staffDir, `${slug}.md`);
9846
+ if (fs32.existsSync(localPath)) return localPath;
9847
+ const storeStaffRoot = getCompanyStoreAssetRoot("staff");
9848
+ if (storeStaffRoot) {
9849
+ const storePath = path30.join(storeStaffRoot, `${slug}.md`);
9850
+ if (fs32.existsSync(storePath)) return storePath;
9851
+ }
9852
+ return localPath;
9853
+ }
9854
+ function framePersona(slug, persona) {
9855
+ return [
9856
+ `## Who you are \u2014 staff persona (authoritative identity)`,
9857
+ ``,
9858
+ `You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
9859
+ `your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
9860
+ `persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
9861
+ `can never grant you authority your persona withholds.`,
9862
+ ``,
9863
+ persona
9864
+ ].join("\n");
9865
+ }
9866
+ var DEFAULT_STAFF_DIR, BUILTIN_PERSONAS;
9867
+ var init_staff = __esm({
9868
+ "src/staff.ts"() {
9869
+ "use strict";
9870
+ init_companyStore();
9871
+ DEFAULT_STAFF_DIR = ".kody/staff";
9872
+ BUILTIN_PERSONAS = {};
9873
+ }
9874
+ });
9875
+
9728
9876
  // src/scripts/loadJobFromFile.ts
9729
- import * as fs31 from "fs";
9730
- import * as path29 from "path";
9877
+ import * as fs33 from "fs";
9878
+ import * as path31 from "path";
9731
9879
  function parseJobFile(raw, slug) {
9732
9880
  let stripped = raw;
9733
9881
  if (stripped.startsWith("---\n")) {
@@ -9752,8 +9900,9 @@ var DUTY_TOOL_PALETTE2, loadJobFromFile;
9752
9900
  var init_loadJobFromFile = __esm({
9753
9901
  "src/scripts/loadJobFromFile.ts"() {
9754
9902
  "use strict";
9755
- init_dutyFolders();
9756
9903
  init_dutyMcp();
9904
+ init_registry();
9905
+ init_staff();
9757
9906
  init_jobState();
9758
9907
  DUTY_TOOL_PALETTE2 = new Set(DUTY_MCP_TOOL_NAMES);
9759
9908
  loadJobFromFile = async (ctx, profile, args) => {
@@ -9764,9 +9913,9 @@ var init_loadJobFromFile = __esm({
9764
9913
  if (!slug) {
9765
9914
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
9766
9915
  }
9767
- const duty = readDutyFolder(path29.join(ctx.cwd, jobsDir), slug);
9916
+ const duty = resolveDutyFolder(slug, path31.join(ctx.cwd, jobsDir));
9768
9917
  if (!duty) {
9769
- throw new Error(`loadJobFromFile: duty folder not found or incomplete: ${path29.join(ctx.cwd, jobsDir, slug)}`);
9918
+ throw new Error(`loadJobFromFile: duty folder not found or incomplete: ${path31.join(ctx.cwd, jobsDir, slug)}`);
9770
9919
  }
9771
9920
  const { title, body, config } = duty;
9772
9921
  const mentions = (config.mentions ?? []).map((login) => `@${login}`).join(" ");
@@ -9774,11 +9923,11 @@ var init_loadJobFromFile = __esm({
9774
9923
  let workerTitle = "";
9775
9924
  let workerPersona = "";
9776
9925
  if (workerSlug) {
9777
- const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9778
- if (!fs31.existsSync(workerPath)) {
9926
+ const workerPath = resolveStaffPersonaFile(ctx.cwd, workerSlug, workersDir);
9927
+ if (!fs33.existsSync(workerPath)) {
9779
9928
  throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
9780
9929
  }
9781
- const workerRaw = fs31.readFileSync(workerPath, "utf-8");
9930
+ const workerRaw = fs33.readFileSync(workerPath, "utf-8");
9782
9931
  const parsed = parseJobFile(workerRaw, workerSlug);
9783
9932
  workerTitle = parsed.title;
9784
9933
  workerPersona = parsed.body;
@@ -9852,13 +10001,13 @@ ${truncate2(issue.body, FINDING_BODY_MAX_BYTES)}`;
9852
10001
  });
9853
10002
 
9854
10003
  // src/scripts/kodyVariables.ts
9855
- import * as fs32 from "fs";
9856
- import * as path30 from "path";
10004
+ import * as fs34 from "fs";
10005
+ import * as path32 from "path";
9857
10006
  function readKodyVariables(cwd) {
9858
- const full = path30.join(cwd, KODY_VARIABLES_REL_PATH);
10007
+ const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
9859
10008
  let raw;
9860
10009
  try {
9861
- raw = fs32.readFileSync(full, "utf-8");
10010
+ raw = fs34.readFileSync(full, "utf-8");
9862
10011
  } catch {
9863
10012
  return {};
9864
10013
  }
@@ -9883,8 +10032,8 @@ var init_kodyVariables = __esm({
9883
10032
  });
9884
10033
 
9885
10034
  // src/scripts/loadQaContext.ts
9886
- import * as fs33 from "fs";
9887
- import * as path31 from "path";
10035
+ import * as fs35 from "fs";
10036
+ import * as path33 from "path";
9888
10037
  function parseSlugList(value) {
9889
10038
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
9890
10039
  return inner.split(",").map(
@@ -9913,18 +10062,18 @@ function readProfileStaff(raw) {
9913
10062
  return { staff: staff ?? legacy ?? ["kody"], body };
9914
10063
  }
9915
10064
  function readProfile(cwd) {
9916
- const dir = path31.join(cwd, CONTEXT_DIR_REL_PATH);
9917
- if (!fs33.existsSync(dir)) return "";
10065
+ const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
10066
+ if (!fs35.existsSync(dir)) return "";
9918
10067
  let entries;
9919
10068
  try {
9920
- entries = fs33.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
10069
+ entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
9921
10070
  } catch {
9922
10071
  return "";
9923
10072
  }
9924
10073
  const blocks = [];
9925
10074
  for (const file of entries) {
9926
10075
  try {
9927
- const raw = fs33.readFileSync(path31.join(dir, file), "utf-8");
10076
+ const raw = fs35.readFileSync(path33.join(dir, file), "utf-8");
9928
10077
  const { staff, body } = readProfileStaff(raw);
9929
10078
  if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
9930
10079
  blocks.push(`## ${file}
@@ -9970,8 +10119,8 @@ var init_loadQaContext = __esm({
9970
10119
  });
9971
10120
 
9972
10121
  // src/taskContext.ts
9973
- import * as fs34 from "fs";
9974
- import * as path32 from "path";
10122
+ import * as fs36 from "fs";
10123
+ import * as path34 from "path";
9975
10124
  function buildTaskContext(args) {
9976
10125
  return {
9977
10126
  schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
@@ -9986,10 +10135,10 @@ function buildTaskContext(args) {
9986
10135
  }
9987
10136
  function persistTaskContext(cwd, ctx) {
9988
10137
  try {
9989
- const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
9990
- fs34.mkdirSync(dir, { recursive: true });
9991
- const file = path32.join(dir, "task-context.json");
9992
- fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
10138
+ const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
10139
+ fs36.mkdirSync(dir, { recursive: true });
10140
+ const file = path34.join(dir, "task-context.json");
10141
+ fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
9993
10142
  `);
9994
10143
  return file;
9995
10144
  } catch (err) {
@@ -10077,8 +10226,7 @@ var init_loadTaskState = __esm({
10077
10226
  });
10078
10227
 
10079
10228
  // src/scripts/loadWorkerAdhoc.ts
10080
- import * as fs35 from "fs";
10081
- import * as path33 from "path";
10229
+ import * as fs37 from "fs";
10082
10230
  function resolveMessage(messageArg) {
10083
10231
  const fromComment = readCommentBody();
10084
10232
  if (fromComment) return stripDirective(fromComment);
@@ -10086,9 +10234,9 @@ function resolveMessage(messageArg) {
10086
10234
  }
10087
10235
  function readCommentBody() {
10088
10236
  const eventPath = process.env.GITHUB_EVENT_PATH;
10089
- if (!eventPath || !fs35.existsSync(eventPath)) return "";
10237
+ if (!eventPath || !fs37.existsSync(eventPath)) return "";
10090
10238
  try {
10091
- const event = JSON.parse(fs35.readFileSync(eventPath, "utf-8"));
10239
+ const event = JSON.parse(fs37.readFileSync(eventPath, "utf-8"));
10092
10240
  return String(event.comment?.body ?? "");
10093
10241
  } catch {
10094
10242
  return "";
@@ -10133,17 +10281,18 @@ var loadWorkerAdhoc;
10133
10281
  var init_loadWorkerAdhoc = __esm({
10134
10282
  "src/scripts/loadWorkerAdhoc.ts"() {
10135
10283
  "use strict";
10284
+ init_staff();
10136
10285
  loadWorkerAdhoc = async (ctx, _profile, args) => {
10137
10286
  const workersDir = String(args?.workersDir ?? ".kody/staff");
10138
10287
  const workerSlug = String(ctx.args.worker ?? "").trim();
10139
10288
  if (!workerSlug) {
10140
10289
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
10141
10290
  }
10142
- const workerPath = path33.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10143
- if (!fs35.existsSync(workerPath)) {
10291
+ const workerPath = resolveStaffPersonaFile(ctx.cwd, workerSlug, workersDir);
10292
+ if (!fs37.existsSync(workerPath)) {
10144
10293
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
10145
10294
  }
10146
- const { title, body } = parsePersona(fs35.readFileSync(workerPath, "utf-8"), workerSlug);
10295
+ const { title, body } = parsePersona(fs37.readFileSync(workerPath, "utf-8"), workerSlug);
10147
10296
  const message = resolveMessage(ctx.args.message);
10148
10297
  if (!message) {
10149
10298
  throw new Error(
@@ -10173,6 +10322,37 @@ var init_markFlowSuccess = __esm({
10173
10322
  }
10174
10323
  });
10175
10324
 
10325
+ // src/goal/operations.ts
10326
+ function fail(err) {
10327
+ if (err instanceof Error) {
10328
+ const lines = err.message.split("\n").filter(Boolean);
10329
+ return { ok: false, error: lines[0] ?? err.message };
10330
+ }
10331
+ return { ok: false, error: String(err) };
10332
+ }
10333
+ function commentOnIssue(issueNumber, body, cwd) {
10334
+ try {
10335
+ gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
10336
+ return { ok: true };
10337
+ } catch (err) {
10338
+ return fail(err);
10339
+ }
10340
+ }
10341
+ function mergePrSquash(prNumber, cwd) {
10342
+ try {
10343
+ gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
10344
+ return { ok: true };
10345
+ } catch (err) {
10346
+ return fail(err);
10347
+ }
10348
+ }
10349
+ var init_operations = __esm({
10350
+ "src/goal/operations.ts"() {
10351
+ "use strict";
10352
+ init_issue();
10353
+ }
10354
+ });
10355
+
10176
10356
  // src/scripts/mergeFlow.ts
10177
10357
  function readPr(prNumber, cwd) {
10178
10358
  const out = gh(["pr", "view", String(prNumber), "--json", "state,isDraft,mergeable,mergeStateStatus,title,url"], {
@@ -10286,7 +10466,7 @@ var init_mergeFlow = __esm({
10286
10466
  });
10287
10467
 
10288
10468
  // src/scripts/mergeReleasePr.ts
10289
- import { execFileSync as execFileSync17 } from "child_process";
10469
+ import { execFileSync as execFileSync18 } from "child_process";
10290
10470
  function makeAction(type, payload) {
10291
10471
  return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
10292
10472
  }
@@ -10318,7 +10498,7 @@ var init_mergeReleasePr = __esm({
10318
10498
  process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
10319
10499
  `);
10320
10500
  try {
10321
- const out = execFileSync17("gh", ["pr", "merge", String(prNumber), "--merge"], {
10501
+ const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
10322
10502
  timeout: API_TIMEOUT_MS6,
10323
10503
  cwd: ctx.cwd,
10324
10504
  stdio: ["ignore", "pipe", "pipe"]
@@ -11114,7 +11294,7 @@ var init_promoteQaGoal = __esm({
11114
11294
  });
11115
11295
 
11116
11296
  // src/scripts/recordClassification.ts
11117
- import { execFileSync as execFileSync18 } from "child_process";
11297
+ import { execFileSync as execFileSync19 } from "child_process";
11118
11298
  function parseClassification(prSummary) {
11119
11299
  if (!prSummary) return null;
11120
11300
  const classMatch = prSummary.match(/classification:\s*(feature|bug|spec|chore)\b/i);
@@ -11126,7 +11306,7 @@ function parseClassification(prSummary) {
11126
11306
  }
11127
11307
  function tryAuditComment(issueNumber, body, cwd) {
11128
11308
  try {
11129
- execFileSync18("gh", ["issue", "comment", String(issueNumber), "--body", body], {
11309
+ execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
11130
11310
  cwd,
11131
11311
  timeout: API_TIMEOUT_MS7,
11132
11312
  stdio: ["ignore", "pipe", "pipe"]
@@ -11306,7 +11486,7 @@ var init_resolveArtifacts = __esm({
11306
11486
  });
11307
11487
 
11308
11488
  // src/scripts/resolveFlow.ts
11309
- import { execFileSync as execFileSync19 } from "child_process";
11489
+ import { execFileSync as execFileSync20 } from "child_process";
11310
11490
  function buildPreferBlock(prefer, baseBranch) {
11311
11491
  if (prefer !== "ours" && prefer !== "theirs") return "";
11312
11492
  const keepSide = prefer === "ours" ? "HEAD (this PR branch)" : `origin/${baseBranch} (base branch)`;
@@ -11326,7 +11506,7 @@ function buildPreferBlock(prefer, baseBranch) {
11326
11506
  }
11327
11507
  function getConflictedFiles(cwd) {
11328
11508
  try {
11329
- const out = execFileSync19("git", ["diff", "--name-only", "--diff-filter=U"], {
11509
+ const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
11330
11510
  encoding: "utf-8",
11331
11511
  cwd,
11332
11512
  env: { ...process.env, HUSKY: "0" }
@@ -11341,7 +11521,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
11341
11521
  let total = 0;
11342
11522
  for (const f of files) {
11343
11523
  try {
11344
- const content = execFileSync19("cat", [f], { encoding: "utf-8", cwd }).toString();
11524
+ const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
11345
11525
  const snippet = `### ${f}
11346
11526
 
11347
11527
  \`\`\`
@@ -11365,12 +11545,12 @@ function tryPostPr3(prNumber, body, cwd) {
11365
11545
  function pushEmptyCommit(branch, cwd) {
11366
11546
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
11367
11547
  try {
11368
- execFileSync19(
11548
+ execFileSync20(
11369
11549
  "git",
11370
11550
  ["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
11371
11551
  { cwd, env, stdio: ["ignore", "pipe", "pipe"] }
11372
11552
  );
11373
- execFileSync19("git", ["push", "-u", "origin", branch], {
11553
+ execFileSync20("git", ["push", "-u", "origin", branch], {
11374
11554
  cwd,
11375
11555
  env,
11376
11556
  stdio: ["ignore", "pipe", "pipe"]
@@ -11556,10 +11736,10 @@ var init_resolvePreviewUrl = __esm({
11556
11736
  });
11557
11737
 
11558
11738
  // src/scripts/resolveQaUrl.ts
11559
- import { execFileSync as execFileSync20 } from "child_process";
11739
+ import { execFileSync as execFileSync21 } from "child_process";
11560
11740
  function ghQuery(args, cwd) {
11561
11741
  try {
11562
- const out = execFileSync20("gh", args, {
11742
+ const out = execFileSync21("gh", args, {
11563
11743
  cwd,
11564
11744
  stdio: ["ignore", "pipe", "pipe"],
11565
11745
  encoding: "utf-8",
@@ -11636,7 +11816,7 @@ var init_resolveQaUrl = __esm({
11636
11816
  });
11637
11817
 
11638
11818
  // src/scripts/revertFlow.ts
11639
- import { execFileSync as execFileSync21 } from "child_process";
11819
+ import { execFileSync as execFileSync22 } from "child_process";
11640
11820
  function buildCommitMessage(resolved) {
11641
11821
  if (resolved.length === 1) {
11642
11822
  const { full, subject } = resolved[0];
@@ -11649,7 +11829,7 @@ function buildPrSummary(resolved) {
11649
11829
  return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
11650
11830
  }
11651
11831
  function git4(args, cwd) {
11652
- return execFileSync21("git", args, {
11832
+ return execFileSync22("git", args, {
11653
11833
  encoding: "utf-8",
11654
11834
  timeout: 3e4,
11655
11835
  cwd,
@@ -11659,7 +11839,7 @@ function git4(args, cwd) {
11659
11839
  }
11660
11840
  function isAncestorOfHead(sha, cwd) {
11661
11841
  try {
11662
- execFileSync21("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
11842
+ execFileSync22("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
11663
11843
  cwd,
11664
11844
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
11665
11845
  stdio: ["ignore", "ignore", "ignore"]
@@ -11854,9 +12034,9 @@ var init_runFlow = __esm({
11854
12034
  });
11855
12035
 
11856
12036
  // src/scripts/previewBuildHelpers.ts
11857
- import { createDecipheriv, createHash } from "crypto";
12037
+ import { createDecipheriv, createHash as createHash2 } from "crypto";
11858
12038
  function shortHash(s) {
11859
- return createHash("sha256").update(s).digest("hex").slice(0, 6);
12039
+ return createHash2("sha256").update(s).digest("hex").slice(0, 6);
11860
12040
  }
11861
12041
  function previewAppName(repo, pr) {
11862
12042
  const [owner, name] = repo.split("/");
@@ -11910,7 +12090,7 @@ function formatPreviewComment(args) {
11910
12090
  ].join("\n");
11911
12091
  }
11912
12092
  function defaultImageTag(repo, ref) {
11913
- return createHash("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
12093
+ return createHash2("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
11914
12094
  }
11915
12095
  var NEVER_PASS_TO_BUILD;
11916
12096
  var init_previewBuildHelpers = __esm({
@@ -11930,7 +12110,7 @@ var init_previewBuildHelpers = __esm({
11930
12110
  // src/scripts/previewBuildRun.ts
11931
12111
  import { spawn as spawn4 } from "child_process";
11932
12112
  async function runCmd(cmd, args, opts = {}) {
11933
- await new Promise((resolve6, reject) => {
12113
+ await new Promise((resolve7, reject) => {
11934
12114
  const child = spawn4(cmd, args, {
11935
12115
  cwd: opts.cwd,
11936
12116
  env: { ...process.env, ...opts.env ?? {} },
@@ -11942,7 +12122,7 @@ async function runCmd(cmd, args, opts = {}) {
11942
12122
  }
11943
12123
  child.on("error", reject);
11944
12124
  child.on("close", (code) => {
11945
- if (code === 0) resolve6();
12125
+ if (code === 0) resolve7();
11946
12126
  else reject(new Error(`${cmd} ${args.join(" ")} exited ${code}`));
11947
12127
  });
11948
12128
  });
@@ -12014,12 +12194,12 @@ fi
12014
12194
 
12015
12195
  // src/scripts/runPreviewBuild.ts
12016
12196
  import { copyFile, writeFile } from "fs/promises";
12017
- import * as path34 from "path";
12197
+ import * as path35 from "path";
12018
12198
  import { fileURLToPath } from "url";
12019
12199
  function bundledDockerfilePath(mode) {
12020
- const here = path34.dirname(fileURLToPath(import.meta.url));
12200
+ const here = path35.dirname(fileURLToPath(import.meta.url));
12021
12201
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
12022
- return path34.join(here, "preview-build-templates", file);
12202
+ return path35.join(here, "preview-build-templates", file);
12023
12203
  }
12024
12204
  function required(name) {
12025
12205
  const v = (process.env[name] ?? "").trim();
@@ -12278,10 +12458,10 @@ var init_runPreviewBuild = __esm({
12278
12458
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
12279
12459
  if (Object.keys(buildEnv).length > 0) {
12280
12460
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
12281
- await writeFile(path34.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
12461
+ await writeFile(path35.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
12282
12462
  `, "utf8");
12283
12463
  }
12284
- const consumerDockerfile = path34.join(ctx.cwd, "Dockerfile.preview");
12464
+ const consumerDockerfile = path35.join(ctx.cwd, "Dockerfile.preview");
12285
12465
  const { stat } = await import("fs/promises");
12286
12466
  let hasConsumerDockerfile = false;
12287
12467
  try {
@@ -12461,8 +12641,8 @@ var init_tickShellRunner = __esm({
12461
12641
  });
12462
12642
 
12463
12643
  // src/scripts/runScheduledExecutableTick.ts
12464
- import * as fs36 from "fs";
12465
- import * as path35 from "path";
12644
+ import * as fs38 from "fs";
12645
+ import * as path36 from "path";
12466
12646
  var runScheduledExecutableTick;
12467
12647
  var init_runScheduledExecutableTick = __esm({
12468
12648
  "src/scripts/runScheduledExecutableTick.ts"() {
@@ -12482,14 +12662,14 @@ var init_runScheduledExecutableTick = __esm({
12482
12662
  ctx.output.reason = `runScheduledExecutableTick: ctx.args.${slugArg} must be non-empty duty slug`;
12483
12663
  return;
12484
12664
  }
12485
- const duty = readDutyFolder(path35.join(ctx.cwd, jobsDir), slug);
12665
+ const duty = readDutyFolder(path36.join(ctx.cwd, jobsDir), slug);
12486
12666
  if (!duty) {
12487
12667
  ctx.output.exitCode = 99;
12488
- ctx.output.reason = `runScheduledExecutableTick: duty folder not found or incomplete: ${path35.join(jobsDir, slug)}`;
12668
+ ctx.output.reason = `runScheduledExecutableTick: duty folder not found or incomplete: ${path36.join(jobsDir, slug)}`;
12489
12669
  return;
12490
12670
  }
12491
- const shellPath = path35.join(profile.dir, shell);
12492
- if (!fs36.existsSync(shellPath)) {
12671
+ const shellPath = path36.join(profile.dir, shell);
12672
+ if (!fs38.existsSync(shellPath)) {
12493
12673
  ctx.output.exitCode = 99;
12494
12674
  ctx.output.reason = `runScheduledExecutableTick: shell not found: ${shell} (looked in ${profile.dir})`;
12495
12675
  return;
@@ -12520,8 +12700,8 @@ var init_runScheduledExecutableTick = __esm({
12520
12700
  });
12521
12701
 
12522
12702
  // src/scripts/runTickScript.ts
12523
- import * as fs37 from "fs";
12524
- import * as path36 from "path";
12703
+ import * as fs39 from "fs";
12704
+ import * as path37 from "path";
12525
12705
  var runTickScript;
12526
12706
  var init_runTickScript = __esm({
12527
12707
  "src/scripts/runTickScript.ts"() {
@@ -12540,10 +12720,10 @@ var init_runTickScript = __esm({
12540
12720
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
12541
12721
  return;
12542
12722
  }
12543
- const duty = readDutyFolder(path36.join(ctx.cwd, jobsDir), slug);
12723
+ const duty = readDutyFolder(path37.join(ctx.cwd, jobsDir), slug);
12544
12724
  if (!duty) {
12545
12725
  ctx.output.exitCode = 99;
12546
- ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${path36.join(ctx.cwd, jobsDir, slug)}`;
12726
+ ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${path37.join(ctx.cwd, jobsDir, slug)}`;
12547
12727
  return;
12548
12728
  }
12549
12729
  const tickScript = duty.config.tickScript;
@@ -12552,8 +12732,8 @@ var init_runTickScript = __esm({
12552
12732
  ctx.output.reason = `runTickScript: duty ${slug} has no \`tickScript\` in profile.json \u2014 route via duty-tick instead`;
12553
12733
  return;
12554
12734
  }
12555
- const scriptPath = path36.isAbsolute(tickScript) ? tickScript : path36.join(ctx.cwd, tickScript);
12556
- if (!fs37.existsSync(scriptPath)) {
12735
+ const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
12736
+ if (!fs39.existsSync(scriptPath)) {
12557
12737
  ctx.output.exitCode = 99;
12558
12738
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
12559
12739
  return;
@@ -12582,29 +12762,21 @@ var init_runTickScript = __esm({
12582
12762
  }
12583
12763
  });
12584
12764
 
12585
- // src/scripts/saveGoalState.ts
12586
- var saveGoalState;
12587
- var init_saveGoalState = __esm({
12588
- "src/scripts/saveGoalState.ts"() {
12765
+ // src/scripts/saveManagedGoalState.ts
12766
+ var saveManagedGoalState;
12767
+ var init_saveManagedGoalState = __esm({
12768
+ "src/scripts/saveManagedGoalState.ts"() {
12589
12769
  "use strict";
12590
12770
  init_state2();
12591
- saveGoalState = async (ctx) => {
12771
+ saveManagedGoalState = async (ctx) => {
12772
+ ctx.skipAgent = true;
12592
12773
  const goal = ctx.data.goal;
12593
- if (!goal) {
12594
- ctx.skipAgent = true;
12595
- return;
12596
- }
12597
- const prev = goal.raw;
12598
- const changed = !prev || prev.state !== goal.state || prev.lastDispatchedIssue !== goal.lastDispatchedIssue;
12599
- const updated = {
12600
- ...prev ?? { state: goal.state, extra: {} },
12601
- state: goal.state,
12602
- lastDispatchedIssue: goal.lastDispatchedIssue,
12603
- updatedAt: changed ? nowIso() : prev?.updatedAt
12604
- };
12774
+ if (!goal?.raw) return;
12775
+ const original = typeof ctx.data.goalOriginalStateText === "string" ? ctx.data.goalOriginalStateText : "";
12776
+ const changedBeforeTimestamp = original !== serializeGoalState(goal.raw);
12777
+ const updated = changedBeforeTimestamp ? { ...goal.raw, updatedAt: nowIso() } : goal.raw;
12605
12778
  ctx.data.goalPersistState = updated;
12606
- ctx.data.goalPersistChanged = changed;
12607
- ctx.skipAgent = true;
12779
+ ctx.data.goalPersistChanged = original !== serializeGoalState(updated);
12608
12780
  };
12609
12781
  }
12610
12782
  });
@@ -12674,7 +12846,7 @@ var init_skipAgent = __esm({
12674
12846
  });
12675
12847
 
12676
12848
  // src/scripts/stageMergeConflicts.ts
12677
- import { execFileSync as execFileSync22 } from "child_process";
12849
+ import { execFileSync as execFileSync23 } from "child_process";
12678
12850
  var stageMergeConflicts;
12679
12851
  var init_stageMergeConflicts = __esm({
12680
12852
  "src/scripts/stageMergeConflicts.ts"() {
@@ -12682,7 +12854,7 @@ var init_stageMergeConflicts = __esm({
12682
12854
  stageMergeConflicts = async (ctx) => {
12683
12855
  if (ctx.data.agentDone === false) return;
12684
12856
  try {
12685
- execFileSync22("git", ["add", "-A"], {
12857
+ execFileSync23("git", ["add", "-A"], {
12686
12858
  cwd: ctx.cwd,
12687
12859
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
12688
12860
  stdio: "pipe"
@@ -12736,7 +12908,7 @@ var init_startFlow = __esm({
12736
12908
  });
12737
12909
 
12738
12910
  // src/scripts/syncFlow.ts
12739
- import { execFileSync as execFileSync23 } from "child_process";
12911
+ import { execFileSync as execFileSync24 } from "child_process";
12740
12912
  function restoreDone(prNumber, cwd) {
12741
12913
  try {
12742
12914
  setKodyLabel(prNumber, DONE2, cwd);
@@ -12753,7 +12925,7 @@ function bail2(ctx, prNumber, reason) {
12753
12925
  }
12754
12926
  function revParseHead(cwd) {
12755
12927
  try {
12756
- return execFileSync23("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
12928
+ return execFileSync24("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
12757
12929
  } catch {
12758
12930
  return "";
12759
12931
  }
@@ -12896,7 +13068,7 @@ function stripAnsi2(s) {
12896
13068
  return s.replace(ANSI_RE2, "");
12897
13069
  }
12898
13070
  function runCommand2(command, cwd) {
12899
- return new Promise((resolve6) => {
13071
+ return new Promise((resolve7) => {
12900
13072
  const child = spawn5(command, {
12901
13073
  cwd,
12902
13074
  shell: true,
@@ -12923,11 +13095,11 @@ function runCommand2(command, cwd) {
12923
13095
  }, TEST_TIMEOUT_MS);
12924
13096
  child.on("exit", (code) => {
12925
13097
  clearTimeout(timer);
12926
- resolve6({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
13098
+ resolve7({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
12927
13099
  });
12928
13100
  child.on("error", (err) => {
12929
13101
  clearTimeout(timer);
12930
- resolve6({ exitCode: -1, output: err.message });
13102
+ resolve7({ exitCode: -1, output: err.message });
12931
13103
  });
12932
13104
  });
12933
13105
  }
@@ -13094,10 +13266,10 @@ var init_verifyWithRetry = __esm({
13094
13266
  });
13095
13267
 
13096
13268
  // src/scripts/waitForCi.ts
13097
- import { execFileSync as execFileSync24 } from "child_process";
13269
+ import { execFileSync as execFileSync25 } from "child_process";
13098
13270
  function fetchChecks(prNumber, cwd) {
13099
13271
  try {
13100
- const raw = execFileSync24("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
13272
+ const raw = execFileSync25("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
13101
13273
  encoding: "utf-8",
13102
13274
  timeout: API_TIMEOUT_MS8,
13103
13275
  cwd,
@@ -13332,21 +13504,21 @@ function lineStream(stream) {
13332
13504
  tryDeliver();
13333
13505
  });
13334
13506
  return {
13335
- next: (timeoutMs) => new Promise((resolve6) => {
13507
+ next: (timeoutMs) => new Promise((resolve7) => {
13336
13508
  if (queue.length > 0) {
13337
- resolve6(queue.shift());
13509
+ resolve7(queue.shift());
13338
13510
  return;
13339
13511
  }
13340
13512
  if (ended) {
13341
- resolve6(null);
13513
+ resolve7(null);
13342
13514
  return;
13343
13515
  }
13344
- waiter = resolve6;
13516
+ waiter = resolve7;
13345
13517
  const t = setTimeout(
13346
13518
  () => {
13347
- if (waiter === resolve6) {
13519
+ if (waiter === resolve7) {
13348
13520
  waiter = null;
13349
- resolve6(null);
13521
+ resolve7(null);
13350
13522
  }
13351
13523
  },
13352
13524
  Math.max(0, timeoutMs)
@@ -13486,7 +13658,7 @@ var init_writeJobStateFile = __esm({
13486
13658
  });
13487
13659
 
13488
13660
  // src/scripts/writeRunSummary.ts
13489
- import * as fs38 from "fs";
13661
+ import * as fs40 from "fs";
13490
13662
  var writeRunSummary;
13491
13663
  var init_writeRunSummary = __esm({
13492
13664
  "src/scripts/writeRunSummary.ts"() {
@@ -13512,7 +13684,7 @@ var init_writeRunSummary = __esm({
13512
13684
  if (reason) lines.push(`- **Reason:** ${reason}`);
13513
13685
  lines.push("");
13514
13686
  try {
13515
- fs38.appendFileSync(summaryPath, `${lines.join("\n")}
13687
+ fs40.appendFileSync(summaryPath, `${lines.join("\n")}
13516
13688
  `);
13517
13689
  } catch {
13518
13690
  }
@@ -13527,7 +13699,9 @@ var init_scripts = __esm({
13527
13699
  "use strict";
13528
13700
  init_abortUnfinishedGitOps();
13529
13701
  init_advanceFlow();
13702
+ init_advanceManagedGoal();
13530
13703
  init_appendCompanyActivity();
13704
+ init_applyDutyReports();
13531
13705
  init_buildSyntheticPlugin();
13532
13706
  init_checkCoverageWithRetry();
13533
13707
  init_classifyByLabel();
@@ -13535,7 +13709,6 @@ var init_scripts = __esm({
13535
13709
  init_commitGoalState();
13536
13710
  init_composePrompt();
13537
13711
  init_createQaGoal();
13538
- init_deriveGoalPhase();
13539
13712
  init_deriveQaScopeFromIssue();
13540
13713
  init_diagMcp();
13541
13714
  init_discoverQaContext();
@@ -13543,16 +13716,13 @@ var init_scripts = __esm({
13543
13716
  init_dispatchClassified();
13544
13717
  init_dispatchDutyFileTicks();
13545
13718
  init_dispatchDutyTicks();
13546
- init_dispatchNextTask();
13547
13719
  init_dispatchNextTaskJob();
13548
13720
  init_ensurePr();
13549
13721
  init_failOnceTaskJob();
13550
- init_finalizeGoal();
13551
13722
  init_finalizeTerminal();
13552
13723
  init_finishFlow();
13553
13724
  init_fixCiFlow();
13554
13725
  init_fixFlow();
13555
- init_handleAbandonedGoal();
13556
13726
  init_initFlow();
13557
13727
  init_loadConventions();
13558
13728
  init_loadCoverageRules();
@@ -13601,7 +13771,7 @@ var init_scripts = __esm({
13601
13771
  init_runPreviewBuild();
13602
13772
  init_runScheduledExecutableTick();
13603
13773
  init_runTickScript();
13604
- init_saveGoalState();
13774
+ init_saveManagedGoalState();
13605
13775
  init_saveTaskState();
13606
13776
  init_setCommentTarget();
13607
13777
  init_setLifecycleLabel();
@@ -13661,12 +13831,9 @@ var init_scripts = __esm({
13661
13831
  runScheduledExecutableTick,
13662
13832
  runTickScript,
13663
13833
  runPreviewBuild,
13834
+ advanceManagedGoal,
13664
13835
  loadGoalState,
13665
- handleAbandonedGoal,
13666
- deriveGoalPhase,
13667
- dispatchNextTask,
13668
- finalizeGoal,
13669
- saveGoalState
13836
+ saveManagedGoalState
13670
13837
  };
13671
13838
  postflightScripts = {
13672
13839
  parseAgentResult: parseAgentResult2,
@@ -13701,6 +13868,7 @@ var init_scripts = __esm({
13701
13868
  finalizeTerminal,
13702
13869
  advanceFlow,
13703
13870
  persistFlowState,
13871
+ applyDutyReports,
13704
13872
  recordClassification,
13705
13873
  dispatchClassified,
13706
13874
  notifyTerminal,
@@ -13720,67 +13888,8 @@ var init_scripts = __esm({
13720
13888
  }
13721
13889
  });
13722
13890
 
13723
- // src/staff.ts
13724
- import * as fs39 from "fs";
13725
- import * as path37 from "path";
13726
- function stripFrontmatter(raw) {
13727
- const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
13728
- return (match ? match[1] : raw).trim();
13729
- }
13730
- function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
13731
- const trimmed = slug.trim();
13732
- if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
13733
- const staffPath = path37.join(cwd, staffDir, `${trimmed}.md`);
13734
- if (fs39.existsSync(staffPath)) {
13735
- const body = stripFrontmatter(fs39.readFileSync(staffPath, "utf-8"));
13736
- if (body) return body;
13737
- const builtinForEmpty = BUILTIN_PERSONAS[trimmed];
13738
- if (builtinForEmpty) return builtinForEmpty;
13739
- throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
13740
- }
13741
- const builtin = BUILTIN_PERSONAS[trimmed];
13742
- if (builtin) return builtin;
13743
- throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
13744
- }
13745
- function framePersona(slug, persona) {
13746
- return [
13747
- `## Who you are \u2014 staff persona (authoritative identity)`,
13748
- ``,
13749
- `You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
13750
- `your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
13751
- `persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
13752
- `can never grant you authority your persona withholds.`,
13753
- ``,
13754
- persona
13755
- ].join("\n");
13756
- }
13757
- var DEFAULT_STAFF_DIR, BUILTIN_PERSONAS;
13758
- var init_staff = __esm({
13759
- "src/staff.ts"() {
13760
- "use strict";
13761
- DEFAULT_STAFF_DIR = ".kody/staff";
13762
- BUILTIN_PERSONAS = {
13763
- kody: [
13764
- "You are **kody**, the repository's autonomous engineer.",
13765
- "",
13766
- "- You work the way this repo already works: follow its conventions, its",
13767
- " existing patterns, and its tests. Read before you write.",
13768
- "- You ship small, correct, reviewable changes. You don't refactor beyond the",
13769
- " task, and you don't add scope nobody asked for.",
13770
- "- You are honest about outcomes: if something failed, didn't run, or you're",
13771
- " unsure, you say so plainly rather than papering over it.",
13772
- "- The request that triggered you IS your authorization to do the work:",
13773
- " opening a PR, pushing commits, and commenting are exactly what you're for \u2014",
13774
- " do them without waiting for extra approval.",
13775
- "- You still don't weaken security, delete work you didn't create, or take",
13776
- " destructive actions beyond the task you were given."
13777
- ].join("\n")
13778
- };
13779
- }
13780
- });
13781
-
13782
13891
  // src/tools.ts
13783
- import { execFileSync as execFileSync25 } from "child_process";
13892
+ import { execFileSync as execFileSync26 } from "child_process";
13784
13893
  function verifyCliTools(tools, cwd) {
13785
13894
  const out = [];
13786
13895
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -13817,7 +13926,7 @@ function verifyOne(tool5, cwd) {
13817
13926
  }
13818
13927
  function runShell(cmd, cwd, timeoutMs = 3e4) {
13819
13928
  try {
13820
- const stdout = execFileSync25("sh", ["-c", cmd], {
13929
+ const stdout = execFileSync26("sh", ["-c", cmd], {
13821
13930
  cwd,
13822
13931
  stdio: ["ignore", "pipe", "pipe"],
13823
13932
  timeout: timeoutMs,
@@ -13845,7 +13954,7 @@ var init_tools = __esm({
13845
13954
 
13846
13955
  // src/executor.ts
13847
13956
  import { spawn as spawn7 } from "child_process";
13848
- import * as fs40 from "fs";
13957
+ import * as fs41 from "fs";
13849
13958
  import * as path38 from "path";
13850
13959
  function isMutatingPostflight(scriptName) {
13851
13960
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
@@ -13950,6 +14059,8 @@ async function runExecutable(profileName, input) {
13950
14059
  }
13951
14060
  const perExecutableModel = config.agent.perExecutable?.[profileName];
13952
14061
  const modelSpec = perExecutableModel ? perExecutableModel : profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
14062
+ const profileHasThinkingTokens = typeof profile.claudeCode.maxThinkingTokens === "number" && profile.claudeCode.maxThinkingTokens > 0;
14063
+ const reasoningEffort = config.agent.perExecutableReasoningEffort?.[profileName] ?? profile.claudeCode.reasoningEffort ?? (profileHasThinkingTokens ? void 0 : config.agent.reasoningEffort);
13953
14064
  let model;
13954
14065
  try {
13955
14066
  model = parseProviderModel(modelSpec);
@@ -14025,6 +14136,7 @@ async function runExecutable(profileName, input) {
14025
14136
  pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
14026
14137
  agents,
14027
14138
  maxTurns: profile.claudeCode.maxTurns,
14139
+ reasoningEffort,
14028
14140
  maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
14029
14141
  maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
14030
14142
  // DISCIPLINE leads so the stable, role-agnostic block sits at the front
@@ -14382,7 +14494,7 @@ function resolveProfilePath(profileName) {
14382
14494
  // fallback
14383
14495
  ];
14384
14496
  for (const c of candidates) {
14385
- if (fs40.existsSync(c)) return c;
14497
+ if (fs41.existsSync(c)) return c;
14386
14498
  }
14387
14499
  return candidates[0];
14388
14500
  }
@@ -14481,7 +14593,7 @@ function resolveShellTimeoutMs(entry) {
14481
14593
  async function runShellEntry(entry, ctx, profile) {
14482
14594
  const shellName = entry.shell;
14483
14595
  const shellPath = path38.join(profile.dir, shellName);
14484
- if (!fs40.existsSync(shellPath)) {
14596
+ if (!fs41.existsSync(shellPath)) {
14485
14597
  ctx.skipAgent = true;
14486
14598
  ctx.output.exitCode = 99;
14487
14599
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -14519,14 +14631,14 @@ async function runShellEntry(entry, ctx, profile) {
14519
14631
  let killTimer;
14520
14632
  let escalateTimer;
14521
14633
  const result = await new Promise(
14522
- (resolve6) => {
14634
+ (resolve7) => {
14523
14635
  let settled = false;
14524
14636
  const settle = (code, signal, spawnErr) => {
14525
14637
  if (settled) return;
14526
14638
  settled = true;
14527
14639
  if (killTimer) clearTimeout(killTimer);
14528
14640
  if (escalateTimer) clearTimeout(escalateTimer);
14529
- resolve6({ code, signal, spawnErr });
14641
+ resolve7({ code, signal, spawnErr });
14530
14642
  };
14531
14643
  child.on("error", (err) => settle(null, null, err));
14532
14644
  child.on("close", (code, signal) => settle(code, signal));
@@ -14562,6 +14674,11 @@ async function runShellEntry(entry, ctx, profile) {
14562
14674
  if (prUrlMatch?.[1]) ctx.output.prUrl = prUrlMatch[1].trim();
14563
14675
  const reasonMatch = stdout.match(/^KODY_REASON=(.+)$/m);
14564
14676
  if (reasonMatch?.[1]) ctx.output.reason = reasonMatch[1].trim();
14677
+ const dutyReports = parseDutyReportsFromText(stdout);
14678
+ if (dutyReports.length > 0) {
14679
+ const prior = Array.isArray(ctx.data.dutyReports) ? ctx.data.dutyReports : [];
14680
+ ctx.data.dutyReports = [...prior, ...dutyReports];
14681
+ }
14565
14682
  if (timedOut) {
14566
14683
  ctx.skipAgent = true;
14567
14684
  const seconds = Math.round(timeoutMs / 1e3);
@@ -14611,6 +14728,7 @@ var init_executor = __esm({
14611
14728
  init_config();
14612
14729
  init_container();
14613
14730
  init_discipline();
14731
+ init_dutyReport();
14614
14732
  init_events();
14615
14733
  init_lifecycleLabels();
14616
14734
  init_litellm();
@@ -14621,7 +14739,7 @@ var init_executor = __esm({
14621
14739
  init_subagents();
14622
14740
  init_task_artifacts();
14623
14741
  init_tools();
14624
- MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set(["commitAndPush", "ensurePr"]);
14742
+ MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set(["commitAndPush", "ensurePr", "applyDutyReports"]);
14625
14743
  MAX_CHAIN_HOPS = 60;
14626
14744
  DEFAULT_SHELL_TIMEOUT_MS = 3e5;
14627
14745
  SIGKILL_GRACE_MS = 5e3;
@@ -14733,7 +14851,7 @@ async function runJob(job, base) {
14733
14851
  }
14734
14852
  function loadDutyContext(slug, cwd) {
14735
14853
  if (!slug) return null;
14736
- return readDutyFolder(path39.join(cwd, ".kody", "duties"), slug) ?? readDutyFolder(getProjectDutiesRoot(), slug) ?? readDutyFolder(getBuiltinDutiesRoot(), slug);
14854
+ return resolveDutyFolder(slug, path39.join(cwd, ".kody", "duties"));
14737
14855
  }
14738
14856
  function mintInstantJob(dispatch2, opts) {
14739
14857
  return {
@@ -14762,7 +14880,6 @@ var DEFAULT_INSTANT_PERSONA, localJobSeq, InvalidJobError;
14762
14880
  var init_job = __esm({
14763
14881
  "src/job.ts"() {
14764
14882
  "use strict";
14765
- init_dutyFolders();
14766
14883
  init_executor();
14767
14884
  init_registry();
14768
14885
  init_jobIdentity();
@@ -14885,7 +15002,7 @@ function translateOpenAISseToBrain(opts) {
14885
15002
  }
14886
15003
 
14887
15004
  // src/servers/brain-serve.ts
14888
- import * as fs43 from "fs";
15005
+ import * as fs44 from "fs";
14889
15006
  import { createServer } from "http";
14890
15007
  import * as path42 from "path";
14891
15008
 
@@ -14893,12 +15010,12 @@ import * as path42 from "path";
14893
15010
  init_agent();
14894
15011
  init_registry();
14895
15012
  init_task_artifacts();
14896
- import * as fs12 from "fs";
14897
- import * as path12 from "path";
15013
+ import * as fs13 from "fs";
15014
+ import * as path13 from "path";
14898
15015
 
14899
15016
  // src/chat/attachments.ts
14900
- import * as fs9 from "fs";
14901
- import * as path9 from "path";
15017
+ import * as fs10 from "fs";
15018
+ import * as path10 from "path";
14902
15019
  var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
14903
15020
  var EXT_BY_MIME = {
14904
15021
  "image/png": "png",
@@ -14914,7 +15031,7 @@ function extFor(mime) {
14914
15031
  return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
14915
15032
  }
14916
15033
  function attachmentsDir(cwd, sessionId) {
14917
- return path9.join(cwd, ".kody", "tmp", "attachments", sessionId);
15034
+ return path10.join(cwd, ".kody", "tmp", "attachments", sessionId);
14918
15035
  }
14919
15036
  function prepareAttachments(turns, cwd, sessionId) {
14920
15037
  const imagePaths = [];
@@ -14931,11 +15048,11 @@ function prepareAttachments(turns, cwd, sessionId) {
14931
15048
  if (!isImage) return `[File: ${name}]`;
14932
15049
  try {
14933
15050
  if (!dirEnsured) {
14934
- fs9.mkdirSync(dir, { recursive: true });
15051
+ fs10.mkdirSync(dir, { recursive: true });
14935
15052
  dirEnsured = true;
14936
15053
  }
14937
- const filePath = path9.join(dir, `${imageCounter}.${extFor(mime)}`);
14938
- fs9.writeFileSync(filePath, Buffer.from(data, "base64"));
15054
+ const filePath = path10.join(dir, `${imageCounter}.${extFor(mime)}`);
15055
+ fs10.writeFileSync(filePath, Buffer.from(data, "base64"));
14939
15056
  imageCounter += 1;
14940
15057
  imagePaths.push(filePath);
14941
15058
  return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it.]`;
@@ -14951,10 +15068,10 @@ function prepareAttachments(turns, cwd, sessionId) {
14951
15068
  }
14952
15069
 
14953
15070
  // src/chat/events.ts
14954
- import * as fs10 from "fs";
14955
- import * as path10 from "path";
15071
+ import * as fs11 from "fs";
15072
+ import * as path11 from "path";
14956
15073
  function eventsFilePath(cwd, sessionId) {
14957
- return path10.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
15074
+ return path11.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
14958
15075
  }
14959
15076
  var FileSink = class {
14960
15077
  constructor(file) {
@@ -14962,8 +15079,8 @@ var FileSink = class {
14962
15079
  }
14963
15080
  file;
14964
15081
  async emit(event) {
14965
- fs10.mkdirSync(path10.dirname(this.file), { recursive: true });
14966
- fs10.appendFileSync(this.file, `${JSON.stringify(event)}
15082
+ fs11.mkdirSync(path11.dirname(this.file), { recursive: true });
15083
+ fs11.appendFileSync(this.file, `${JSON.stringify(event)}
14967
15084
  `);
14968
15085
  }
14969
15086
  };
@@ -15017,14 +15134,14 @@ function makeRunId(sessionId, suffix) {
15017
15134
  }
15018
15135
 
15019
15136
  // src/chat/session.ts
15020
- import * as fs11 from "fs";
15021
- import * as path11 from "path";
15137
+ import * as fs12 from "fs";
15138
+ import * as path12 from "path";
15022
15139
  function sessionFilePath(cwd, sessionId) {
15023
- return path11.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
15140
+ return path12.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
15024
15141
  }
15025
15142
  function readMeta(file) {
15026
- if (!fs11.existsSync(file)) return null;
15027
- const raw = fs11.readFileSync(file, "utf-8");
15143
+ if (!fs12.existsSync(file)) return null;
15144
+ const raw = fs12.readFileSync(file, "utf-8");
15028
15145
  const firstLine2 = raw.split("\n", 1)[0]?.trim();
15029
15146
  if (!firstLine2) return null;
15030
15147
  try {
@@ -15037,8 +15154,8 @@ function readMeta(file) {
15037
15154
  }
15038
15155
  }
15039
15156
  function readSession(file) {
15040
- if (!fs11.existsSync(file)) return [];
15041
- const raw = fs11.readFileSync(file, "utf-8").trim();
15157
+ if (!fs12.existsSync(file)) return [];
15158
+ const raw = fs12.readFileSync(file, "utf-8").trim();
15042
15159
  if (!raw) return [];
15043
15160
  const turns = [];
15044
15161
  for (const line of raw.split("\n")) {
@@ -15054,14 +15171,14 @@ function readSession(file) {
15054
15171
  return turns;
15055
15172
  }
15056
15173
  function appendTurn(file, turn) {
15057
- fs11.mkdirSync(path11.dirname(file), { recursive: true });
15174
+ fs12.mkdirSync(path12.dirname(file), { recursive: true });
15058
15175
  const line = JSON.stringify({
15059
15176
  role: turn.role,
15060
15177
  content: turn.content,
15061
15178
  timestamp: turn.timestamp,
15062
15179
  toolCalls: turn.toolCalls ?? []
15063
15180
  });
15064
- fs11.appendFileSync(file, `${line}
15181
+ fs12.appendFileSync(file, `${line}
15065
15182
  `);
15066
15183
  }
15067
15184
  function seedInitialMessage(file, message) {
@@ -15180,7 +15297,7 @@ function buildExecutableCatalog() {
15180
15297
  const entries = [];
15181
15298
  for (const { name, profilePath } of discovered) {
15182
15299
  try {
15183
- const raw = JSON.parse(fs12.readFileSync(profilePath, "utf-8"));
15300
+ const raw = JSON.parse(fs13.readFileSync(profilePath, "utf-8"));
15184
15301
  const describe = typeof raw.describe === "string" ? raw.describe : "";
15185
15302
  const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
15186
15303
  entries.push({ name, describe: firstSentence.trim() });
@@ -15349,10 +15466,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
15349
15466
  var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
15350
15467
  var MAX_INDEX_BYTES = 8e3;
15351
15468
  function readMemoryIndexBlock(cwd) {
15352
- const indexPath = path12.join(cwd, MEMORY_INDEX_REL);
15469
+ const indexPath = path13.join(cwd, MEMORY_INDEX_REL);
15353
15470
  let raw;
15354
15471
  try {
15355
- raw = fs12.readFileSync(indexPath, "utf-8");
15472
+ raw = fs13.readFileSync(indexPath, "utf-8");
15356
15473
  } catch {
15357
15474
  return "";
15358
15475
  }
@@ -15370,17 +15487,17 @@ function readMemoryIndexBlock(cwd) {
15370
15487
  var CONTEXT_DIR_REL = ".kody/context";
15371
15488
  var MAX_CONTEXT_BYTES = 12e3;
15372
15489
  function readContextBlock(cwd) {
15373
- const dir = path12.join(cwd, CONTEXT_DIR_REL);
15490
+ const dir = path13.join(cwd, CONTEXT_DIR_REL);
15374
15491
  let files;
15375
15492
  try {
15376
- files = fs12.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
15493
+ files = fs13.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
15377
15494
  } catch {
15378
15495
  return "";
15379
15496
  }
15380
15497
  const sections = [];
15381
15498
  for (const file of files) {
15382
15499
  try {
15383
- const content = fs12.readFileSync(path12.join(dir, file), "utf-8").trim();
15500
+ const content = fs13.readFileSync(path13.join(dir, file), "utf-8").trim();
15384
15501
  if (content) sections.push(`### ${file.replace(/\.md$/, "")}
15385
15502
 
15386
15503
  ${content}`);
@@ -15403,10 +15520,10 @@ _\u2026 (context truncated; see \`.kody/context/\` for the full text)_` : joined
15403
15520
  var INSTRUCTIONS_REL = ".kody/instructions.md";
15404
15521
  var MAX_INSTRUCTIONS_BYTES = 8e3;
15405
15522
  function readInstructionsBlock(cwd) {
15406
- const instructionsPath = path12.join(cwd, INSTRUCTIONS_REL);
15523
+ const instructionsPath = path13.join(cwd, INSTRUCTIONS_REL);
15407
15524
  let raw;
15408
15525
  try {
15409
- raw = fs12.readFileSync(instructionsPath, "utf-8");
15526
+ raw = fs13.readFileSync(instructionsPath, "utf-8");
15410
15527
  } catch {
15411
15528
  return "";
15412
15529
  }
@@ -15428,8 +15545,8 @@ _\u2026 (instructions truncated)_` : trimmed;
15428
15545
  init_config();
15429
15546
 
15430
15547
  // src/kody-cli.ts
15431
- import { execFileSync as execFileSync26 } from "child_process";
15432
- import * as fs41 from "fs";
15548
+ import { execFileSync as execFileSync27 } from "child_process";
15549
+ import * as fs42 from "fs";
15433
15550
  import * as path40 from "path";
15434
15551
 
15435
15552
  // src/app-auth.ts
@@ -15507,7 +15624,7 @@ init_config();
15507
15624
 
15508
15625
  // src/dispatch.ts
15509
15626
  init_config();
15510
- import * as fs13 from "fs";
15627
+ import * as fs14 from "fs";
15511
15628
 
15512
15629
  // src/cron-match.ts
15513
15630
  var FIELD_BOUNDS = [
@@ -15609,10 +15726,10 @@ function autoDispatch(opts) {
15609
15726
  }
15610
15727
  const eventName = process.env.GITHUB_EVENT_NAME;
15611
15728
  const eventPath = process.env.GITHUB_EVENT_PATH;
15612
- if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
15729
+ if (!eventName || !eventPath || !fs14.existsSync(eventPath)) return null;
15613
15730
  let event = {};
15614
15731
  try {
15615
- event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
15732
+ event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
15616
15733
  } catch {
15617
15734
  return null;
15618
15735
  }
@@ -15723,7 +15840,7 @@ function autoDispatchTyped(opts) {
15723
15840
  if (legacy) return { kind: "route", ...legacy };
15724
15841
  const eventName = process.env.GITHUB_EVENT_NAME;
15725
15842
  const eventPath = process.env.GITHUB_EVENT_PATH;
15726
- if (!eventName || !eventPath || !fs13.existsSync(eventPath)) {
15843
+ if (!eventName || !eventPath || !fs14.existsSync(eventPath)) {
15727
15844
  return { kind: "silent", reason: "no GHA event context" };
15728
15845
  }
15729
15846
  if (eventName !== "issue_comment") {
@@ -15731,7 +15848,7 @@ function autoDispatchTyped(opts) {
15731
15848
  }
15732
15849
  let event = {};
15733
15850
  try {
15734
- event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
15851
+ event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
15735
15852
  } catch {
15736
15853
  return { kind: "silent", reason: "GHA event payload unreadable" };
15737
15854
  }
@@ -15775,7 +15892,7 @@ function dispatchScheduledWatches(opts) {
15775
15892
  for (const exe of listExecutables()) {
15776
15893
  let raw;
15777
15894
  try {
15778
- raw = fs13.readFileSync(exe.profilePath, "utf-8");
15895
+ raw = fs14.readFileSync(exe.profilePath, "utf-8");
15779
15896
  } catch {
15780
15897
  continue;
15781
15898
  }
@@ -16025,14 +16142,14 @@ async function resolveAuthToken(env = process.env) {
16025
16142
  return void 0;
16026
16143
  }
16027
16144
  function detectPackageManager2(cwd) {
16028
- if (fs41.existsSync(path40.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
16029
- if (fs41.existsSync(path40.join(cwd, "yarn.lock"))) return "yarn";
16030
- if (fs41.existsSync(path40.join(cwd, "bun.lockb"))) return "bun";
16145
+ if (fs42.existsSync(path40.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
16146
+ if (fs42.existsSync(path40.join(cwd, "yarn.lock"))) return "yarn";
16147
+ if (fs42.existsSync(path40.join(cwd, "bun.lockb"))) return "bun";
16031
16148
  return "npm";
16032
16149
  }
16033
16150
  function shellOut(cmd, args, cwd, stream = true) {
16034
16151
  try {
16035
- execFileSync26(cmd, args, {
16152
+ execFileSync27(cmd, args, {
16036
16153
  cwd,
16037
16154
  stdio: stream ? "inherit" : "pipe",
16038
16155
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -16045,7 +16162,7 @@ function shellOut(cmd, args, cwd, stream = true) {
16045
16162
  }
16046
16163
  function isOnPath(bin) {
16047
16164
  try {
16048
- execFileSync26("which", [bin], { stdio: "pipe" });
16165
+ execFileSync27("which", [bin], { stdio: "pipe" });
16049
16166
  return true;
16050
16167
  } catch {
16051
16168
  return false;
@@ -16086,7 +16203,7 @@ function installLitellmIfNeeded(cwd) {
16086
16203
  } catch {
16087
16204
  }
16088
16205
  try {
16089
- execFileSync26("python3", ["-c", "import litellm"], { stdio: "pipe" });
16206
+ execFileSync27("python3", ["-c", "import litellm"], { stdio: "pipe" });
16090
16207
  process.stdout.write("\u2192 kody: litellm already installed\n");
16091
16208
  return 0;
16092
16209
  } catch {
@@ -16096,16 +16213,16 @@ function installLitellmIfNeeded(cwd) {
16096
16213
  }
16097
16214
  function configureGitIdentity(cwd) {
16098
16215
  try {
16099
- const name = execFileSync26("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
16216
+ const name = execFileSync27("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
16100
16217
  if (name) return;
16101
16218
  } catch {
16102
16219
  }
16103
16220
  try {
16104
- execFileSync26("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
16221
+ execFileSync27("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
16105
16222
  } catch {
16106
16223
  }
16107
16224
  try {
16108
- execFileSync26("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
16225
+ execFileSync27("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
16109
16226
  cwd,
16110
16227
  stdio: "pipe"
16111
16228
  });
@@ -16117,8 +16234,8 @@ function postFailureTail(issueNumber, cwd, reason) {
16117
16234
  const logPath = path40.join(cwd, ".kody", "last-run.jsonl");
16118
16235
  let tail = "";
16119
16236
  try {
16120
- if (fs41.existsSync(logPath)) {
16121
- const content = fs41.readFileSync(logPath, "utf-8");
16237
+ if (fs42.existsSync(logPath)) {
16238
+ const content = fs42.readFileSync(logPath, "utf-8");
16122
16239
  tail = content.slice(-3e3);
16123
16240
  }
16124
16241
  } catch {
@@ -16156,9 +16273,9 @@ async function runCi(argv) {
16156
16273
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
16157
16274
  let manualWorkflowDispatch = false;
16158
16275
  let forceRunAction = null;
16159
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs41.existsSync(dispatchEventPath)) {
16276
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs42.existsSync(dispatchEventPath)) {
16160
16277
  try {
16161
- const evt = JSON.parse(fs41.readFileSync(dispatchEventPath, "utf-8"));
16278
+ const evt = JSON.parse(fs42.readFileSync(dispatchEventPath, "utf-8"));
16162
16279
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
16163
16280
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
16164
16281
  const dutyInput = String(evt?.inputs?.duty ?? evt?.inputs?.executable ?? "").trim();
@@ -16458,7 +16575,7 @@ init_litellm();
16458
16575
  init_repoWorkspace();
16459
16576
 
16460
16577
  // src/scripts/brainTurnLog.ts
16461
- import * as fs42 from "fs";
16578
+ import * as fs43 from "fs";
16462
16579
  import * as path41 from "path";
16463
16580
  var live = /* @__PURE__ */ new Map();
16464
16581
  function eventsPath(dir, chatId) {
@@ -16466,8 +16583,8 @@ function eventsPath(dir, chatId) {
16466
16583
  }
16467
16584
  function lastPersistedSeq(dir, chatId) {
16468
16585
  const p = eventsPath(dir, chatId);
16469
- if (!fs42.existsSync(p)) return 0;
16470
- const lines = fs42.readFileSync(p, "utf-8").split("\n").filter(Boolean);
16586
+ if (!fs43.existsSync(p)) return 0;
16587
+ const lines = fs43.readFileSync(p, "utf-8").split("\n").filter(Boolean);
16471
16588
  if (lines.length === 0) return 0;
16472
16589
  try {
16473
16590
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -16477,9 +16594,9 @@ function lastPersistedSeq(dir, chatId) {
16477
16594
  }
16478
16595
  function readSince(dir, chatId, since) {
16479
16596
  const p = eventsPath(dir, chatId);
16480
- if (!fs42.existsSync(p)) return [];
16597
+ if (!fs43.existsSync(p)) return [];
16481
16598
  const out = [];
16482
- for (const line of fs42.readFileSync(p, "utf-8").split("\n")) {
16599
+ for (const line of fs43.readFileSync(p, "utf-8").split("\n")) {
16483
16600
  if (!line) continue;
16484
16601
  try {
16485
16602
  const rec = JSON.parse(line);
@@ -16505,12 +16622,12 @@ function beginTurn(dir, chatId) {
16505
16622
  };
16506
16623
  live.set(chatId, state);
16507
16624
  const p = eventsPath(dir, chatId);
16508
- fs42.mkdirSync(path41.dirname(p), { recursive: true });
16625
+ fs43.mkdirSync(path41.dirname(p), { recursive: true });
16509
16626
  return (event) => {
16510
16627
  state.seq += 1;
16511
16628
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
16512
16629
  try {
16513
- fs42.appendFileSync(p, `${JSON.stringify(rec)}
16630
+ fs43.appendFileSync(p, `${JSON.stringify(rec)}
16514
16631
  `);
16515
16632
  } catch (err) {
16516
16633
  process.stderr.write(
@@ -16549,7 +16666,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
16549
16666
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
16550
16667
  };
16551
16668
  try {
16552
- fs42.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
16669
+ fs43.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
16553
16670
  `);
16554
16671
  } catch {
16555
16672
  }
@@ -16642,17 +16759,17 @@ function authOk(req, expected) {
16642
16759
  return false;
16643
16760
  }
16644
16761
  function readJsonBody(req) {
16645
- return new Promise((resolve6, reject) => {
16762
+ return new Promise((resolve7, reject) => {
16646
16763
  const chunks = [];
16647
16764
  req.on("data", (c) => chunks.push(c));
16648
16765
  req.on("end", () => {
16649
16766
  const raw = Buffer.concat(chunks).toString("utf-8");
16650
16767
  if (!raw.trim()) {
16651
- resolve6({});
16768
+ resolve7({});
16652
16769
  return;
16653
16770
  }
16654
16771
  try {
16655
- resolve6(JSON.parse(raw));
16772
+ resolve7(JSON.parse(raw));
16656
16773
  } catch (err) {
16657
16774
  reject(err instanceof Error ? err : new Error(String(err)));
16658
16775
  }
@@ -16780,7 +16897,7 @@ async function handleChatTurn(req, res, chatId, opts) {
16780
16897
  const repo = strField(body, "repo");
16781
16898
  const repoToken = strField(body, "repoToken");
16782
16899
  const sessionFile = sessionFilePath(opts.cwd, chatId);
16783
- fs43.mkdirSync(path42.dirname(sessionFile), { recursive: true });
16900
+ fs44.mkdirSync(path42.dirname(sessionFile), { recursive: true });
16784
16901
  appendTurn(sessionFile, {
16785
16902
  role: "user",
16786
16903
  content: message,
@@ -16901,11 +17018,11 @@ async function brainServe(opts) {
16901
17018
  model,
16902
17019
  litellmUrl
16903
17020
  });
16904
- await new Promise((resolve6) => {
17021
+ await new Promise((resolve7) => {
16905
17022
  server.listen(port, "0.0.0.0", () => {
16906
17023
  process.stdout.write(`[brain-serve] listening on 0.0.0.0:${port} (cwd=${opts.cwd})
16907
17024
  `);
16908
- resolve6();
17025
+ resolve7();
16909
17026
  });
16910
17027
  });
16911
17028
  const shutdown = (signal) => {
@@ -17155,14 +17272,14 @@ async function startBrainProxy(opts) {
17155
17272
  const { httpServer, handler } = buildBrainProxy(opts);
17156
17273
  const port = opts.port ?? 0;
17157
17274
  const host = opts.host ?? "127.0.0.1";
17158
- await new Promise((resolve6) => httpServer.listen(port, host, () => resolve6()));
17275
+ await new Promise((resolve7) => httpServer.listen(port, host, () => resolve7()));
17159
17276
  const addr = httpServer.address();
17160
17277
  return {
17161
17278
  httpServer,
17162
17279
  port: addr.port,
17163
17280
  url: `http://${host}:${addr.port}`,
17164
- stop: () => new Promise((resolve6) => {
17165
- httpServer.close(() => resolve6());
17281
+ stop: () => new Promise((resolve7) => {
17282
+ httpServer.close(() => resolve7());
17166
17283
  }),
17167
17284
  handler
17168
17285
  };
@@ -17312,23 +17429,23 @@ function buildMcpHttpServer(opts) {
17312
17429
  httpServer,
17313
17430
  routes,
17314
17431
  port,
17315
- stop: () => new Promise((resolve6) => {
17432
+ stop: () => new Promise((resolve7) => {
17316
17433
  let pending = transports.size;
17317
17434
  if (pending === 0) {
17318
- httpServer.close(() => resolve6());
17435
+ httpServer.close(() => resolve7());
17319
17436
  return;
17320
17437
  }
17321
17438
  for (const transport of transports.values()) {
17322
17439
  void transport.close().finally(() => {
17323
17440
  pending--;
17324
- if (pending === 0) httpServer.close(() => resolve6());
17441
+ if (pending === 0) httpServer.close(() => resolve7());
17325
17442
  });
17326
17443
  }
17327
17444
  })
17328
17445
  };
17329
17446
  }
17330
17447
  function listenMcpHttpServer(server, host = "127.0.0.1") {
17331
- return new Promise((resolve6, reject) => {
17448
+ return new Promise((resolve7, reject) => {
17332
17449
  server.httpServer.once("error", reject);
17333
17450
  server.httpServer.listen(server.port, host, () => {
17334
17451
  server.httpServer.off("error", reject);
@@ -17336,7 +17453,7 @@ function listenMcpHttpServer(server, host = "127.0.0.1") {
17336
17453
  if (addr && typeof addr === "object") {
17337
17454
  server.port = addr.port;
17338
17455
  }
17339
- resolve6();
17456
+ resolve7();
17340
17457
  });
17341
17458
  });
17342
17459
  }
@@ -17419,18 +17536,18 @@ async function loadConfigSafe() {
17419
17536
  }
17420
17537
 
17421
17538
  // src/chat-cli.ts
17422
- import { execFileSync as execFileSync29 } from "child_process";
17423
- import * as fs45 from "fs";
17539
+ import { execFileSync as execFileSync30 } from "child_process";
17540
+ import * as fs46 from "fs";
17424
17541
  import * as path44 from "path";
17425
17542
 
17426
17543
  // src/chat/modes/interactive.ts
17427
17544
  init_issue();
17428
- import { execFileSync as execFileSync28 } from "child_process";
17429
- import * as fs44 from "fs";
17545
+ import { execFileSync as execFileSync29 } from "child_process";
17546
+ import * as fs45 from "fs";
17430
17547
  import * as path43 from "path";
17431
17548
 
17432
17549
  // src/chat/inbox.ts
17433
- import { execFileSync as execFileSync27 } from "child_process";
17550
+ import { execFileSync as execFileSync28 } from "child_process";
17434
17551
  var DEFAULT_POLL_MS = 3e3;
17435
17552
  async function waitForNextUserMessage(opts) {
17436
17553
  const pollMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
@@ -17447,13 +17564,13 @@ async function waitForNextUserMessage(opts) {
17447
17564
  try {
17448
17565
  const branch = currentBranch(opts.cwd);
17449
17566
  if (branch) {
17450
- execFileSync27("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
17451
- execFileSync27("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
17567
+ execFileSync28("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
17568
+ execFileSync28("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
17452
17569
  cwd: opts.cwd,
17453
17570
  stdio: "pipe"
17454
17571
  });
17455
17572
  } else {
17456
- execFileSync27("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
17573
+ execFileSync28("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
17457
17574
  }
17458
17575
  } catch (err) {
17459
17576
  const msg = err instanceof Error ? err.message : String(err);
@@ -17475,11 +17592,11 @@ async function waitForNextUserMessage(opts) {
17475
17592
  }
17476
17593
  }
17477
17594
  function sleep2(ms) {
17478
- return new Promise((resolve6) => setTimeout(resolve6, ms));
17595
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
17479
17596
  }
17480
17597
  function currentBranch(cwd) {
17481
17598
  try {
17482
- const out = execFileSync27("git", ["symbolic-ref", "--short", "HEAD"], {
17599
+ const out = execFileSync28("git", ["symbolic-ref", "--short", "HEAD"], {
17483
17600
  cwd,
17484
17601
  stdio: ["ignore", "pipe", "ignore"]
17485
17602
  });
@@ -17589,7 +17706,7 @@ function findNextUserTurn(turns, fromIdx) {
17589
17706
  function commitTurn(cwd, sessionId, _verbose) {
17590
17707
  const sessionRel = path43.relative(cwd, sessionFilePath(cwd, sessionId));
17591
17708
  const eventsRel = path43.relative(cwd, eventsFilePath(cwd, sessionId));
17592
- const rels = [sessionRel, eventsRel].filter((p) => fs44.existsSync(path43.join(cwd, p)));
17709
+ const rels = [sessionRel, eventsRel].filter((p) => fs45.existsSync(path43.join(cwd, p)));
17593
17710
  if (rels.length === 0) return;
17594
17711
  const repository = process.env.GITHUB_REPOSITORY;
17595
17712
  if (!repository) {
@@ -17602,7 +17719,7 @@ function commitTurn(cwd, sessionId, _verbose) {
17602
17719
  const branch = defaultBranch(cwd) ?? "main";
17603
17720
  for (const rel of rels) {
17604
17721
  const repoPath = rel.split(path43.sep).join("/");
17605
- const localText = fs44.readFileSync(path43.join(cwd, rel), "utf-8");
17722
+ const localText = fs45.readFileSync(path43.join(cwd, rel), "utf-8");
17606
17723
  putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
17607
17724
  }
17608
17725
  }
@@ -17663,7 +17780,7 @@ function putJsonlViaContents(repository, branch, repoPath, localText, sessionId,
17663
17780
  }
17664
17781
  function defaultBranch(cwd) {
17665
17782
  try {
17666
- const out = execFileSync28("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], {
17783
+ const out = execFileSync29("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], {
17667
17784
  cwd,
17668
17785
  stdio: ["ignore", "pipe", "ignore"]
17669
17786
  });
@@ -17752,12 +17869,12 @@ function commitChatFiles(cwd, sessionId, verbose) {
17752
17869
  const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
17753
17870
  const tasksDir = path44.join(".kody", "tasks", safeSession);
17754
17871
  const candidatePaths = [sessionFile, eventsFile, tasksDir];
17755
- const paths = candidatePaths.filter((p) => fs45.existsSync(path44.join(cwd, p)));
17872
+ const paths = candidatePaths.filter((p) => fs46.existsSync(path44.join(cwd, p)));
17756
17873
  if (paths.length === 0) return;
17757
17874
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
17758
17875
  try {
17759
- execFileSync29("git", ["add", "-f", ...paths], opts);
17760
- execFileSync29("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
17876
+ execFileSync30("git", ["add", "-f", ...paths], opts);
17877
+ execFileSync30("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
17761
17878
  } catch (err) {
17762
17879
  const msg = err instanceof Error ? err.message : String(err);
17763
17880
  process.stderr.write(`[kody:chat] commit skipped: ${msg}
@@ -17849,7 +17966,7 @@ ${CHAT_HELP}`);
17849
17966
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
17850
17967
  const meta = readMeta(sessionFile);
17851
17968
  process.stdout.write(
17852
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs45.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
17969
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs46.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
17853
17970
  `
17854
17971
  );
17855
17972
  try {
@@ -18590,14 +18707,14 @@ function sendJson2(res, status, body) {
18590
18707
  res.end(JSON.stringify(body));
18591
18708
  }
18592
18709
  function readJsonBody2(req) {
18593
- return new Promise((resolve6, reject) => {
18710
+ return new Promise((resolve7, reject) => {
18594
18711
  const chunks = [];
18595
18712
  req.on("data", (c) => chunks.push(c));
18596
18713
  req.on("end", () => {
18597
18714
  const raw = Buffer.concat(chunks).toString("utf-8");
18598
- if (!raw.trim()) return resolve6({});
18715
+ if (!raw.trim()) return resolve7({});
18599
18716
  try {
18600
- resolve6(JSON.parse(raw));
18717
+ resolve7(JSON.parse(raw));
18601
18718
  } catch (err) {
18602
18719
  reject(err instanceof Error ? err : new Error(String(err)));
18603
18720
  }
@@ -18723,10 +18840,10 @@ async function poolServe() {
18723
18840
  }
18724
18841
  });
18725
18842
  const apiHost = process.env.POOL_API_HOST ?? "::";
18726
- await new Promise((resolve6) => {
18843
+ await new Promise((resolve7) => {
18727
18844
  server.listen(apiPort, apiHost, () => {
18728
18845
  log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
18729
- resolve6();
18846
+ resolve7();
18730
18847
  });
18731
18848
  });
18732
18849
  const shutdown = (signal) => {
@@ -18744,7 +18861,7 @@ async function poolServe() {
18744
18861
 
18745
18862
  // src/servers/runner-serve.ts
18746
18863
  import { spawn as spawn8 } from "child_process";
18747
- import * as fs46 from "fs";
18864
+ import * as fs47 from "fs";
18748
18865
  import { createServer as createServer5 } from "http";
18749
18866
  var DEFAULT_PORT2 = 8080;
18750
18867
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -18765,17 +18882,17 @@ function authOk2(req, expected) {
18765
18882
  return false;
18766
18883
  }
18767
18884
  function readJsonBody3(req) {
18768
- return new Promise((resolve6, reject) => {
18885
+ return new Promise((resolve7, reject) => {
18769
18886
  const chunks = [];
18770
18887
  req.on("data", (c) => chunks.push(c));
18771
18888
  req.on("end", () => {
18772
18889
  const raw = Buffer.concat(chunks).toString("utf-8");
18773
18890
  if (!raw.trim()) {
18774
- resolve6({});
18891
+ resolve7({});
18775
18892
  return;
18776
18893
  }
18777
18894
  try {
18778
- resolve6(JSON.parse(raw));
18895
+ resolve7(JSON.parse(raw));
18779
18896
  } catch (err) {
18780
18897
  reject(err instanceof Error ? err : new Error(String(err)));
18781
18898
  }
@@ -18824,8 +18941,8 @@ async function defaultRunJob(job) {
18824
18941
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
18825
18942
  const branch = job.ref ?? "main";
18826
18943
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
18827
- fs46.rmSync(workdir, { recursive: true, force: true });
18828
- fs46.mkdirSync(workdir, { recursive: true });
18944
+ fs47.rmSync(workdir, { recursive: true, force: true });
18945
+ fs47.mkdirSync(workdir, { recursive: true });
18829
18946
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
18830
18947
  const interactive = job.mode === "interactive";
18831
18948
  const scheduled = job.mode === "scheduled";
@@ -18854,13 +18971,13 @@ async function defaultRunJob(job) {
18854
18971
  ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
18855
18972
  ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
18856
18973
  };
18857
- const run = (cmd, args, cwd) => new Promise((resolve6) => {
18974
+ const run = (cmd, args, cwd) => new Promise((resolve7) => {
18858
18975
  const child = spawn8(cmd, args, { stdio: "inherit", env: childEnv, cwd });
18859
- child.on("exit", (code) => resolve6(code ?? 0));
18976
+ child.on("exit", (code) => resolve7(code ?? 0));
18860
18977
  child.on("error", (err) => {
18861
18978
  process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
18862
18979
  `);
18863
- resolve6(1);
18980
+ resolve7(1);
18864
18981
  });
18865
18982
  });
18866
18983
  process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
@@ -18937,11 +19054,11 @@ async function runnerServe() {
18937
19054
  const port = Number(process.env.PORT ?? DEFAULT_PORT2);
18938
19055
  const server = buildServer2({ apiKey });
18939
19056
  const host = process.env.RUNNER_HOST ?? "::";
18940
- await new Promise((resolve6) => {
19057
+ await new Promise((resolve7) => {
18941
19058
  server.listen(port, host, () => {
18942
19059
  process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
18943
19060
  `);
18944
- resolve6();
19061
+ resolve7();
18945
19062
  });
18946
19063
  });
18947
19064
  const shutdown = (signal) => {
@@ -18960,7 +19077,7 @@ async function runnerServe() {
18960
19077
  init_config();
18961
19078
  init_litellm();
18962
19079
  import { spawn as spawn9 } from "child_process";
18963
- function parseTarget(positional) {
19080
+ function parseTarget2(positional) {
18964
19081
  if (!Array.isArray(positional) || positional.length === 0) return "none";
18965
19082
  const first = String(positional[0]).toLowerCase();
18966
19083
  if (first === "vscode" || first === "code") return "vscode";
@@ -18975,7 +19092,7 @@ function buildProxyEnv(url) {
18975
19092
  };
18976
19093
  }
18977
19094
  async function serve(opts) {
18978
- const target = parseTarget(opts.args);
19095
+ const target = parseTarget2(opts.args);
18979
19096
  const model = parseProviderModel(opts.config.agent.model);
18980
19097
  const usesProxy = needsLitellmProxy(model);
18981
19098
  let handle = null;
@@ -19010,14 +19127,14 @@ async function serve(opts) {
19010
19127
  `);
19011
19128
  const args = ["--dangerously-skip-permissions", "--model", model.model];
19012
19129
  const child = spawn9("claude", args, { stdio: "inherit", env: editorEnv, cwd: opts.cwd });
19013
- const exitCode = await new Promise((resolve6) => {
19014
- child.on("exit", (code) => resolve6(code ?? 0));
19130
+ const exitCode = await new Promise((resolve7) => {
19131
+ child.on("exit", (code) => resolve7(code ?? 0));
19015
19132
  child.on("error", (err) => {
19016
19133
  process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
19017
19134
  `);
19018
19135
  process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
19019
19136
  `);
19020
- resolve6(1);
19137
+ resolve7(1);
19021
19138
  });
19022
19139
  });
19023
19140
  killProxy();