@kody-ade/kody-engine 0.4.201 → 0.4.202

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js CHANGED
@@ -19,8 +19,8 @@ __export(events_exports, {
19
19
  resolveRunId: () => resolveRunId
20
20
  });
21
21
  import * as crypto from "crypto";
22
- import * as fs5 from "fs";
23
- import * as path5 from "path";
22
+ import * as fs4 from "fs";
23
+ import * as path4 from "path";
24
24
  function resolveRunId() {
25
25
  if (cachedRunId) return cachedRunId;
26
26
  if (process.env.KODY_RUN_ID) {
@@ -49,17 +49,17 @@ function emitEvent(cwd, ev) {
49
49
  runId,
50
50
  ...ev
51
51
  };
52
- const eventsPath2 = path5.join(cwd, ".kody", "runs", runId, "events.jsonl");
53
- fs5.mkdirSync(path5.dirname(eventsPath2), { recursive: true });
54
- fs5.appendFileSync(eventsPath2, `${JSON.stringify(fullEvent)}
52
+ const eventsPath2 = path4.join(cwd, ".kody", "runs", runId, "events.jsonl");
53
+ fs4.mkdirSync(path4.dirname(eventsPath2), { recursive: true });
54
+ fs4.appendFileSync(eventsPath2, `${JSON.stringify(fullEvent)}
55
55
  `);
56
56
  } catch {
57
57
  }
58
58
  }
59
59
  function readEvents(cwd, runId) {
60
- const eventsPath2 = path5.join(cwd, ".kody", "runs", runId, "events.jsonl");
61
- if (!fs5.existsSync(eventsPath2)) return [];
62
- const lines = fs5.readFileSync(eventsPath2, "utf-8").split("\n");
60
+ const eventsPath2 = path4.join(cwd, ".kody", "runs", runId, "events.jsonl");
61
+ if (!fs4.existsSync(eventsPath2)) return [];
62
+ const lines = fs4.readFileSync(eventsPath2, "utf-8").split("\n");
63
63
  const out = [];
64
64
  for (const line of lines) {
65
65
  const trimmed = line.trim();
@@ -72,11 +72,11 @@ function readEvents(cwd, runId) {
72
72
  return out;
73
73
  }
74
74
  function listRuns(cwd) {
75
- const runsDir = path5.join(cwd, ".kody", "runs");
76
- if (!fs5.existsSync(runsDir)) return [];
77
- return fs5.readdirSync(runsDir).filter((name) => {
75
+ const runsDir = path4.join(cwd, ".kody", "runs");
76
+ if (!fs4.existsSync(runsDir)) return [];
77
+ return fs4.readdirSync(runsDir).filter((name) => {
78
78
  try {
79
- return fs5.statSync(path5.join(runsDir, name)).isDirectory();
79
+ return fs4.statSync(path4.join(runsDir, name)).isDirectory();
80
80
  } catch {
81
81
  return false;
82
82
  }
@@ -181,8 +181,10 @@ function summarizeFailure(result) {
181
181
  for (let attempt = 1; ; attempt++) {
182
182
  const retry = result.details[`${name} (retry ${attempt})`];
183
183
  if (!retry) break;
184
- lines.push(`
185
- --- ${name} (retry ${attempt}: exit ${retry.exitCode}, ${(retry.durationMs / 1e3).toFixed(1)}s) ---`);
184
+ lines.push(
185
+ `
186
+ --- ${name} (retry ${attempt}: exit ${retry.exitCode}, ${(retry.durationMs / 1e3).toFixed(1)}s) ---`
187
+ );
186
188
  lines.push(stripAnsi(retry.tail));
187
189
  }
188
190
  }
@@ -621,18 +623,7 @@ function readLedger(label) {
621
623
  const startTag = `<!-- ${label}:start -->`;
622
624
  const endTag = `<!-- ${label}:end -->`;
623
625
  try {
624
- const raw = gh([
625
- "issue",
626
- "list",
627
- "--state",
628
- "open",
629
- "--label",
630
- label,
631
- "--limit",
632
- "5",
633
- "--json",
634
- "number,body"
635
- ]);
626
+ const raw = gh(["issue", "list", "--state", "open", "--label", label, "--limit", "5", "--json", "number,body"]);
636
627
  const issues = JSON.parse(raw);
637
628
  if (issues.length === 0) return { found: false, payload: null };
638
629
  const issue = issues.sort((a, b) => a.number - b.number)[0];
@@ -805,9 +796,13 @@ function buildDutyMcpServer(opts) {
805
796
  "ensure_issue",
806
797
  "Idempotently ensure ONE open tracking issue exists for `key`. Searches OPEN issues (issues API, not the laggy search index) for `key`'s hidden marker; if found, returns {created:false, number} and creates NOTHING; otherwise creates the issue (title + body, marker appended) and returns {created:true, number}. This is the anti-duplication primitive: use one stable `key` per recurring finding so re-ticks reuse the same issue. Only take follow-up actions (dispatch/comment) when created===true.",
807
798
  {
808
- key: z3.string().min(1).describe("Stable dedup identity for this finding (e.g. 'dev-ci-red', 'docs-drift:<feature>'). Same key across ticks = same issue."),
799
+ key: z3.string().min(1).describe(
800
+ "Stable dedup identity for this finding (e.g. 'dev-ci-red', 'docs-drift:<feature>'). Same key across ticks = same issue."
801
+ ),
809
802
  title: z3.string().min(1).describe("Issue title (used only on first creation)."),
810
- body: z3.string().min(1).describe("Issue body markdown (used only on first creation). Include the operator mention verbatim if the duty body has one.")
803
+ body: z3.string().min(1).describe(
804
+ "Issue body markdown (used only on first creation). Include the operator mention verbatim if the duty body has one."
805
+ )
811
806
  },
812
807
  async (args) => {
813
808
  const result = ensureIssue(opts.repoSlug, args.key, args.title, args.body);
@@ -886,15 +881,15 @@ var init_dutyMcp = __esm({
886
881
 
887
882
  // src/repoWorkspace.ts
888
883
  import { spawn as spawn2, spawnSync } from "child_process";
889
- import * as fs6 from "fs";
890
- import * as path6 from "path";
884
+ import * as fs5 from "fs";
885
+ import * as path5 from "path";
891
886
  async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
892
887
  const name = repo?.trim();
893
888
  if (!name || !REPO_RE.test(name)) return null;
894
- const root = path6.resolve(reposRoot);
895
- const dir = path6.resolve(root, name);
896
- if (dir !== root && !dir.startsWith(root + path6.sep)) return null;
897
- if (fs6.existsSync(path6.join(dir, ".git"))) return dir;
889
+ const root = path5.resolve(reposRoot);
890
+ const dir = path5.resolve(root, name);
891
+ if (dir !== root && !dir.startsWith(root + path5.sep)) return null;
892
+ if (fs5.existsSync(path5.join(dir, ".git"))) return dir;
898
893
  const inflight = repoClones.get(dir);
899
894
  if (inflight) {
900
895
  await inflight;
@@ -908,25 +903,13 @@ async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
908
903
  return dir;
909
904
  }
910
905
  async function ensureRepoCwd(opts) {
911
- const dir = await resolveAndClone(
912
- opts.reposRoot,
913
- opts.repo,
914
- opts.repoToken,
915
- opts.cloneRepo
916
- );
906
+ const dir = await resolveAndClone(opts.reposRoot, opts.repo, opts.repoToken, opts.cloneRepo);
917
907
  return dir ?? opts.baseCwd;
918
908
  }
919
909
  async function fetchRepo(opts) {
920
- const dir = await resolveAndClone(
921
- opts.reposRoot,
922
- opts.repo,
923
- opts.repoToken,
924
- opts.cloneRepo ?? defaultCloneRepo
925
- );
910
+ const dir = await resolveAndClone(opts.reposRoot, opts.repo, opts.repoToken, opts.cloneRepo ?? defaultCloneRepo);
926
911
  if (!dir) {
927
- throw new Error(
928
- `invalid repo "${opts.repo}" \u2014 expected "owner/name" with no path escapes`
929
- );
912
+ throw new Error(`invalid repo "${opts.repo}" \u2014 expected "owner/name" with no path escapes`);
930
913
  }
931
914
  return dir;
932
915
  }
@@ -937,7 +920,7 @@ var init_repoWorkspace = __esm({
937
920
  REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
938
921
  repoClones = /* @__PURE__ */ new Map();
939
922
  defaultCloneRepo = (repo, token, dir) => {
940
- fs6.mkdirSync(path6.dirname(dir), { recursive: true });
923
+ fs5.mkdirSync(path5.dirname(dir), { recursive: true });
941
924
  const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
942
925
  return new Promise((resolve6, reject) => {
943
926
  const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
@@ -968,10 +951,7 @@ var fetchRepoMcp_exports = {};
968
951
  __export(fetchRepoMcp_exports, {
969
952
  buildFetchRepoMcpServer: () => buildFetchRepoMcpServer
970
953
  });
971
- import {
972
- createSdkMcpServer as createSdkMcpServer4,
973
- tool as tool4
974
- } from "@anthropic-ai/claude-agent-sdk";
954
+ import { createSdkMcpServer as createSdkMcpServer4, tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
975
955
  import { z as z4 } from "zod";
976
956
  function buildFetchRepoMcpServer(opts) {
977
957
  const fetchTool = tool4(
@@ -1431,7 +1411,7 @@ var init_loadCoverageRules = __esm({
1431
1411
  // package.json
1432
1412
  var package_default = {
1433
1413
  name: "@kody-ade/kody-engine",
1434
- version: "0.4.201",
1414
+ version: "0.4.202",
1435
1415
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
1436
1416
  license: "MIT",
1437
1417
  type: "module",
@@ -1562,104 +1542,16 @@ function makeRunId(sessionId, suffix) {
1562
1542
  import * as fs11 from "fs";
1563
1543
  import * as path11 from "path";
1564
1544
 
1565
- // src/task-artifacts.ts
1566
- import fs2 from "fs";
1567
- import path2 from "path";
1568
- var TASK_ARTIFACT_FILES = [
1569
- "context.json",
1570
- "memory-recs.json",
1571
- "followups.json",
1572
- "handoff-notes.md"
1573
- ];
1574
- function prepareTaskArtifactsDir(cwd, taskId) {
1575
- const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
1576
- const relDir = path2.join(".kody", "tasks", safeId);
1577
- const absDir = path2.join(cwd, relDir);
1578
- fs2.mkdirSync(absDir, { recursive: true });
1579
- return { taskId: safeId, absDir, relDir };
1580
- }
1581
- function verifyTaskArtifacts(absDir) {
1582
- const missing = [];
1583
- for (const name of TASK_ARTIFACT_FILES) {
1584
- const full = path2.join(absDir, name);
1585
- try {
1586
- const stat = fs2.statSync(full);
1587
- if (!stat.isFile() || stat.size === 0) missing.push(name);
1588
- } catch {
1589
- missing.push(name);
1590
- }
1591
- }
1592
- return missing;
1593
- }
1594
- function taskArtifactsPromptAddendum(opts) {
1595
- return [
1596
- "## Per-task artifacts (REQUIRED before your final response)",
1597
- "",
1598
- `Before you finish, write these four files into \`${opts.relDir}/\`:`,
1599
- "",
1600
- `1. **context.json** \u2014 task header. Shape:`,
1601
- " ```json",
1602
- " {",
1603
- ` "taskId": "${opts.taskId}",`,
1604
- ` "taskType": "${opts.taskType}",`,
1605
- ` "target": "<issue/PR number, session id, or job slug>",`,
1606
- ` "outcome": "success" | "failure" | "partial",`,
1607
- ` "exitCode": <number>,`,
1608
- ` "reason": "<one-line summary of why you exited>",`,
1609
- ` "prUrl": "<url or null>",`,
1610
- ` "runUrl": "<url or null>",`,
1611
- ` "filesTouched": ["path/from/repo/root.ts", ...],`,
1612
- ` "sessionLog": ".kody/sessions/<id>.jsonl",`,
1613
- ` "startedAt": "<ISO>",`,
1614
- ` "finishedAt": "<ISO>"`,
1615
- " }",
1616
- " ```",
1617
- "",
1618
- `2. **memory-recs.json** \u2014 array of sticky-note candidates worth promoting`,
1619
- ` to long-term \`.kody/memory/\`. Each item:`,
1620
- " ```json",
1621
- " {",
1622
- ` "type": "preference" | "decision" | "lesson",`,
1623
- ` "name": "kebab-case-slug",`,
1624
- ` "hook": "one-line summary for INDEX.md",`,
1625
- ` "body": "markdown body \u2014 explain the rule plus a Why: line",`,
1626
- ` "why": "the load-bearing reason a future session needs this",`,
1627
- ` "confidence": 0.0 to 1.0`,
1628
- " }",
1629
- " ```",
1630
- ` Use \`[]\` if nothing in this task is worth remembering. Forced`,
1631
- ` filler is worse than nothing \u2014 only record what would be lost`,
1632
- ` otherwise.`,
1633
- "",
1634
- `3. **followups.json** \u2014 array of TODOs uncovered but not fixed.`,
1635
- " ```json",
1636
- " {",
1637
- ` "title": "short summary",`,
1638
- ` "body": "what the operator should do, and where",`,
1639
- ` "rationale": "why this matters",`,
1640
- ` "priority": "low" | "medium" | "high"`,
1641
- " }",
1642
- " ```",
1643
- ` Use \`[]\` if nothing surfaced.`,
1644
- "",
1645
- `4. **handoff-notes.md** \u2014 short prose (\u2264200 words), no frontmatter:`,
1646
- ` what you did and why, so the next person/agent can pick up cold.`,
1647
- "",
1648
- "Skipping any of the four files is an error. Empty arrays are fine;",
1649
- "skipping the file is not."
1650
- ].join("\n");
1651
- }
1652
-
1653
1545
  // src/agent.ts
1654
- import * as fs7 from "fs";
1655
- import * as path7 from "path";
1546
+ import * as fs6 from "fs";
1547
+ import * as path6 from "path";
1656
1548
  import { query } from "@anthropic-ai/claude-agent-sdk";
1657
1549
 
1658
1550
  // src/claudeBinary.ts
1659
- import * as fs3 from "fs";
1551
+ import * as fs2 from "fs";
1660
1552
  import { createRequire } from "module";
1661
1553
  import * as os from "os";
1662
- import * as path3 from "path";
1554
+ import * as path2 from "path";
1663
1555
  var SDK_PKG = "@anthropic-ai/claude-agent-sdk";
1664
1556
  function candidateSpecs(platform, arch) {
1665
1557
  const ext = platform === "win32" ? ".exe" : "";
@@ -1669,8 +1561,8 @@ function candidateSpecs(platform, arch) {
1669
1561
  function readSdkVersion(req) {
1670
1562
  try {
1671
1563
  const entry = req.resolve(SDK_PKG);
1672
- const pkgDir = path3.dirname(entry);
1673
- const raw = fs3.readFileSync(path3.join(pkgDir, "package.json"), "utf8");
1564
+ const pkgDir = path2.dirname(entry);
1565
+ const raw = fs2.readFileSync(path2.join(pkgDir, "package.json"), "utf8");
1674
1566
  const v = JSON.parse(raw)?.version;
1675
1567
  return typeof v === "string" && v.length > 0 ? v : "unknown";
1676
1568
  } catch {
@@ -1692,24 +1584,24 @@ function ensureStableClaudeBinary() {
1692
1584
  } catch {
1693
1585
  }
1694
1586
  }
1695
- if (!source || !fs3.existsSync(source)) {
1587
+ if (!source || !fs2.existsSync(source)) {
1696
1588
  cached = null;
1697
1589
  return cached;
1698
1590
  }
1699
1591
  const ext = process.platform === "win32" ? ".exe" : "";
1700
1592
  const version = readSdkVersion(req);
1701
- const destDir = path3.join(os.tmpdir(), "kody-claude-sdk", version);
1702
- const dest = path3.join(destDir, `claude${ext}`);
1703
- const srcSize = fs3.statSync(source).size;
1704
- if (fs3.existsSync(dest) && fs3.statSync(dest).size === srcSize) {
1593
+ const destDir = path2.join(os.tmpdir(), "kody-claude-sdk", version);
1594
+ const dest = path2.join(destDir, `claude${ext}`);
1595
+ const srcSize = fs2.statSync(source).size;
1596
+ if (fs2.existsSync(dest) && fs2.statSync(dest).size === srcSize) {
1705
1597
  cached = dest;
1706
1598
  return cached;
1707
1599
  }
1708
- fs3.mkdirSync(destDir, { recursive: true });
1709
- const tmp = path3.join(destDir, `.claude.${process.pid}.${Date.now()}.tmp`);
1710
- fs3.copyFileSync(source, tmp);
1711
- fs3.chmodSync(tmp, 493);
1712
- fs3.renameSync(tmp, dest);
1600
+ fs2.mkdirSync(destDir, { recursive: true });
1601
+ const tmp = path2.join(destDir, `.claude.${process.pid}.${Date.now()}.tmp`);
1602
+ fs2.copyFileSync(source, tmp);
1603
+ fs2.chmodSync(tmp, 493);
1604
+ fs2.renameSync(tmp, dest);
1713
1605
  cached = dest;
1714
1606
  return cached;
1715
1607
  } catch {
@@ -1719,8 +1611,8 @@ function ensureStableClaudeBinary() {
1719
1611
  }
1720
1612
 
1721
1613
  // src/config.ts
1722
- import * as fs4 from "fs";
1723
- import * as path4 from "path";
1614
+ import * as fs3 from "fs";
1615
+ import * as path3 from "path";
1724
1616
  var LITELLM_DEFAULT_PORT = 4e3;
1725
1617
  var LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
1726
1618
  function parseProviderModel(s) {
@@ -1738,13 +1630,13 @@ function needsLitellmProxy(model) {
1738
1630
  return model.provider !== "claude" && model.provider !== "anthropic";
1739
1631
  }
1740
1632
  function loadConfig(projectDir = process.cwd()) {
1741
- const configPath = path4.join(projectDir, "kody.config.json");
1742
- if (!fs4.existsSync(configPath)) {
1633
+ const configPath = path3.join(projectDir, "kody.config.json");
1634
+ if (!fs3.existsSync(configPath)) {
1743
1635
  throw new Error(`kody.config.json not found at ${configPath}`);
1744
1636
  }
1745
1637
  let raw;
1746
1638
  try {
1747
- raw = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
1639
+ raw = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1748
1640
  } catch (err) {
1749
1641
  const msg = err instanceof Error ? err.message : String(err);
1750
1642
  throw new Error(`kody.config.json is invalid JSON: ${msg}`);
@@ -1781,6 +1673,7 @@ function loadConfig(projectDir = process.cwd()) {
1781
1673
  testRequirements: parseTestRequirements(raw.testRequirements),
1782
1674
  defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable : "run",
1783
1675
  defaultPrExecutable: typeof raw.defaultPrExecutable === "string" && raw.defaultPrExecutable.length > 0 ? raw.defaultPrExecutable : "fix",
1676
+ onPullRequest: typeof raw.onPullRequest === "string" && raw.onPullRequest.length > 0 ? raw.onPullRequest : void 0,
1784
1677
  aliases: mergeAliases(raw.aliases),
1785
1678
  classify: parseClassifyConfig(raw.classify),
1786
1679
  release: parseReleaseConfig(raw.release),
@@ -2062,9 +1955,9 @@ function toolMayMutate(name, input) {
2062
1955
  return false;
2063
1956
  }
2064
1957
  async function runAgent(opts) {
2065
- const ndjsonDir = opts.ndjsonDir ?? path7.join(opts.cwd, ".kody");
2066
- fs7.mkdirSync(ndjsonDir, { recursive: true });
2067
- const ndjsonPath = path7.join(ndjsonDir, "last-run.jsonl");
1958
+ const ndjsonDir = opts.ndjsonDir ?? path6.join(opts.cwd, ".kody");
1959
+ fs6.mkdirSync(ndjsonDir, { recursive: true });
1960
+ const ndjsonPath = path6.join(ndjsonDir, "last-run.jsonl");
2068
1961
  const env = {
2069
1962
  ...process.env,
2070
1963
  SKIP_HOOKS: "1",
@@ -2093,7 +1986,7 @@ async function runAgent(opts) {
2093
1986
  let finalText = "";
2094
1987
  let getSubmitted;
2095
1988
  for (let attempt = 0; ; attempt++) {
2096
- const fullLog = fs7.createWriteStream(ndjsonPath, { flags: "w" });
1989
+ const fullLog = fs6.createWriteStream(ndjsonPath, { flags: "w" });
2097
1990
  const resultTexts = [];
2098
1991
  outcome = "failed";
2099
1992
  outcomeKind = "generic_failed";
@@ -2390,48 +2283,48 @@ async function runAgent(opts) {
2390
2283
  }
2391
2284
 
2392
2285
  // src/registry.ts
2393
- import * as fs8 from "fs";
2394
- import * as path8 from "path";
2286
+ import * as fs7 from "fs";
2287
+ import * as path7 from "path";
2395
2288
  function getExecutablesRoot() {
2396
- const here = path8.dirname(new URL(import.meta.url).pathname);
2289
+ const here = path7.dirname(new URL(import.meta.url).pathname);
2397
2290
  const candidates = [
2398
- path8.join(here, "executables"),
2291
+ path7.join(here, "executables"),
2399
2292
  // dev: src/
2400
- path8.join(here, "..", "executables"),
2293
+ path7.join(here, "..", "executables"),
2401
2294
  // built: dist/bin → dist/executables
2402
- path8.join(here, "..", "src", "executables")
2295
+ path7.join(here, "..", "src", "executables")
2403
2296
  // fallback
2404
2297
  ];
2405
2298
  for (const c of candidates) {
2406
- if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
2299
+ if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
2407
2300
  }
2408
2301
  return candidates[0];
2409
2302
  }
2410
2303
  function getProjectExecutablesRoot() {
2411
- return path8.join(process.cwd(), ".kody", "executables");
2304
+ return path7.join(process.cwd(), ".kody", "executables");
2412
2305
  }
2413
2306
  function getBuiltinJobsRoot() {
2414
- const here = path8.dirname(new URL(import.meta.url).pathname);
2307
+ const here = path7.dirname(new URL(import.meta.url).pathname);
2415
2308
  const candidates = [
2416
- path8.join(here, "jobs"),
2309
+ path7.join(here, "jobs"),
2417
2310
  // dev: src/
2418
- path8.join(here, "..", "jobs"),
2311
+ path7.join(here, "..", "jobs"),
2419
2312
  // built: dist/bin → dist/jobs
2420
- path8.join(here, "..", "src", "jobs")
2313
+ path7.join(here, "..", "src", "jobs")
2421
2314
  // fallback
2422
2315
  ];
2423
2316
  for (const c of candidates) {
2424
- if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
2317
+ if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
2425
2318
  }
2426
2319
  return candidates[0];
2427
2320
  }
2428
2321
  function listBuiltinJobs(root = getBuiltinJobsRoot()) {
2429
- if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
2322
+ if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
2430
2323
  const out = [];
2431
- for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
2324
+ for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
2432
2325
  if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
2433
2326
  const slug = ent.name.slice(0, -3);
2434
- out.push({ slug, filePath: path8.join(root, ent.name) });
2327
+ out.push({ slug, filePath: path7.join(root, ent.name) });
2435
2328
  }
2436
2329
  out.sort((a, b) => a.slug.localeCompare(b.slug));
2437
2330
  return out;
@@ -2444,13 +2337,13 @@ function listExecutables(roots = getExecutableRoots()) {
2444
2337
  const seen = /* @__PURE__ */ new Set();
2445
2338
  const out = [];
2446
2339
  for (const root of rootList) {
2447
- if (!fs8.existsSync(root)) continue;
2448
- const entries = fs8.readdirSync(root, { withFileTypes: true });
2340
+ if (!fs7.existsSync(root)) continue;
2341
+ const entries = fs7.readdirSync(root, { withFileTypes: true });
2449
2342
  for (const ent of entries) {
2450
2343
  if (!ent.isDirectory()) continue;
2451
2344
  if (seen.has(ent.name)) continue;
2452
- const profilePath = path8.join(root, ent.name, "profile.json");
2453
- if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
2345
+ const profilePath = path7.join(root, ent.name, "profile.json");
2346
+ if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
2454
2347
  out.push({ name: ent.name, profilePath });
2455
2348
  seen.add(ent.name);
2456
2349
  }
@@ -2462,8 +2355,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
2462
2355
  if (!isSafeName(name)) return null;
2463
2356
  const rootList = typeof roots === "string" ? [roots] : roots;
2464
2357
  for (const root of rootList) {
2465
- const profilePath = path8.join(root, name, "profile.json");
2466
- if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
2358
+ const profilePath = path7.join(root, name, "profile.json");
2359
+ if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
2467
2360
  return profilePath;
2468
2361
  }
2469
2362
  }
@@ -2479,7 +2372,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
2479
2372
  const profilePath = resolveExecutable(name, roots);
2480
2373
  if (!profilePath) return null;
2481
2374
  try {
2482
- const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
2375
+ const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
2483
2376
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
2484
2377
  return raw.inputs;
2485
2378
  } catch {
@@ -2497,7 +2390,11 @@ function parseGenericFlags(argv) {
2497
2390
  }
2498
2391
  const key = arg.slice(2);
2499
2392
  const next = argv[i + 1];
2500
- const value = next !== void 0 && !next.startsWith("--") ? (i++, next) : true;
2393
+ let value = true;
2394
+ if (next !== void 0 && !next.startsWith("--")) {
2395
+ value = next;
2396
+ i++;
2397
+ }
2501
2398
  args[key] = value;
2502
2399
  if (key.includes("-")) {
2503
2400
  const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
@@ -2508,70 +2405,92 @@ function parseGenericFlags(argv) {
2508
2405
  return args;
2509
2406
  }
2510
2407
 
2511
- // src/chat/session.ts
2512
- import * as fs9 from "fs";
2513
- import * as path9 from "path";
2514
- function sessionFilePath(cwd, sessionId) {
2515
- return path9.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
2516
- }
2517
- function readMeta(file) {
2518
- if (!fs9.existsSync(file)) return null;
2519
- const raw = fs9.readFileSync(file, "utf-8");
2520
- const firstLine2 = raw.split("\n", 1)[0]?.trim();
2521
- if (!firstLine2) return null;
2522
- try {
2523
- const parsed = JSON.parse(firstLine2);
2524
- if (parsed.type !== "meta") return null;
2525
- if (parsed.mode !== "one-shot" && parsed.mode !== "interactive") return null;
2526
- return parsed;
2527
- } catch {
2528
- return null;
2529
- }
2408
+ // src/task-artifacts.ts
2409
+ import fs8 from "fs";
2410
+ import path8 from "path";
2411
+ var TASK_ARTIFACT_FILES = ["context.json", "memory-recs.json", "followups.json", "handoff-notes.md"];
2412
+ function prepareTaskArtifactsDir(cwd, taskId) {
2413
+ const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
2414
+ const relDir = path8.join(".kody", "tasks", safeId);
2415
+ const absDir = path8.join(cwd, relDir);
2416
+ fs8.mkdirSync(absDir, { recursive: true });
2417
+ return { taskId: safeId, absDir, relDir };
2530
2418
  }
2531
- function readSession(file) {
2532
- if (!fs9.existsSync(file)) return [];
2533
- const raw = fs9.readFileSync(file, "utf-8").trim();
2534
- if (!raw) return [];
2535
- const turns = [];
2536
- for (const line of raw.split("\n")) {
2537
- if (!line.trim()) continue;
2419
+ function verifyTaskArtifacts(absDir) {
2420
+ const missing = [];
2421
+ for (const name of TASK_ARTIFACT_FILES) {
2422
+ const full = path8.join(absDir, name);
2538
2423
  try {
2539
- const parsed = JSON.parse(line);
2540
- if (parsed.role !== "user" && parsed.role !== "assistant") continue;
2541
- if (typeof parsed.content !== "string") continue;
2542
- turns.push(parsed);
2424
+ const stat = fs8.statSync(full);
2425
+ if (!stat.isFile() || stat.size === 0) missing.push(name);
2543
2426
  } catch {
2427
+ missing.push(name);
2544
2428
  }
2545
2429
  }
2546
- return turns;
2547
- }
2548
- function appendTurn(file, turn) {
2549
- fs9.mkdirSync(path9.dirname(file), { recursive: true });
2550
- const line = JSON.stringify({
2551
- role: turn.role,
2552
- content: turn.content,
2553
- timestamp: turn.timestamp,
2554
- toolCalls: turn.toolCalls ?? []
2555
- });
2556
- fs9.appendFileSync(file, `${line}
2557
- `);
2430
+ return missing;
2558
2431
  }
2559
- function seedInitialMessage(file, message) {
2560
- if (!message.trim()) return false;
2561
- const turns = readSession(file);
2562
- const lastUser = [...turns].reverse().find((t) => t.role === "user");
2563
- if (lastUser && lastUser.content === message) return false;
2564
- appendTurn(file, {
2565
- role: "user",
2566
- content: message,
2567
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2568
- });
2569
- return true;
2432
+ function taskArtifactsPromptAddendum(opts) {
2433
+ return [
2434
+ "## Per-task artifacts (REQUIRED before your final response)",
2435
+ "",
2436
+ `Before you finish, write these four files into \`${opts.relDir}/\`:`,
2437
+ "",
2438
+ `1. **context.json** \u2014 task header. Shape:`,
2439
+ " ```json",
2440
+ " {",
2441
+ ` "taskId": "${opts.taskId}",`,
2442
+ ` "taskType": "${opts.taskType}",`,
2443
+ ` "target": "<issue/PR number, session id, or job slug>",`,
2444
+ ` "outcome": "success" | "failure" | "partial",`,
2445
+ ` "exitCode": <number>,`,
2446
+ ` "reason": "<one-line summary of why you exited>",`,
2447
+ ` "prUrl": "<url or null>",`,
2448
+ ` "runUrl": "<url or null>",`,
2449
+ ` "filesTouched": ["path/from/repo/root.ts", ...],`,
2450
+ ` "sessionLog": ".kody/sessions/<id>.jsonl",`,
2451
+ ` "startedAt": "<ISO>",`,
2452
+ ` "finishedAt": "<ISO>"`,
2453
+ " }",
2454
+ " ```",
2455
+ "",
2456
+ `2. **memory-recs.json** \u2014 array of sticky-note candidates worth promoting`,
2457
+ ` to long-term \`.kody/memory/\`. Each item:`,
2458
+ " ```json",
2459
+ " {",
2460
+ ` "type": "preference" | "decision" | "lesson",`,
2461
+ ` "name": "kebab-case-slug",`,
2462
+ ` "hook": "one-line summary for INDEX.md",`,
2463
+ ` "body": "markdown body \u2014 explain the rule plus a Why: line",`,
2464
+ ` "why": "the load-bearing reason a future session needs this",`,
2465
+ ` "confidence": 0.0 to 1.0`,
2466
+ " }",
2467
+ " ```",
2468
+ ` Use \`[]\` if nothing in this task is worth remembering. Forced`,
2469
+ ` filler is worse than nothing \u2014 only record what would be lost`,
2470
+ ` otherwise.`,
2471
+ "",
2472
+ `3. **followups.json** \u2014 array of TODOs uncovered but not fixed.`,
2473
+ " ```json",
2474
+ " {",
2475
+ ` "title": "short summary",`,
2476
+ ` "body": "what the operator should do, and where",`,
2477
+ ` "rationale": "why this matters",`,
2478
+ ` "priority": "low" | "medium" | "high"`,
2479
+ " }",
2480
+ " ```",
2481
+ ` Use \`[]\` if nothing surfaced.`,
2482
+ "",
2483
+ `4. **handoff-notes.md** \u2014 short prose (\u2264200 words), no frontmatter:`,
2484
+ ` what you did and why, so the next person/agent can pick up cold.`,
2485
+ "",
2486
+ "Skipping any of the four files is an error. Empty arrays are fine;",
2487
+ "skipping the file is not."
2488
+ ].join("\n");
2570
2489
  }
2571
2490
 
2572
2491
  // src/chat/attachments.ts
2573
- import * as fs10 from "fs";
2574
- import * as path10 from "path";
2492
+ import * as fs9 from "fs";
2493
+ import * as path9 from "path";
2575
2494
  var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
2576
2495
  var EXT_BY_MIME = {
2577
2496
  "image/png": "png",
@@ -2587,7 +2506,7 @@ function extFor(mime) {
2587
2506
  return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
2588
2507
  }
2589
2508
  function attachmentsDir(cwd, sessionId) {
2590
- return path10.join(cwd, ".kody", "tmp", "attachments", sessionId);
2509
+ return path9.join(cwd, ".kody", "tmp", "attachments", sessionId);
2591
2510
  }
2592
2511
  function prepareAttachments(turns, cwd, sessionId) {
2593
2512
  const imagePaths = [];
@@ -2604,11 +2523,11 @@ function prepareAttachments(turns, cwd, sessionId) {
2604
2523
  if (!isImage) return `[File: ${name}]`;
2605
2524
  try {
2606
2525
  if (!dirEnsured) {
2607
- fs10.mkdirSync(dir, { recursive: true });
2526
+ fs9.mkdirSync(dir, { recursive: true });
2608
2527
  dirEnsured = true;
2609
2528
  }
2610
- const filePath = path10.join(dir, `${imageCounter}.${extFor(mime)}`);
2611
- fs10.writeFileSync(filePath, Buffer.from(data, "base64"));
2529
+ const filePath = path9.join(dir, `${imageCounter}.${extFor(mime)}`);
2530
+ fs9.writeFileSync(filePath, Buffer.from(data, "base64"));
2612
2531
  imageCounter += 1;
2613
2532
  imagePaths.push(filePath);
2614
2533
  return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it.]`;
@@ -2623,42 +2542,103 @@ function prepareAttachments(turns, cwd, sessionId) {
2623
2542
  return { turns: rewritten, imagePaths };
2624
2543
  }
2625
2544
 
2626
- // src/chat/loop.ts
2627
- var CHAT_SYSTEM_PROMPT = [
2628
- "You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the",
2629
- "user's latest message using the full conversation below as context. Keep replies",
2630
- "short and simple. Prefer one-liners and short paragraphs. Use plain terms, not jargon.",
2631
- "When you diagnose something, answer in this shape: a few words on the issue, a",
2632
- "few words on the fix, then a single question asking whether to proceed. Do not",
2633
- "pad it with preamble, restated context, or a trailing summary.",
2634
- "",
2635
- "# Your environment and capabilities",
2636
- "You run inside a sandboxed runner with a full clone of the user's repository",
2637
- "checked out at the current working directory. The runtime varies \u2014 it may be a",
2638
- "GitHub Actions job, a Fly Machine, or another container \u2014 but the tools and",
2639
- "capabilities below are identical across runtimes. Use the actual environment",
2640
- "(e.g. `uname`, `pwd`, `env`) to verify before claiming where you run. You have",
2641
- "real tools \u2014 use them before claiming you cannot do something. Never tell the",
2642
- "user you lack repo, filesystem, or GitHub access; you have all three.",
2643
- "",
2644
- "Tools you can call:",
2645
- "- Read, Edit, Write \u2014 full read/write access to every file in the repo (permission",
2646
- " mode is acceptEdits, so writes do not require confirmation).",
2647
- "- Glob, Grep \u2014 search the repo by filename pattern or content.",
2648
- "- Bash \u2014 run any shell command in the repo. The runner has:",
2649
- " - `git` (the repo is a real git checkout \u2014 `git log`, `git diff`,",
2650
- " `git show`, `git blame`, `git branch`, etc. all work).",
2651
- " - `gh` authenticated against this repository's GitHub via a `GITHUB_TOKEN`",
2652
- " env var (read issues, PRs, workflows, runs, comments; query the API",
2653
- " with `gh api`).",
2654
- " - the repo's package manager and test/build/lint tooling (npm/pnpm/yarn,",
2655
- " pytest, go test, cargo, etc., whatever the project uses).",
2656
- " - standard Unix utilities (curl, jq, sed, awk, find, etc.).",
2657
- "",
2658
- "The repo's configured secrets are in the environment \u2014 check `env` before",
2659
- "claiming you lack a credential. Never print a secret's value.",
2660
- "",
2661
- "# Clarify before you act (HARD RULE)",
2545
+ // src/chat/session.ts
2546
+ import * as fs10 from "fs";
2547
+ import * as path10 from "path";
2548
+ function sessionFilePath(cwd, sessionId) {
2549
+ return path10.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
2550
+ }
2551
+ function readMeta(file) {
2552
+ if (!fs10.existsSync(file)) return null;
2553
+ const raw = fs10.readFileSync(file, "utf-8");
2554
+ const firstLine2 = raw.split("\n", 1)[0]?.trim();
2555
+ if (!firstLine2) return null;
2556
+ try {
2557
+ const parsed = JSON.parse(firstLine2);
2558
+ if (parsed.type !== "meta") return null;
2559
+ if (parsed.mode !== "one-shot" && parsed.mode !== "interactive") return null;
2560
+ return parsed;
2561
+ } catch {
2562
+ return null;
2563
+ }
2564
+ }
2565
+ function readSession(file) {
2566
+ if (!fs10.existsSync(file)) return [];
2567
+ const raw = fs10.readFileSync(file, "utf-8").trim();
2568
+ if (!raw) return [];
2569
+ const turns = [];
2570
+ for (const line of raw.split("\n")) {
2571
+ if (!line.trim()) continue;
2572
+ try {
2573
+ const parsed = JSON.parse(line);
2574
+ if (parsed.role !== "user" && parsed.role !== "assistant") continue;
2575
+ if (typeof parsed.content !== "string") continue;
2576
+ turns.push(parsed);
2577
+ } catch {
2578
+ }
2579
+ }
2580
+ return turns;
2581
+ }
2582
+ function appendTurn(file, turn) {
2583
+ fs10.mkdirSync(path10.dirname(file), { recursive: true });
2584
+ const line = JSON.stringify({
2585
+ role: turn.role,
2586
+ content: turn.content,
2587
+ timestamp: turn.timestamp,
2588
+ toolCalls: turn.toolCalls ?? []
2589
+ });
2590
+ fs10.appendFileSync(file, `${line}
2591
+ `);
2592
+ }
2593
+ function seedInitialMessage(file, message) {
2594
+ if (!message.trim()) return false;
2595
+ const turns = readSession(file);
2596
+ const lastUser = [...turns].reverse().find((t) => t.role === "user");
2597
+ if (lastUser && lastUser.content === message) return false;
2598
+ appendTurn(file, {
2599
+ role: "user",
2600
+ content: message,
2601
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2602
+ });
2603
+ return true;
2604
+ }
2605
+
2606
+ // src/chat/loop.ts
2607
+ var CHAT_SYSTEM_PROMPT = [
2608
+ "You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the",
2609
+ "user's latest message using the full conversation below as context. Keep replies",
2610
+ "short and simple. Prefer one-liners and short paragraphs. Use plain terms, not jargon.",
2611
+ "When you diagnose something, answer in this shape: a few words on the issue, a",
2612
+ "few words on the fix, then a single question asking whether to proceed. Do not",
2613
+ "pad it with preamble, restated context, or a trailing summary.",
2614
+ "",
2615
+ "# Your environment and capabilities",
2616
+ "You run inside a sandboxed runner with a full clone of the user's repository",
2617
+ "checked out at the current working directory. The runtime varies \u2014 it may be a",
2618
+ "GitHub Actions job, a Fly Machine, or another container \u2014 but the tools and",
2619
+ "capabilities below are identical across runtimes. Use the actual environment",
2620
+ "(e.g. `uname`, `pwd`, `env`) to verify before claiming where you run. You have",
2621
+ "real tools \u2014 use them before claiming you cannot do something. Never tell the",
2622
+ "user you lack repo, filesystem, or GitHub access; you have all three.",
2623
+ "",
2624
+ "Tools you can call:",
2625
+ "- Read, Edit, Write \u2014 full read/write access to every file in the repo (permission",
2626
+ " mode is acceptEdits, so writes do not require confirmation).",
2627
+ "- Glob, Grep \u2014 search the repo by filename pattern or content.",
2628
+ "- Bash \u2014 run any shell command in the repo. The runner has:",
2629
+ " - `git` (the repo is a real git checkout \u2014 `git log`, `git diff`,",
2630
+ " `git show`, `git blame`, `git branch`, etc. all work).",
2631
+ " - `gh` authenticated against this repository's GitHub via a `GITHUB_TOKEN`",
2632
+ " env var (read issues, PRs, workflows, runs, comments; query the API",
2633
+ " with `gh api`).",
2634
+ " - the repo's package manager and test/build/lint tooling (npm/pnpm/yarn,",
2635
+ " pytest, go test, cargo, etc., whatever the project uses).",
2636
+ " - standard Unix utilities (curl, jq, sed, awk, find, etc.).",
2637
+ "",
2638
+ "The repo's configured secrets are in the environment \u2014 check `env` before",
2639
+ "claiming you lack a credential. Never print a secret's value.",
2640
+ "",
2641
+ "# Clarify before you act (HARD RULE)",
2662
2642
  "If the user's request is ambiguous or under-specified \u2014 you can read it two",
2663
2643
  "plausible ways, or you'd have to guess what they actually want \u2014 ask",
2664
2644
  "clarifying questions and stop. Ask as many as you genuinely need; do NOT",
@@ -2934,7 +2914,9 @@ ${content}`);
2934
2914
  }
2935
2915
  const joined = sections.join("\n\n").trim();
2936
2916
  if (!joined) return "";
2937
- const body = joined.length > MAX_CONTEXT_BYTES ? joined.slice(0, MAX_CONTEXT_BYTES) + "\n\n_\u2026 (context truncated; see `.kody/context/` for the full text)_" : joined;
2917
+ const body = joined.length > MAX_CONTEXT_BYTES ? `${joined.slice(0, MAX_CONTEXT_BYTES)}
2918
+
2919
+ _\u2026 (context truncated; see \`.kody/context/\` for the full text)_` : joined;
2938
2920
  return [
2939
2921
  "# Context (`.kody/context/`) \u2014 your default frame",
2940
2922
  "",
@@ -2955,7 +2937,9 @@ function readInstructionsBlock(cwd) {
2955
2937
  }
2956
2938
  const trimmed = raw.trim();
2957
2939
  if (!trimmed) return "";
2958
- const body = trimmed.length > MAX_INSTRUCTIONS_BYTES ? trimmed.slice(0, MAX_INSTRUCTIONS_BYTES) + "\n\n_\u2026 (instructions truncated)_" : trimmed;
2940
+ const body = trimmed.length > MAX_INSTRUCTIONS_BYTES ? `${trimmed.slice(0, MAX_INSTRUCTIONS_BYTES)}
2941
+
2942
+ _\u2026 (instructions truncated)_` : trimmed;
2959
2943
  return [
2960
2944
  "# User instructions for this repo (`.kody/instructions.md`)",
2961
2945
  "",
@@ -3172,7 +3156,8 @@ function putJsonlViaContents(repository, branch, repoPath, localText, sessionId,
3172
3156
  const localLines = jsonlLines(localText);
3173
3157
  const localSet = new Set(localLines);
3174
3158
  const extra = remote.lines.filter((l) => !localSet.has(l));
3175
- if (extra.length > 0) body = [...localLines, ...extra].join("\n") + "\n";
3159
+ if (extra.length > 0) body = `${[...localLines, ...extra].join("\n")}
3160
+ `;
3176
3161
  }
3177
3162
  const payload = {
3178
3163
  message: `chat: interactive turn for ${sessionId}`,
@@ -3372,18 +3357,7 @@ function cronMatchesInWindow(spec, end, windowSec) {
3372
3357
  }
3373
3358
 
3374
3359
  // src/dispatch.ts
3375
- var POLITE_WORDS = /* @__PURE__ */ new Set([
3376
- "please",
3377
- "kindly",
3378
- "hi",
3379
- "hey",
3380
- "hello",
3381
- "thanks",
3382
- "thank",
3383
- "plz",
3384
- "pls",
3385
- "yo"
3386
- ]);
3360
+ var POLITE_WORDS = /* @__PURE__ */ new Set(["please", "kindly", "hi", "hey", "hello", "thanks", "thank", "plz", "pls", "yo"]);
3387
3361
  function primaryNumericInputName(executable) {
3388
3362
  const inputs = getProfileInputs(executable);
3389
3363
  if (!inputs) return null;
@@ -3417,7 +3391,18 @@ function autoDispatch(opts) {
3417
3391
  return null;
3418
3392
  }
3419
3393
  if (eventName === "schedule") return null;
3420
- if (eventName === "pull_request") return null;
3394
+ if (eventName === "pull_request") {
3395
+ const exe = opts?.config?.onPullRequest?.trim();
3396
+ const action = String(event.action ?? "");
3397
+ if (exe && (action === "opened" || action === "synchronize" || action === "reopened")) {
3398
+ const prNum = Number(event.pull_request?.number ?? event.number ?? 0);
3399
+ if (prNum > 0) {
3400
+ const targetKey = primaryNumericInputName(exe) ?? "pr";
3401
+ return { executable: exe, cliArgs: { [targetKey]: prNum }, target: prNum };
3402
+ }
3403
+ }
3404
+ return null;
3405
+ }
3421
3406
  if (eventName !== "issue_comment") return null;
3422
3407
  const rawBody = String(event.comment?.body ?? "");
3423
3408
  const authorLogin = String(event.comment?.user?.login ?? "");
@@ -3519,7 +3504,10 @@ function autoDispatchTyped(opts) {
3519
3504
  const afterTag = extractAfterTag(rawBody.toLowerCase());
3520
3505
  const tokenRaw = extractSubcommand(afterTag) ?? "";
3521
3506
  if (!tokenRaw || POLITE_WORDS.has(tokenRaw)) {
3522
- return { kind: "silent", reason: tokenRaw ? `polite-word lead-in '${tokenRaw}', no default executable configured` : "no subcommand token, no default executable configured" };
3507
+ return {
3508
+ kind: "silent",
3509
+ reason: tokenRaw ? `polite-word lead-in '${tokenRaw}', no default executable configured` : "no subcommand token, no default executable configured"
3510
+ };
3523
3511
  }
3524
3512
  const available = listExecutables().map((e) => e.name).filter((n) => !n.startsWith("goal-") && !n.startsWith("job-")).sort();
3525
3513
  return { kind: "unrecognized", token: tokenRaw, target: targetNum, isPr, available };
@@ -3745,7 +3733,10 @@ function validateConfig(raw, profilePath) {
3745
3733
  const lbl = label;
3746
3734
  for (const k of ["name", "color", "description"]) {
3747
3735
  if (typeof lbl[k] !== "string" || lbl[k].length === 0) {
3748
- throw new ProfileError(profilePath, `lifecycle "pr-branch": lifecycleConfig.label.${k} must be a non-empty string`);
3736
+ throw new ProfileError(
3737
+ profilePath,
3738
+ `lifecycle "pr-branch": lifecycleConfig.label.${k} must be a non-empty string`
3739
+ );
3749
3740
  }
3750
3741
  }
3751
3742
  const context = raw.context === void 0 ? "task" : raw.context;
@@ -4256,7 +4247,9 @@ function parseStateComment(body) {
4256
4247
  try {
4257
4248
  parsed = JSON.parse(jsonStr);
4258
4249
  } catch (err) {
4259
- throw new CorruptStateError(`state JSON unparseable (truncated comment?): ${err instanceof Error ? err.message : String(err)}`);
4250
+ throw new CorruptStateError(
4251
+ `state JSON unparseable (truncated comment?): ${err instanceof Error ? err.message : String(err)}`
4252
+ );
4260
4253
  }
4261
4254
  if (parsed?.schemaVersion !== 1) {
4262
4255
  throw new CorruptStateError(`unexpected schemaVersion: ${JSON.stringify(parsed?.schemaVersion)}`);
@@ -4757,7 +4750,7 @@ function extractLabelSpec(entry) {
4757
4750
  const w = entry.with;
4758
4751
  if (!w) return null;
4759
4752
  const label = typeof w.label === "string" ? w.label : null;
4760
- if (!label || !label.startsWith(KODY_NAMESPACE)) return null;
4753
+ if (!label?.startsWith(KODY_NAMESPACE)) return null;
4761
4754
  return {
4762
4755
  label,
4763
4756
  color: typeof w.color === "string" ? w.color : void 0,
@@ -5001,139 +4994,6 @@ function stripBlockingEnv(env) {
5001
4994
  return out;
5002
4995
  }
5003
4996
 
5004
- // src/subagents.ts
5005
- import * as fs20 from "fs";
5006
- import * as path18 from "path";
5007
-
5008
- // src/scripts/buildSyntheticPlugin.ts
5009
- import * as fs19 from "fs";
5010
- import * as os3 from "os";
5011
- import * as path17 from "path";
5012
- function getPluginsCatalogRoot() {
5013
- const here = path17.dirname(new URL(import.meta.url).pathname);
5014
- const candidates = [
5015
- path17.join(here, "..", "plugins"),
5016
- // dev: src/scripts → src/plugins
5017
- path17.join(here, "..", "..", "plugins"),
5018
- // built: dist/scripts → dist/plugins
5019
- path17.join(here, "..", "..", "src", "plugins")
5020
- // fallback
5021
- ];
5022
- for (const c of candidates) {
5023
- if (fs19.existsSync(c) && fs19.statSync(c).isDirectory()) return c;
5024
- }
5025
- return candidates[0];
5026
- }
5027
- var buildSyntheticPlugin = async (ctx, profile) => {
5028
- const cc = profile.claudeCode;
5029
- const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
5030
- if (!needsSynthetic) return;
5031
- const catalog = getPluginsCatalogRoot();
5032
- const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
5033
- const root = path17.join(os3.tmpdir(), `kody-synth-${runId}`);
5034
- fs19.mkdirSync(path17.join(root, ".claude-plugin"), { recursive: true });
5035
- const resolvePart = (bucket, entry) => {
5036
- const local = path17.join(profile.dir, bucket, entry);
5037
- if (fs19.existsSync(local)) return local;
5038
- const central = path17.join(catalog, bucket, entry);
5039
- if (fs19.existsSync(central)) return central;
5040
- throw new Error(
5041
- `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
5042
- );
5043
- };
5044
- if (cc.skills.length > 0) {
5045
- const dst = path17.join(root, "skills");
5046
- fs19.mkdirSync(dst, { recursive: true });
5047
- for (const name of cc.skills) {
5048
- copyDir(resolvePart("skills", name), path17.join(dst, name));
5049
- }
5050
- }
5051
- if (cc.commands.length > 0) {
5052
- const dst = path17.join(root, "commands");
5053
- fs19.mkdirSync(dst, { recursive: true });
5054
- for (const name of cc.commands) {
5055
- fs19.copyFileSync(resolvePart("commands", `${name}.md`), path17.join(dst, `${name}.md`));
5056
- }
5057
- }
5058
- if (cc.hooks.length > 0) {
5059
- const dst = path17.join(root, "hooks");
5060
- fs19.mkdirSync(dst, { recursive: true });
5061
- const merged = { hooks: {} };
5062
- for (const name of cc.hooks) {
5063
- const src = resolvePart("hooks", `${name}.json`);
5064
- const parsed = JSON.parse(fs19.readFileSync(src, "utf-8"));
5065
- for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
5066
- if (!Array.isArray(entries)) continue;
5067
- if (!merged.hooks[event]) merged.hooks[event] = [];
5068
- merged.hooks[event].push(...entries);
5069
- }
5070
- }
5071
- fs19.writeFileSync(path17.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
5072
- `);
5073
- }
5074
- const manifest = {
5075
- name: `kody-synth-${profile.name}`,
5076
- version: "1.0.0",
5077
- description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
5078
- };
5079
- if (cc.skills.length > 0) manifest.skills = ["./skills/"];
5080
- if (cc.commands.length > 0) manifest.commands = ["./commands/"];
5081
- fs19.writeFileSync(path17.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
5082
- `);
5083
- ctx.data.syntheticPluginPath = root;
5084
- };
5085
- function copyDir(src, dst) {
5086
- fs19.mkdirSync(dst, { recursive: true });
5087
- for (const ent of fs19.readdirSync(src, { withFileTypes: true })) {
5088
- const s = path17.join(src, ent.name);
5089
- const d = path17.join(dst, ent.name);
5090
- if (ent.isDirectory()) copyDir(s, d);
5091
- else if (ent.isFile()) fs19.copyFileSync(s, d);
5092
- }
5093
- }
5094
-
5095
- // src/subagents.ts
5096
- function splitFrontmatter(raw) {
5097
- const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
5098
- if (!match) return { fm: {}, body: raw.trim() };
5099
- const fm = {};
5100
- for (const line of match[1].split("\n")) {
5101
- const idx = line.indexOf(":");
5102
- if (idx === -1) continue;
5103
- fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
5104
- }
5105
- return { fm, body: (match[2] ?? "").trim() };
5106
- }
5107
- function resolveAgentFile(profileDir, name) {
5108
- const local = path18.join(profileDir, "agents", `${name}.md`);
5109
- if (fs20.existsSync(local)) return local;
5110
- const central = path18.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
5111
- if (fs20.existsSync(central)) return central;
5112
- throw new Error(
5113
- `loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`
5114
- );
5115
- }
5116
- function loadSubagents(profile) {
5117
- const names = profile.claudeCode.subagents;
5118
- if (!names || names.length === 0) return void 0;
5119
- const agents = {};
5120
- for (const name of names) {
5121
- const { fm, body } = splitFrontmatter(fs20.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
5122
- if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
5123
- const def = {
5124
- description: fm.description ?? `Subagent ${name}`,
5125
- prompt: body
5126
- };
5127
- if (fm.tools) {
5128
- const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
5129
- if (tools.length > 0) def.tools = tools;
5130
- }
5131
- if (fm.model) def.model = fm.model;
5132
- agents[fm.name || name] = def;
5133
- }
5134
- return agents;
5135
- }
5136
-
5137
4997
  // src/commit.ts
5138
4998
  import { execFileSync as execFileSync8 } from "child_process";
5139
4999
 
@@ -5164,7 +5024,7 @@ function runGit(args, cwd) {
5164
5024
  }
5165
5025
  }
5166
5026
  function resolveBranch(cwd, explicit) {
5167
- if (explicit && explicit.trim()) return explicit.trim();
5027
+ if (explicit?.trim()) return explicit.trim();
5168
5028
  const r = runGit(["symbolic-ref", "--short", "HEAD"], cwd);
5169
5029
  return r.ok ? r.stdout.trim() : "";
5170
5030
  }
@@ -5214,8 +5074,8 @@ function pushWithRetry(opts = {}) {
5214
5074
  }
5215
5075
 
5216
5076
  // src/commit.ts
5217
- import * as fs21 from "fs";
5218
- import * as path19 from "path";
5077
+ import * as fs19 from "fs";
5078
+ import * as path17 from "path";
5219
5079
  var FORBIDDEN_PATH_PREFIXES = [
5220
5080
  ".kody/",
5221
5081
  ".kody-engine/",
@@ -5276,18 +5136,18 @@ function tryGit(args, cwd) {
5276
5136
  }
5277
5137
  function abortUnfinishedGitOps(cwd) {
5278
5138
  const aborted = [];
5279
- const gitDir = path19.join(cwd ?? process.cwd(), ".git");
5280
- if (!fs21.existsSync(gitDir)) return aborted;
5281
- if (fs21.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
5139
+ const gitDir = path17.join(cwd ?? process.cwd(), ".git");
5140
+ if (!fs19.existsSync(gitDir)) return aborted;
5141
+ if (fs19.existsSync(path17.join(gitDir, "MERGE_HEAD"))) {
5282
5142
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
5283
5143
  }
5284
- if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
5144
+ if (fs19.existsSync(path17.join(gitDir, "CHERRY_PICK_HEAD"))) {
5285
5145
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
5286
5146
  }
5287
- if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
5147
+ if (fs19.existsSync(path17.join(gitDir, "REVERT_HEAD"))) {
5288
5148
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
5289
5149
  }
5290
- if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
5150
+ if (fs19.existsSync(path17.join(gitDir, "rebase-merge")) || fs19.existsSync(path17.join(gitDir, "rebase-apply"))) {
5291
5151
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
5292
5152
  }
5293
5153
  try {
@@ -5343,7 +5203,7 @@ function normalizeCommitMessage(raw) {
5343
5203
  function commitAndPush(branch, agentMessage, cwd) {
5344
5204
  const allChanged = listChangedFiles(cwd);
5345
5205
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
5346
- const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
5206
+ const mergeHeadExists = fs19.existsSync(path17.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
5347
5207
  if (allowedFiles.length === 0 && !mergeHeadExists) {
5348
5208
  return { committed: false, pushed: false, sha: "", message: "" };
5349
5209
  }
@@ -5473,49 +5333,216 @@ var advanceFlow = async (ctx, profile) => {
5473
5333
  ctx.output.nextDispatch = { executable: flow.name, cliArgs: { issue: flow.issueNumber } };
5474
5334
  };
5475
5335
 
5476
- // src/scripts/brainServe.ts
5477
- init_repoWorkspace();
5478
- import { createServer } from "http";
5479
- import * as fs23 from "fs";
5480
- import * as path21 from "path";
5481
-
5482
- // src/scripts/brainTurnLog.ts
5483
- import * as fs22 from "fs";
5484
- import * as path20 from "path";
5485
- var live = /* @__PURE__ */ new Map();
5486
- function eventsPath(dir, chatId) {
5487
- return path20.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
5336
+ // src/gha.ts
5337
+ import { execFileSync as execFileSync10 } from "child_process";
5338
+ import * as fs20 from "fs";
5339
+ function getRunUrl() {
5340
+ const server = process.env.GITHUB_SERVER_URL;
5341
+ const repo = process.env.GITHUB_REPOSITORY;
5342
+ const runId = process.env.GITHUB_RUN_ID;
5343
+ if (!server || !repo || !runId) return "";
5344
+ return `${server}/${repo}/actions/runs/${runId}`;
5488
5345
  }
5489
- function lastPersistedSeq(dir, chatId) {
5490
- const p = eventsPath(dir, chatId);
5491
- if (!fs22.existsSync(p)) return 0;
5492
- const lines = fs22.readFileSync(p, "utf-8").split("\n").filter(Boolean);
5493
- if (lines.length === 0) return 0;
5346
+ function reactToTriggerComment(cwd) {
5347
+ if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
5348
+ const eventPath = process.env.GITHUB_EVENT_PATH;
5349
+ if (!eventPath || !fs20.existsSync(eventPath)) return;
5350
+ let event = null;
5494
5351
  try {
5495
- return JSON.parse(lines[lines.length - 1]).seq || 0;
5352
+ event = JSON.parse(fs20.readFileSync(eventPath, "utf-8"));
5496
5353
  } catch {
5497
- return 0;
5498
- }
5499
- }
5500
- function readSince(dir, chatId, since) {
5501
- const p = eventsPath(dir, chatId);
5502
- if (!fs22.existsSync(p)) return [];
5503
- const out = [];
5504
- for (const line of fs22.readFileSync(p, "utf-8").split("\n")) {
5505
- if (!line) continue;
5506
- try {
5507
- const rec = JSON.parse(line);
5508
- if (rec.seq > since) out.push(rec);
5509
- } catch {
5510
- }
5354
+ return;
5511
5355
  }
5512
- return out;
5513
- }
5514
- function isTerminal(event) {
5515
- return event.type === "done" || event.type === "error";
5516
- }
5517
- function beginTurn(dir, chatId) {
5518
- const existing = live.get(chatId);
5356
+ const commentId = event?.comment?.id;
5357
+ const repo = process.env.GITHUB_REPOSITORY;
5358
+ if (!commentId || !repo) return;
5359
+ const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
5360
+ const args = [
5361
+ "api",
5362
+ "-X",
5363
+ "POST",
5364
+ "-H",
5365
+ "Accept: application/vnd.github+json",
5366
+ `/repos/${repo}/issues/comments/${commentId}/reactions`,
5367
+ "-f",
5368
+ "content=eyes"
5369
+ ];
5370
+ const opts = {
5371
+ cwd,
5372
+ env: { ...process.env, GH_TOKEN: token ?? process.env.GH_TOKEN ?? "" },
5373
+ stdio: "pipe",
5374
+ timeout: 15e3
5375
+ };
5376
+ let lastErr = null;
5377
+ for (let attempt = 0; attempt < 3; attempt++) {
5378
+ if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
5379
+ try {
5380
+ execFileSync10("gh", args, opts);
5381
+ return;
5382
+ } catch (err) {
5383
+ lastErr = err;
5384
+ }
5385
+ }
5386
+ process.stderr.write(
5387
+ `[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
5388
+ `
5389
+ );
5390
+ }
5391
+ function sleepMs(ms) {
5392
+ try {
5393
+ execFileSync10("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
5394
+ } catch {
5395
+ }
5396
+ }
5397
+
5398
+ // src/scripts/appendCompanyActivity.ts
5399
+ init_issue();
5400
+
5401
+ // src/stateBranch.ts
5402
+ init_issue();
5403
+ var STATE_BRANCH = "kody-state";
5404
+ function is404(err) {
5405
+ const msg = err instanceof Error ? err.message : String(err);
5406
+ return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
5407
+ }
5408
+ function ensureStateBranch(owner, repo, cwd) {
5409
+ try {
5410
+ gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
5411
+ return;
5412
+ } catch (err) {
5413
+ if (!is404(err)) throw err;
5414
+ }
5415
+ const repoInfo = JSON.parse(gh(["api", `/repos/${owner}/${repo}`], { cwd }));
5416
+ const defaultBranch2 = repoInfo.default_branch;
5417
+ if (!defaultBranch2) {
5418
+ throw new Error(`ensureStateBranch: could not resolve default branch for ${owner}/${repo}`);
5419
+ }
5420
+ const headRef = JSON.parse(gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${defaultBranch2}`], { cwd }));
5421
+ const sha = headRef.object?.sha;
5422
+ if (!sha) {
5423
+ throw new Error(`ensureStateBranch: could not resolve head sha for ${owner}/${repo}@${defaultBranch2}`);
5424
+ }
5425
+ try {
5426
+ gh(["api", "--method", "POST", `/repos/${owner}/${repo}/git/refs`, "--input", "-"], {
5427
+ cwd,
5428
+ input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
5429
+ });
5430
+ } catch (err) {
5431
+ const msg = err instanceof Error ? err.message : String(err);
5432
+ if (/already exists/i.test(msg) || /HTTP 422/i.test(msg)) return;
5433
+ throw err;
5434
+ }
5435
+ }
5436
+
5437
+ // src/scripts/appendCompanyActivity.ts
5438
+ function resolveTrigger(force) {
5439
+ const event = process.env.GITHUB_EVENT_NAME ?? "";
5440
+ if (event === "schedule") return "schedule";
5441
+ if (force || event === "issue_comment" || event === "workflow_dispatch") return "manual";
5442
+ return "event";
5443
+ }
5444
+ function appendLine(owner, repo, cwd, record) {
5445
+ const filePath = `.kody/activity/${record.ts.slice(0, 10)}.jsonl`;
5446
+ let existing = "";
5447
+ let sha;
5448
+ try {
5449
+ const out = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
5450
+ const json = JSON.parse(out);
5451
+ if (json.sha) sha = json.sha;
5452
+ if (json.content) existing = Buffer.from(json.content, "base64").toString("utf-8");
5453
+ } catch {
5454
+ }
5455
+ const body = `${existing}${JSON.stringify(record)}
5456
+ `;
5457
+ const payload = {
5458
+ message: `chore(activity): ${record.action}`,
5459
+ content: Buffer.from(body, "utf-8").toString("base64"),
5460
+ // Keep this high-frequency feed off the default branch.
5461
+ branch: STATE_BRANCH
5462
+ };
5463
+ if (sha) payload.sha = sha;
5464
+ ensureStateBranch(owner, repo, cwd);
5465
+ gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
5466
+ cwd,
5467
+ input: JSON.stringify(payload)
5468
+ });
5469
+ }
5470
+ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
5471
+ try {
5472
+ const owner = ctx.config?.github?.owner;
5473
+ const repo = ctx.config?.github?.repo;
5474
+ const duty = String(ctx.data.jobSlug ?? ctx.args?.job ?? "").trim();
5475
+ if (!owner || !repo || !duty) return;
5476
+ const dutyTitle = ctx.data.jobTitle ?? null;
5477
+ const staff = ctx.data.workerSlug || null;
5478
+ const staffTitle = ctx.data.workerTitle || null;
5479
+ const force = ctx.args?.force === true;
5480
+ const record = {
5481
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5482
+ action: `Ran duty: ${dutyTitle ?? duty}`,
5483
+ duty,
5484
+ dutyTitle,
5485
+ staff,
5486
+ staffTitle,
5487
+ trigger: resolveTrigger(force),
5488
+ outcome: agentResult?.outcome ?? "unknown",
5489
+ outcomeKind: agentResult?.outcomeKind ?? null,
5490
+ reason: agentResult?.error ?? null,
5491
+ durationMs: agentResult?.durationMs ?? null,
5492
+ runUrl: getRunUrl() || null
5493
+ };
5494
+ appendLine(owner, repo, ctx.cwd, record);
5495
+ } catch (err) {
5496
+ process.stderr.write(
5497
+ `[activity] company-activity append failed: ${err instanceof Error ? err.message : String(err)}
5498
+ `
5499
+ );
5500
+ }
5501
+ };
5502
+
5503
+ // src/scripts/brainServe.ts
5504
+ import * as fs22 from "fs";
5505
+ import { createServer } from "http";
5506
+ import * as path19 from "path";
5507
+ init_repoWorkspace();
5508
+
5509
+ // src/scripts/brainTurnLog.ts
5510
+ import * as fs21 from "fs";
5511
+ import * as path18 from "path";
5512
+ var live = /* @__PURE__ */ new Map();
5513
+ function eventsPath(dir, chatId) {
5514
+ return path18.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
5515
+ }
5516
+ function lastPersistedSeq(dir, chatId) {
5517
+ const p = eventsPath(dir, chatId);
5518
+ if (!fs21.existsSync(p)) return 0;
5519
+ const lines = fs21.readFileSync(p, "utf-8").split("\n").filter(Boolean);
5520
+ if (lines.length === 0) return 0;
5521
+ try {
5522
+ return JSON.parse(lines[lines.length - 1]).seq || 0;
5523
+ } catch {
5524
+ return 0;
5525
+ }
5526
+ }
5527
+ function readSince(dir, chatId, since) {
5528
+ const p = eventsPath(dir, chatId);
5529
+ if (!fs21.existsSync(p)) return [];
5530
+ const out = [];
5531
+ for (const line of fs21.readFileSync(p, "utf-8").split("\n")) {
5532
+ if (!line) continue;
5533
+ try {
5534
+ const rec = JSON.parse(line);
5535
+ if (rec.seq > since) out.push(rec);
5536
+ } catch {
5537
+ }
5538
+ }
5539
+ return out;
5540
+ }
5541
+ function isTerminal(event) {
5542
+ return event.type === "done" || event.type === "error";
5543
+ }
5544
+ function beginTurn(dir, chatId) {
5545
+ const existing = live.get(chatId);
5519
5546
  const seqFloor = existing ? existing.seq : lastPersistedSeq(dir, chatId);
5520
5547
  const turn = (existing?.turn ?? 0) + 1;
5521
5548
  const state = {
@@ -5527,12 +5554,13 @@ function beginTurn(dir, chatId) {
5527
5554
  };
5528
5555
  live.set(chatId, state);
5529
5556
  const p = eventsPath(dir, chatId);
5530
- fs22.mkdirSync(path20.dirname(p), { recursive: true });
5557
+ fs21.mkdirSync(path18.dirname(p), { recursive: true });
5531
5558
  return (event) => {
5532
5559
  state.seq += 1;
5533
5560
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
5534
5561
  try {
5535
- fs22.appendFileSync(p, JSON.stringify(rec) + "\n");
5562
+ fs21.appendFileSync(p, `${JSON.stringify(rec)}
5563
+ `);
5536
5564
  } catch (err) {
5537
5565
  process.stderr.write(
5538
5566
  `[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
@@ -5570,7 +5598,8 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
5570
5598
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
5571
5599
  };
5572
5600
  try {
5573
- fs22.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
5601
+ fs21.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
5602
+ `);
5574
5603
  } catch {
5575
5604
  }
5576
5605
  state.status = "ended";
@@ -5642,9 +5671,7 @@ var DEFAULT_PORT = 8080;
5642
5671
  function getApiKey() {
5643
5672
  const key = (process.env.BRAIN_API_KEY ?? "").trim();
5644
5673
  if (!key) {
5645
- throw new Error(
5646
- "BRAIN_API_KEY env var is required \u2014 set it on the Fly machine before boot."
5647
- );
5674
+ throw new Error("BRAIN_API_KEY env var is required \u2014 set it on the Fly machine before boot.");
5648
5675
  }
5649
5676
  return key;
5650
5677
  }
@@ -5657,8 +5684,8 @@ function isSafeChatId(id) {
5657
5684
  function authOk(req, expected) {
5658
5685
  const xApiKey = req.headers["x-api-key"]?.trim();
5659
5686
  if (xApiKey && xApiKey === expected) return true;
5660
- const auth = req.headers["authorization"]?.trim();
5661
- if (auth && auth.toLowerCase().startsWith("bearer ")) {
5687
+ const auth = req.headers.authorization?.trim();
5688
+ if (auth?.toLowerCase().startsWith("bearer ")) {
5662
5689
  return auth.slice(7).trim() === expected;
5663
5690
  }
5664
5691
  return false;
@@ -5802,7 +5829,7 @@ async function handleChatTurn(req, res, chatId, opts) {
5802
5829
  const repo = strField(body, "repo");
5803
5830
  const repoToken = strField(body, "repoToken");
5804
5831
  const sessionFile = sessionFilePath(opts.cwd, chatId);
5805
- fs23.mkdirSync(path21.dirname(sessionFile), { recursive: true });
5832
+ fs22.mkdirSync(path19.dirname(sessionFile), { recursive: true });
5806
5833
  appendTurn(sessionFile, {
5807
5834
  role: "user",
5808
5835
  content: message,
@@ -5849,7 +5876,7 @@ async function handleChatTurn(req, res, chatId, opts) {
5849
5876
  function buildServer(opts) {
5850
5877
  const runTurn = opts.runTurn ?? runChatTurn;
5851
5878
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
5852
- const reposRoot = opts.reposRoot ?? path21.join(path21.dirname(path21.resolve(opts.cwd)), "repos");
5879
+ const reposRoot = opts.reposRoot ?? path19.join(path19.dirname(path19.resolve(opts.cwd)), "repos");
5853
5880
  return createServer(async (req, res) => {
5854
5881
  if (!req.method || !req.url) {
5855
5882
  sendJson(res, 400, { error: "bad request" });
@@ -5900,10 +5927,8 @@ var brainServe = async (ctx) => {
5900
5927
  ctx.skipAgent = true;
5901
5928
  const unpacked = unpackAllSecrets();
5902
5929
  if (unpacked > 0) {
5903
- process.stdout.write(
5904
- `[brain-serve] unpacked ${unpacked} secret(s) from ALL_SECRETS
5905
- `
5906
- );
5930
+ process.stdout.write(`[brain-serve] unpacked ${unpacked} secret(s) from ALL_SECRETS
5931
+ `);
5907
5932
  }
5908
5933
  const apiKey = getApiKey();
5909
5934
  const port = Number(process.env.PORT ?? DEFAULT_PORT);
@@ -5911,15 +5936,11 @@ var brainServe = async (ctx) => {
5911
5936
  const usesProxy = needsLitellmProxy(model);
5912
5937
  let handle = null;
5913
5938
  if (usesProxy) {
5914
- process.stdout.write(
5915
- `[brain-serve] starting LiteLLM proxy for ${model.provider}/${model.model}...
5916
- `
5917
- );
5939
+ process.stdout.write(`[brain-serve] starting LiteLLM proxy for ${model.provider}/${model.model}...
5940
+ `);
5918
5941
  handle = await startLitellmIfNeeded(model, ctx.cwd);
5919
- process.stdout.write(
5920
- `[brain-serve] LiteLLM ready at ${handle?.url ?? LITELLM_DEFAULT_URL}
5921
- `
5922
- );
5942
+ process.stdout.write(`[brain-serve] LiteLLM ready at ${handle?.url ?? LITELLM_DEFAULT_URL}
5943
+ `);
5923
5944
  }
5924
5945
  const litellmUrl = usesProxy ? handle?.url ?? LITELLM_DEFAULT_URL : null;
5925
5946
  const server = buildServer({
@@ -5932,10 +5953,8 @@ var brainServe = async (ctx) => {
5932
5953
  });
5933
5954
  await new Promise((resolve6) => {
5934
5955
  server.listen(port, "0.0.0.0", () => {
5935
- process.stdout.write(
5936
- `[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
5937
- `
5938
- );
5956
+ process.stdout.write(`[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
5957
+ `);
5939
5958
  resolve6();
5940
5959
  });
5941
5960
  });
@@ -5958,8 +5977,95 @@ var brainServe = async (ctx) => {
5958
5977
  });
5959
5978
  };
5960
5979
 
5980
+ // src/scripts/buildSyntheticPlugin.ts
5981
+ import * as fs23 from "fs";
5982
+ import * as os3 from "os";
5983
+ import * as path20 from "path";
5984
+ function getPluginsCatalogRoot() {
5985
+ const here = path20.dirname(new URL(import.meta.url).pathname);
5986
+ const candidates = [
5987
+ path20.join(here, "..", "plugins"),
5988
+ // dev: src/scripts → src/plugins
5989
+ path20.join(here, "..", "..", "plugins"),
5990
+ // built: dist/scripts → dist/plugins
5991
+ path20.join(here, "..", "..", "src", "plugins")
5992
+ // fallback
5993
+ ];
5994
+ for (const c of candidates) {
5995
+ if (fs23.existsSync(c) && fs23.statSync(c).isDirectory()) return c;
5996
+ }
5997
+ return candidates[0];
5998
+ }
5999
+ var buildSyntheticPlugin = async (ctx, profile) => {
6000
+ const cc = profile.claudeCode;
6001
+ const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
6002
+ if (!needsSynthetic) return;
6003
+ const catalog = getPluginsCatalogRoot();
6004
+ const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
6005
+ const root = path20.join(os3.tmpdir(), `kody-synth-${runId}`);
6006
+ fs23.mkdirSync(path20.join(root, ".claude-plugin"), { recursive: true });
6007
+ const resolvePart = (bucket, entry) => {
6008
+ const local = path20.join(profile.dir, bucket, entry);
6009
+ if (fs23.existsSync(local)) return local;
6010
+ const central = path20.join(catalog, bucket, entry);
6011
+ if (fs23.existsSync(central)) return central;
6012
+ throw new Error(
6013
+ `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
6014
+ );
6015
+ };
6016
+ if (cc.skills.length > 0) {
6017
+ const dst = path20.join(root, "skills");
6018
+ fs23.mkdirSync(dst, { recursive: true });
6019
+ for (const name of cc.skills) {
6020
+ copyDir(resolvePart("skills", name), path20.join(dst, name));
6021
+ }
6022
+ }
6023
+ if (cc.commands.length > 0) {
6024
+ const dst = path20.join(root, "commands");
6025
+ fs23.mkdirSync(dst, { recursive: true });
6026
+ for (const name of cc.commands) {
6027
+ fs23.copyFileSync(resolvePart("commands", `${name}.md`), path20.join(dst, `${name}.md`));
6028
+ }
6029
+ }
6030
+ if (cc.hooks.length > 0) {
6031
+ const dst = path20.join(root, "hooks");
6032
+ fs23.mkdirSync(dst, { recursive: true });
6033
+ const merged = { hooks: {} };
6034
+ for (const name of cc.hooks) {
6035
+ const src = resolvePart("hooks", `${name}.json`);
6036
+ const parsed = JSON.parse(fs23.readFileSync(src, "utf-8"));
6037
+ for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
6038
+ if (!Array.isArray(entries)) continue;
6039
+ if (!merged.hooks[event]) merged.hooks[event] = [];
6040
+ merged.hooks[event].push(...entries);
6041
+ }
6042
+ }
6043
+ fs23.writeFileSync(path20.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
6044
+ `);
6045
+ }
6046
+ const manifest = {
6047
+ name: `kody-synth-${profile.name}`,
6048
+ version: "1.0.0",
6049
+ description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
6050
+ };
6051
+ if (cc.skills.length > 0) manifest.skills = ["./skills/"];
6052
+ if (cc.commands.length > 0) manifest.commands = ["./commands/"];
6053
+ fs23.writeFileSync(path20.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
6054
+ `);
6055
+ ctx.data.syntheticPluginPath = root;
6056
+ };
6057
+ function copyDir(src, dst) {
6058
+ fs23.mkdirSync(dst, { recursive: true });
6059
+ for (const ent of fs23.readdirSync(src, { withFileTypes: true })) {
6060
+ const s = path20.join(src, ent.name);
6061
+ const d = path20.join(dst, ent.name);
6062
+ if (ent.isDirectory()) copyDir(s, d);
6063
+ else if (ent.isFile()) fs23.copyFileSync(s, d);
6064
+ }
6065
+ }
6066
+
5961
6067
  // src/coverage.ts
5962
- import { execFileSync as execFileSync10 } from "child_process";
6068
+ import { execFileSync as execFileSync11 } from "child_process";
5963
6069
  function patternToRegex(pattern) {
5964
6070
  let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
5965
6071
  s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
@@ -5977,7 +6083,7 @@ function renderSiblingPath(file, requireSibling) {
5977
6083
  }
5978
6084
  function safeGit(args, cwd) {
5979
6085
  try {
5980
- return execFileSync10("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
6086
+ return execFileSync11("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
5981
6087
  } catch {
5982
6088
  return "";
5983
6089
  }
@@ -6053,8 +6159,10 @@ ${formatMissesForFeedback(misses)}`;
6053
6159
  retry = await invoker(retryPrompt);
6054
6160
  } catch (err) {
6055
6161
  const msg = err instanceof Error ? err.message : String(err);
6056
- process.stderr.write(`[kody] coverage retry agent failed (${msg}); keeping ${misses.length} miss(es) \u2014 PR will draft
6057
- `);
6162
+ process.stderr.write(
6163
+ `[kody] coverage retry agent failed (${msg}); keeping ${misses.length} miss(es) \u2014 PR will draft
6164
+ `
6165
+ );
6058
6166
  ctx.data.coverageMisses = misses;
6059
6167
  return;
6060
6168
  }
@@ -6105,12 +6213,12 @@ function defaultLabelMap() {
6105
6213
 
6106
6214
  // src/scripts/commitAndPush.ts
6107
6215
  import * as fs24 from "fs";
6108
- import * as path22 from "path";
6216
+ import * as path21 from "path";
6109
6217
  init_events();
6110
6218
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
6111
6219
  function sentinelPathForStage(cwd, profileName) {
6112
6220
  const runId = resolveRunId();
6113
- return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
6221
+ return path21.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
6114
6222
  }
6115
6223
  var commitAndPush2 = async (ctx, profile) => {
6116
6224
  const branch = ctx.data.branch;
@@ -6175,7 +6283,7 @@ var commitAndPush2 = async (ctx, profile) => {
6175
6283
  const result = ctx.data.commitResult;
6176
6284
  if (sentinel && result?.committed) {
6177
6285
  try {
6178
- fs24.mkdirSync(path22.dirname(sentinel), { recursive: true });
6286
+ fs24.mkdirSync(path21.dirname(sentinel), { recursive: true });
6179
6287
  fs24.writeFileSync(
6180
6288
  sentinel,
6181
6289
  JSON.stringify(
@@ -6198,47 +6306,9 @@ var commitAndPush2 = async (ctx, profile) => {
6198
6306
  // src/goal/stateStore.ts
6199
6307
  init_issue();
6200
6308
 
6201
- // src/stateBranch.ts
6202
- init_issue();
6203
- var STATE_BRANCH = "kody-state";
6204
- function is404(err) {
6205
- const msg = err instanceof Error ? err.message : String(err);
6206
- return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
6207
- }
6208
- function ensureStateBranch(owner, repo, cwd) {
6209
- try {
6210
- gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
6211
- return;
6212
- } catch (err) {
6213
- if (!is404(err)) throw err;
6214
- }
6215
- const repoInfo = JSON.parse(gh(["api", `/repos/${owner}/${repo}`], { cwd }));
6216
- const defaultBranch2 = repoInfo.default_branch;
6217
- if (!defaultBranch2) {
6218
- throw new Error(`ensureStateBranch: could not resolve default branch for ${owner}/${repo}`);
6219
- }
6220
- const headRef = JSON.parse(
6221
- gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${defaultBranch2}`], { cwd })
6222
- );
6223
- const sha = headRef.object?.sha;
6224
- if (!sha) {
6225
- throw new Error(`ensureStateBranch: could not resolve head sha for ${owner}/${repo}@${defaultBranch2}`);
6226
- }
6227
- try {
6228
- gh(["api", "--method", "POST", `/repos/${owner}/${repo}/git/refs`, "--input", "-"], {
6229
- cwd,
6230
- input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
6231
- });
6232
- } catch (err) {
6233
- const msg = err instanceof Error ? err.message : String(err);
6234
- if (/already exists/i.test(msg) || /HTTP 422/i.test(msg)) return;
6235
- throw err;
6236
- }
6237
- }
6238
-
6239
6309
  // src/goal/state.ts
6240
6310
  import * as fs25 from "fs";
6241
- import * as path23 from "path";
6311
+ import * as path22 from "path";
6242
6312
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
6243
6313
  var GoalStateError = class extends Error {
6244
6314
  constructor(path41, message) {
@@ -6385,15 +6455,15 @@ function describeCommitMessage(goal) {
6385
6455
 
6386
6456
  // src/scripts/composePrompt.ts
6387
6457
  import * as fs26 from "fs";
6388
- import * as path24 from "path";
6458
+ import * as path23 from "path";
6389
6459
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
6390
6460
  var composePrompt = async (ctx, profile) => {
6391
6461
  const explicit = ctx.data.promptTemplate;
6392
6462
  const mode = ctx.args.mode;
6393
6463
  const candidates = [
6394
- explicit ? path24.join(profile.dir, explicit) : null,
6395
- mode ? path24.join(profile.dir, "prompts", `${mode}.md`) : null,
6396
- path24.join(profile.dir, "prompt.md")
6464
+ explicit ? path23.join(profile.dir, explicit) : null,
6465
+ mode ? path23.join(profile.dir, "prompts", `${mode}.md`) : null,
6466
+ path23.join(profile.dir, "prompt.md")
6397
6467
  ].filter(Boolean);
6398
6468
  let templatePath = "";
6399
6469
  let template = "";
@@ -6809,12 +6879,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
6809
6879
  ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
6810
6880
  return;
6811
6881
  }
6812
- await promoteReportToGoal(
6813
- ctx,
6814
- finalText,
6815
- ctx.args.scope,
6816
- ctx.args.goal
6817
- );
6882
+ await promoteReportToGoal(ctx, finalText, ctx.args.scope, ctx.args.goal);
6818
6883
  };
6819
6884
  async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
6820
6885
  const { markdown, data, jsonError } = splitReport(finalText);
@@ -6830,10 +6895,10 @@ async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
6830
6895
  const title = `QA [${verdict}]: ${scope2?.trim() || "smoke"} \u2014 ${todayIso()}`.slice(0, 240);
6831
6896
  let url = "";
6832
6897
  try {
6833
- const out = gh(
6834
- ["issue", "create", "--title", title, "--label", FINDING_LABEL, "--body-file", "-"],
6835
- { input: finalText, cwd: ctx.cwd }
6836
- );
6898
+ const out = gh(["issue", "create", "--title", title, "--label", FINDING_LABEL, "--body-file", "-"], {
6899
+ input: finalText,
6900
+ cwd: ctx.cwd
6901
+ });
6837
6902
  url = out.split("\n").map((l) => l.trim()).filter(Boolean).pop() ?? "";
6838
6903
  } catch (err) {
6839
6904
  const msg = err instanceof Error ? err.message : String(err);
@@ -6866,13 +6931,9 @@ QA_REPORT_POSTED=${url} (verdict: ${verdict})
6866
6931
  if (manifestRead.number !== null) {
6867
6932
  manifestIssueNumber = manifestRead.number;
6868
6933
  try {
6869
- postIssueComment(
6870
- manifestRead.number,
6871
- `## QA \u2014 ${verdict} \xB7 goal \`${goalId}\`
6934
+ postIssueComment(manifestRead.number, `## QA \u2014 ${verdict} \xB7 goal \`${goalId}\`
6872
6935
 
6873
- ${markdown}`,
6874
- ctx.cwd
6875
- );
6936
+ ${markdown}`, ctx.cwd);
6876
6937
  } catch (err) {
6877
6938
  const reason = err instanceof Error ? err.message : String(err);
6878
6939
  process.stderr.write(`[createQaGoal] could not comment on manifest issue: ${reason.slice(0, 300)}
@@ -6925,7 +6986,14 @@ ${markdown}`,
6925
6986
  const now = nowIso();
6926
6987
  const goalState = { state: "active", startedAt: now, updatedAt: now, extra: { version: 1 } };
6927
6988
  try {
6928
- putGoalState(ctx.config.github.owner, ctx.config.github.repo, goalId, goalState, `chore(goals): activate ${goalId}`, ctx.cwd);
6989
+ putGoalState(
6990
+ ctx.config.github.owner,
6991
+ ctx.config.github.repo,
6992
+ goalId,
6993
+ goalState,
6994
+ `chore(goals): activate ${goalId}`,
6995
+ ctx.cwd
6996
+ );
6929
6997
  } catch (err) {
6930
6998
  process.stderr.write(
6931
6999
  `[createQaGoal] failed to persist goal state to kody-state: ${err instanceof Error ? err.message : String(err)}
@@ -7000,16 +7068,7 @@ function listGoalIssues(goalId, cwd) {
7000
7068
  function listOpenPrs(cwd) {
7001
7069
  try {
7002
7070
  const out = gh(
7003
- [
7004
- "pr",
7005
- "list",
7006
- "--state",
7007
- "open",
7008
- "--limit",
7009
- "200",
7010
- "--json",
7011
- "number,url,isDraft,headRefName,baseRefName,body"
7012
- ],
7071
+ ["pr", "list", "--state", "open", "--limit", "200", "--json", "number,url,isDraft,headRefName,baseRefName,body"],
7013
7072
  { cwd }
7014
7073
  );
7015
7074
  return { ok: true, value: JSON.parse(out) };
@@ -7116,10 +7175,7 @@ function mergePrSquash(prNumber, cwd) {
7116
7175
  }
7117
7176
  function editPrBase(prNumber, baseBranch, cwd) {
7118
7177
  try {
7119
- gh(
7120
- ["api", "--method", "PATCH", `repos/{owner}/{repo}/pulls/${prNumber}`, "-f", `base=${baseBranch}`],
7121
- { cwd }
7122
- );
7178
+ gh(["api", "--method", "PATCH", `repos/{owner}/{repo}/pulls/${prNumber}`, "-f", `base=${baseBranch}`], { cwd });
7123
7179
  return { ok: true };
7124
7180
  } catch (err) {
7125
7181
  return fail(err);
@@ -7195,7 +7251,10 @@ var deriveGoalPhase = async (ctx) => {
7195
7251
  goal.phase = "idle";
7196
7252
  return;
7197
7253
  }
7198
- const taskPrs = filterGoalTaskPrs(allPrs.value ?? [], rawIssues.map((i) => i.number));
7254
+ const taskPrs = filterGoalTaskPrs(
7255
+ allPrs.value ?? [],
7256
+ rawIssues.map((i) => i.number)
7257
+ );
7199
7258
  goal.openTaskPrs = taskPrs;
7200
7259
  goal.leafPr = pickLeafPr(taskPrs);
7201
7260
  goal.childTasks = pairIssuesWithPrs(rawIssues, taskPrs);
@@ -7252,13 +7311,13 @@ var deriveQaScopeFromIssue = async (ctx) => {
7252
7311
  };
7253
7312
 
7254
7313
  // src/scripts/diagMcp.ts
7255
- import { execFileSync as execFileSync11 } from "child_process";
7314
+ import { execFileSync as execFileSync12 } from "child_process";
7256
7315
  import * as fs27 from "fs";
7257
7316
  import * as os4 from "os";
7258
- import * as path25 from "path";
7317
+ import * as path24 from "path";
7259
7318
  var diagMcp = async (_ctx) => {
7260
7319
  const home = os4.homedir();
7261
- const cacheDir = path25.join(home, ".cache", "ms-playwright");
7320
+ const cacheDir = path24.join(home, ".cache", "ms-playwright");
7262
7321
  let entries = [];
7263
7322
  try {
7264
7323
  entries = fs27.readdirSync(cacheDir);
@@ -7272,7 +7331,7 @@ var diagMcp = async (_ctx) => {
7272
7331
  process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
7273
7332
  `);
7274
7333
  try {
7275
- const v = execFileSync11("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
7334
+ const v = execFileSync12("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
7276
7335
  stdio: "pipe",
7277
7336
  timeout: 6e4,
7278
7337
  encoding: "utf8"
@@ -7288,16 +7347,16 @@ var diagMcp = async (_ctx) => {
7288
7347
 
7289
7348
  // src/scripts/discoverQaContext.ts
7290
7349
  import * as fs29 from "fs";
7291
- import * as path27 from "path";
7350
+ import * as path26 from "path";
7292
7351
 
7293
7352
  // src/scripts/frameworkDetectors.ts
7294
7353
  import * as fs28 from "fs";
7295
- import * as path26 from "path";
7354
+ import * as path25 from "path";
7296
7355
  function detectFrameworks(cwd) {
7297
7356
  const out = [];
7298
7357
  let deps = {};
7299
7358
  try {
7300
- const pkg = JSON.parse(fs28.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
7359
+ const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
7301
7360
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
7302
7361
  } catch {
7303
7362
  return out;
@@ -7334,7 +7393,7 @@ function detectFrameworks(cwd) {
7334
7393
  }
7335
7394
  function findFile(cwd, candidates) {
7336
7395
  for (const c of candidates) {
7337
- if (fs28.existsSync(path26.join(cwd, c))) return c;
7396
+ if (fs28.existsSync(path25.join(cwd, c))) return c;
7338
7397
  }
7339
7398
  return null;
7340
7399
  }
@@ -7347,7 +7406,7 @@ var COLLECTION_DIRS = [
7347
7406
  function discoverPayloadCollections(cwd) {
7348
7407
  const out = [];
7349
7408
  for (const dir of COLLECTION_DIRS) {
7350
- const full = path26.join(cwd, dir);
7409
+ const full = path25.join(cwd, dir);
7351
7410
  if (!fs28.existsSync(full)) continue;
7352
7411
  let files;
7353
7412
  try {
@@ -7357,7 +7416,7 @@ function discoverPayloadCollections(cwd) {
7357
7416
  }
7358
7417
  for (const file of files) {
7359
7418
  try {
7360
- const filePath = path26.join(full, file);
7419
+ const filePath = path25.join(full, file);
7361
7420
  const content = fs28.readFileSync(filePath, "utf-8").slice(0, 1e4);
7362
7421
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
7363
7422
  if (!slugMatch) continue;
@@ -7372,7 +7431,7 @@ function discoverPayloadCollections(cwd) {
7372
7431
  out.push({
7373
7432
  name,
7374
7433
  slug,
7375
- filePath: path26.relative(cwd, filePath),
7434
+ filePath: path25.relative(cwd, filePath),
7376
7435
  fields: fields.slice(0, 20),
7377
7436
  hasAdmin
7378
7437
  });
@@ -7386,7 +7445,7 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
7386
7445
  function discoverAdminComponents(cwd, collections) {
7387
7446
  const out = [];
7388
7447
  for (const dir of ADMIN_COMPONENT_DIRS) {
7389
- const full = path26.join(cwd, dir);
7448
+ const full = path25.join(cwd, dir);
7390
7449
  if (!fs28.existsSync(full)) continue;
7391
7450
  let entries;
7392
7451
  try {
@@ -7395,19 +7454,19 @@ function discoverAdminComponents(cwd, collections) {
7395
7454
  continue;
7396
7455
  }
7397
7456
  for (const entry of entries) {
7398
- const entryPath = path26.join(full, entry.name);
7457
+ const entryPath = path25.join(full, entry.name);
7399
7458
  let name;
7400
7459
  let filePath;
7401
7460
  if (entry.isDirectory()) {
7402
7461
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
7403
- (f) => fs28.existsSync(path26.join(entryPath, f))
7462
+ (f) => fs28.existsSync(path25.join(entryPath, f))
7404
7463
  );
7405
7464
  if (!indexFile) continue;
7406
7465
  name = entry.name;
7407
- filePath = path26.relative(cwd, path26.join(entryPath, indexFile));
7466
+ filePath = path25.relative(cwd, path25.join(entryPath, indexFile));
7408
7467
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
7409
7468
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
7410
- filePath = path26.relative(cwd, entryPath);
7469
+ filePath = path25.relative(cwd, entryPath);
7411
7470
  } else {
7412
7471
  continue;
7413
7472
  }
@@ -7415,7 +7474,7 @@ function discoverAdminComponents(cwd, collections) {
7415
7474
  if (collections) {
7416
7475
  for (const col of collections) {
7417
7476
  try {
7418
- const colContent = fs28.readFileSync(path26.join(cwd, col.filePath), "utf-8");
7477
+ const colContent = fs28.readFileSync(path25.join(cwd, col.filePath), "utf-8");
7419
7478
  if (colContent.includes(name)) {
7420
7479
  usedInCollection = col.slug;
7421
7480
  break;
@@ -7434,7 +7493,7 @@ function scanApiRoutes(cwd) {
7434
7493
  const out = [];
7435
7494
  const appDirs = ["src/app", "app"];
7436
7495
  for (const appDir of appDirs) {
7437
- const apiDir = path26.join(cwd, appDir, "api");
7496
+ const apiDir = path25.join(cwd, appDir, "api");
7438
7497
  if (!fs28.existsSync(apiDir)) continue;
7439
7498
  walkApiRoutes(apiDir, "/api", cwd, out);
7440
7499
  break;
@@ -7451,7 +7510,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7451
7510
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
7452
7511
  if (routeFile) {
7453
7512
  try {
7454
- const content = fs28.readFileSync(path26.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
7513
+ const content = fs28.readFileSync(path25.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
7455
7514
  const methods = HTTP_METHODS.filter(
7456
7515
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
7457
7516
  );
@@ -7459,7 +7518,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7459
7518
  out.push({
7460
7519
  path: prefix,
7461
7520
  methods,
7462
- filePath: path26.relative(cwd, path26.join(dir, routeFile.name))
7521
+ filePath: path25.relative(cwd, path25.join(dir, routeFile.name))
7463
7522
  });
7464
7523
  }
7465
7524
  } catch {
@@ -7470,7 +7529,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7470
7529
  if (entry.name === "node_modules" || entry.name === ".next") continue;
7471
7530
  let segment = entry.name;
7472
7531
  if (segment.startsWith("(") && segment.endsWith(")")) {
7473
- walkApiRoutes(path26.join(dir, entry.name), prefix, cwd, out);
7532
+ walkApiRoutes(path25.join(dir, entry.name), prefix, cwd, out);
7474
7533
  continue;
7475
7534
  }
7476
7535
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -7478,7 +7537,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7478
7537
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
7479
7538
  segment = `:${segment.slice(1, -1)}`;
7480
7539
  }
7481
- walkApiRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
7540
+ walkApiRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
7482
7541
  }
7483
7542
  }
7484
7543
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -7498,7 +7557,7 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
7498
7557
  function scanEnvVars(cwd) {
7499
7558
  const candidates = [".env.example", ".env.local.example", ".env.template"];
7500
7559
  for (const envFile of candidates) {
7501
- const envPath = path26.join(cwd, envFile);
7560
+ const envPath = path25.join(cwd, envFile);
7502
7561
  if (!fs28.existsSync(envPath)) continue;
7503
7562
  try {
7504
7563
  const content = fs28.readFileSync(envPath, "utf-8");
@@ -7549,9 +7608,9 @@ function runQaDiscovery(cwd) {
7549
7608
  }
7550
7609
  function detectDevServer(cwd, out) {
7551
7610
  try {
7552
- const pkg = JSON.parse(fs29.readFileSync(path27.join(cwd, "package.json"), "utf-8"));
7611
+ const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
7553
7612
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
7554
- const pm = fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs29.existsSync(path27.join(cwd, "yarn.lock")) ? "yarn" : fs29.existsSync(path27.join(cwd, "bun.lockb")) ? "bun" : "npm";
7613
+ 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";
7555
7614
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
7556
7615
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
7557
7616
  else if (allDeps.vite) out.devPort = 5173;
@@ -7561,7 +7620,7 @@ function detectDevServer(cwd, out) {
7561
7620
  function scanFrontendRoutes(cwd, out) {
7562
7621
  const appDirs = ["src/app", "app"];
7563
7622
  for (const appDir of appDirs) {
7564
- const full = path27.join(cwd, appDir);
7623
+ const full = path26.join(cwd, appDir);
7565
7624
  if (!fs29.existsSync(full)) continue;
7566
7625
  walkFrontendRoutes(full, "", out);
7567
7626
  break;
@@ -7587,7 +7646,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7587
7646
  if (entry.name === "node_modules" || entry.name === ".next") continue;
7588
7647
  let segment = entry.name;
7589
7648
  if (segment.startsWith("(") && segment.endsWith(")")) {
7590
- walkFrontendRoutes(path27.join(dir, entry.name), prefix, out);
7649
+ walkFrontendRoutes(path26.join(dir, entry.name), prefix, out);
7591
7650
  continue;
7592
7651
  }
7593
7652
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -7595,7 +7654,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7595
7654
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
7596
7655
  segment = `:${segment.slice(1, -1)}`;
7597
7656
  }
7598
- walkFrontendRoutes(path27.join(dir, entry.name), `${prefix}/${segment}`, out);
7657
+ walkFrontendRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, out);
7599
7658
  }
7600
7659
  }
7601
7660
  function detectAuthFiles(cwd, out) {
@@ -7612,13 +7671,13 @@ function detectAuthFiles(cwd, out) {
7612
7671
  "src/app/api/oauth"
7613
7672
  ];
7614
7673
  for (const c of candidates) {
7615
- if (fs29.existsSync(path27.join(cwd, c))) out.authFiles.push(c);
7674
+ if (fs29.existsSync(path26.join(cwd, c))) out.authFiles.push(c);
7616
7675
  }
7617
7676
  }
7618
7677
  function detectRoles(cwd, out) {
7619
7678
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
7620
7679
  for (const rp of rolePaths) {
7621
- const dir = path27.join(cwd, rp);
7680
+ const dir = path26.join(cwd, rp);
7622
7681
  if (!fs29.existsSync(dir)) continue;
7623
7682
  let files;
7624
7683
  try {
@@ -7628,7 +7687,7 @@ function detectRoles(cwd, out) {
7628
7687
  }
7629
7688
  for (const f of files) {
7630
7689
  try {
7631
- const content = fs29.readFileSync(path27.join(dir, f), "utf-8").slice(0, 5e3);
7690
+ const content = fs29.readFileSync(path26.join(dir, f), "utf-8").slice(0, 5e3);
7632
7691
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
7633
7692
  if (roleMatches) {
7634
7693
  for (const m of roleMatches) {
@@ -7701,7 +7760,8 @@ Required env vars: ${d.envVars.join(", ")}`);
7701
7760
  let result = sections.join("\n");
7702
7761
  if (result.length > MAX_SERIALIZED_LENGTH) {
7703
7762
  const cutoff = result.lastIndexOf("\n", MAX_SERIALIZED_LENGTH - 20);
7704
- result = result.slice(0, cutoff > 0 ? cutoff : MAX_SERIALIZED_LENGTH - 20) + "\n... (truncated)";
7763
+ result = `${result.slice(0, cutoff > 0 ? cutoff : MAX_SERIALIZED_LENGTH - 20)}
7764
+ ... (truncated)`;
7705
7765
  }
7706
7766
  return result;
7707
7767
  }
@@ -7756,7 +7816,7 @@ function parsePr(url) {
7756
7816
  }
7757
7817
 
7758
7818
  // src/scripts/dispatchClassified.ts
7759
- import { execFileSync as execFileSync12 } from "child_process";
7819
+ import { execFileSync as execFileSync13 } from "child_process";
7760
7820
  var API_TIMEOUT_MS4 = 3e4;
7761
7821
  var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
7762
7822
  var dispatchClassified = async (ctx) => {
@@ -7779,14 +7839,18 @@ ${stateBody}`;
7779
7839
  try {
7780
7840
  const existing = findStateComment("issue", issueNumber, ctx.cwd);
7781
7841
  if (existing) {
7782
- execFileSync12("gh", ["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"], {
7783
- cwd: ctx.cwd,
7784
- timeout: API_TIMEOUT_MS4,
7785
- input: body,
7786
- stdio: ["pipe", "pipe", "pipe"]
7787
- });
7842
+ execFileSync13(
7843
+ "gh",
7844
+ ["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"],
7845
+ {
7846
+ cwd: ctx.cwd,
7847
+ timeout: API_TIMEOUT_MS4,
7848
+ input: body,
7849
+ stdio: ["pipe", "pipe", "pipe"]
7850
+ }
7851
+ );
7788
7852
  } else {
7789
- execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7853
+ execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7790
7854
  cwd: ctx.cwd,
7791
7855
  timeout: API_TIMEOUT_MS4,
7792
7856
  stdio: ["ignore", "pipe", "pipe"]
@@ -7807,7 +7871,7 @@ ${stateBody}`;
7807
7871
 
7808
7872
  // src/scripts/dispatchJobFileTicks.ts
7809
7873
  import * as fs31 from "fs";
7810
- import * as path29 from "path";
7874
+ import * as path28 from "path";
7811
7875
 
7812
7876
  // src/scripts/jobFrontmatter.ts
7813
7877
  var SCHEDULE_EVERY_VALUES = [
@@ -7823,7 +7887,7 @@ var SCHEDULE_EVERY_VALUES = [
7823
7887
  "manual"
7824
7888
  ];
7825
7889
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
7826
- function splitFrontmatter2(raw) {
7890
+ function splitFrontmatter(raw) {
7827
7891
  const match = FRONTMATTER_RE.exec(raw);
7828
7892
  if (!match) return { frontmatter: {}, body: raw };
7829
7893
  const inner = match[1] ?? "";
@@ -8061,7 +8125,8 @@ var ContentsApiBackend = class {
8061
8125
  return false;
8062
8126
  }
8063
8127
  const slug = slugFromStateFilePath(loaded.path);
8064
- const body = JSON.stringify(next, null, 2) + "\n";
8128
+ const body = `${JSON.stringify(next, null, 2)}
8129
+ `;
8065
8130
  const payload = {
8066
8131
  message: `chore(jobs): update state for ${slug} (rev ${next.rev})`,
8067
8132
  content: Buffer.from(body, "utf-8").toString("base64"),
@@ -8100,7 +8165,7 @@ function isShaConflict(err) {
8100
8165
 
8101
8166
  // src/scripts/jobState/localFileBackend.ts
8102
8167
  import * as fs30 from "fs";
8103
- import * as path28 from "path";
8168
+ import * as path27 from "path";
8104
8169
  var LocalFileBackend = class {
8105
8170
  name = "local-file";
8106
8171
  cwd;
@@ -8115,7 +8180,7 @@ var LocalFileBackend = class {
8115
8180
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
8116
8181
  this.cwd = opts.cwd;
8117
8182
  this.jobsDir = opts.jobsDir;
8118
- this.absDir = path28.join(opts.cwd, opts.jobsDir);
8183
+ this.absDir = path27.join(opts.cwd, opts.jobsDir);
8119
8184
  this.owner = opts.owner;
8120
8185
  this.repo = opts.repo;
8121
8186
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -8175,7 +8240,7 @@ var LocalFileBackend = class {
8175
8240
  }
8176
8241
  load(slug) {
8177
8242
  const relPath = stateFilePath(this.jobsDir, slug);
8178
- const absPath = path28.join(this.cwd, relPath);
8243
+ const absPath = path27.join(this.cwd, relPath);
8179
8244
  if (!fs30.existsSync(absPath)) {
8180
8245
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
8181
8246
  }
@@ -8196,9 +8261,10 @@ var LocalFileBackend = class {
8196
8261
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
8197
8262
  return false;
8198
8263
  }
8199
- const absPath = path28.join(this.cwd, loaded.path);
8200
- fs30.mkdirSync(path28.dirname(absPath), { recursive: true });
8201
- const body = JSON.stringify(next, null, 2) + "\n";
8264
+ const absPath = path27.join(this.cwd, loaded.path);
8265
+ fs30.mkdirSync(path27.dirname(absPath), { recursive: true });
8266
+ const body = `${JSON.stringify(next, null, 2)}
8267
+ `;
8202
8268
  const tmpPath = `${absPath}.${process.pid}.tmp`;
8203
8269
  fs30.writeFileSync(tmpPath, body, "utf-8");
8204
8270
  fs30.renameSync(tmpPath, absPath);
@@ -8279,7 +8345,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
8279
8345
  await backend.hydrate();
8280
8346
  }
8281
8347
  try {
8282
- const slugs = listJobSlugs(path29.join(ctx.cwd, jobsDir));
8348
+ const slugs = listJobSlugs(path28.join(ctx.cwd, jobsDir));
8283
8349
  ctx.data.jobSlugCount = slugs.length;
8284
8350
  if (slugs.length === 0) {
8285
8351
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -8299,10 +8365,8 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
8299
8365
  continue;
8300
8366
  }
8301
8367
  if (!frontmatter.staff || frontmatter.staff.trim().length === 0) {
8302
- process.stderr.write(
8303
- `[jobs] \u23ED skip ${slug}: no staff assigned (add 'staff: <slug>' frontmatter)
8304
- `
8305
- );
8368
+ process.stderr.write(`[jobs] \u23ED skip ${slug}: no staff assigned (add 'staff: <slug>' frontmatter)
8369
+ `);
8306
8370
  results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
8307
8371
  continue;
8308
8372
  }
@@ -8392,8 +8456,8 @@ function formatAgo(ms) {
8392
8456
  }
8393
8457
  function readJobFrontmatter(cwd, jobsDir, slug) {
8394
8458
  try {
8395
- const raw = fs31.readFileSync(path29.join(cwd, jobsDir, `${slug}.md`), "utf-8");
8396
- return splitFrontmatter2(raw).frontmatter;
8459
+ const raw = fs31.readFileSync(path28.join(cwd, jobsDir, `${slug}.md`), "utf-8");
8460
+ return splitFrontmatter(raw).frontmatter;
8397
8461
  } catch {
8398
8462
  return {};
8399
8463
  }
@@ -8499,13 +8563,10 @@ var dispatchNextTask = async (ctx) => {
8499
8563
 
8500
8564
  // src/pr.ts
8501
8565
  init_issue();
8502
- import { execFileSync as execFileSync13 } from "child_process";
8566
+ import { execFileSync as execFileSync14 } from "child_process";
8503
8567
  function prMergeStatus(prNumber, cwd) {
8504
8568
  try {
8505
- const out = gh(
8506
- ["pr", "view", String(prNumber), "--json", "mergeable,mergeStateStatus"],
8507
- { cwd }
8508
- );
8569
+ const out = gh(["pr", "view", String(prNumber), "--json", "mergeable,mergeStateStatus"], { cwd });
8509
8570
  const parsed = JSON.parse(out);
8510
8571
  const mergeable = parsed.mergeable ?? "UNKNOWN";
8511
8572
  const mergeStateStatus = parsed.mergeStateStatus ?? "UNKNOWN";
@@ -8625,7 +8686,7 @@ function isAlreadyExistsError(err) {
8625
8686
  return ALREADY_EXISTS_RE.test(msg);
8626
8687
  }
8627
8688
  function git2(args, cwd) {
8628
- return execFileSync13("git", args, {
8689
+ return execFileSync14("git", args, {
8629
8690
  encoding: "utf-8",
8630
8691
  timeout: 3e4,
8631
8692
  cwd,
@@ -8890,8 +8951,10 @@ var finalizeGoal = async (ctx) => {
8890
8951
  );
8891
8952
  continue;
8892
8953
  }
8893
- process.stdout.write(`[goal-tick] closing task issue #${t.number} (goal finalized \u2014 carried by PR #${leaf.number})
8894
- `);
8954
+ process.stdout.write(
8955
+ `[goal-tick] closing task issue #${t.number} (goal finalized \u2014 carried by PR #${leaf.number})
8956
+ `
8957
+ );
8895
8958
  const closed = closeIssue(
8896
8959
  t.number,
8897
8960
  {
@@ -8956,7 +9019,7 @@ var finalizeTerminal = async (ctx) => {
8956
9019
 
8957
9020
  // src/scripts/finishFlow.ts
8958
9021
  init_issue();
8959
- import { execFileSync as execFileSync14 } from "child_process";
9022
+ import { execFileSync as execFileSync15 } from "child_process";
8960
9023
  var TERMINAL_PHASE = {
8961
9024
  "review-passed": { phase: "shipped", status: "succeeded" },
8962
9025
  "fix-applied": { phase: "shipped", status: "succeeded" },
@@ -8978,7 +9041,7 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
8978
9041
  if (state) state.flow = void 0;
8979
9042
  if (!issueNumber) return;
8980
9043
  const label = typeof args?.label === "string" ? args.label : void 0;
8981
- if (label && label.startsWith(KODY_NAMESPACE)) {
9044
+ if (label?.startsWith(KODY_NAMESPACE)) {
8982
9045
  const spec = {
8983
9046
  label,
8984
9047
  color: typeof args?.color === "string" ? args.color : void 0,
@@ -8996,7 +9059,7 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
8996
9059
  **PR:** ${state.core.prUrl}` : "";
8997
9060
  const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
8998
9061
  try {
8999
- execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
9062
+ execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
9000
9063
  timeout: API_TIMEOUT_MS5,
9001
9064
  cwd: ctx.cwd,
9002
9065
  stdio: ["ignore", "pipe", "pipe"]
@@ -9026,9 +9089,9 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
9026
9089
  };
9027
9090
 
9028
9091
  // src/branch.ts
9029
- import { execFileSync as execFileSync15 } from "child_process";
9092
+ import { execFileSync as execFileSync16 } from "child_process";
9030
9093
  function git3(args, cwd) {
9031
- return execFileSync15("git", args, {
9094
+ return execFileSync16("git", args, {
9032
9095
  encoding: "utf-8",
9033
9096
  timeout: 3e4,
9034
9097
  cwd,
@@ -9045,11 +9108,11 @@ function getCurrentBranch(cwd) {
9045
9108
  }
9046
9109
  function resetWorkingTree2(cwd) {
9047
9110
  try {
9048
- execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
9111
+ execFileSync16("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
9049
9112
  } catch {
9050
9113
  }
9051
9114
  try {
9052
- execFileSync15("git", ["clean", "-fd", "-e", ".kody"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
9115
+ execFileSync16("git", ["clean", "-fd", "-e", ".kody"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
9053
9116
  } catch {
9054
9117
  }
9055
9118
  }
@@ -9061,14 +9124,19 @@ function checkoutPrBranch(prNumber, cwd) {
9061
9124
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
9062
9125
  };
9063
9126
  try {
9064
- execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
9127
+ execFileSync16("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
9065
9128
  } catch {
9066
9129
  }
9067
9130
  try {
9068
- execFileSync15("git", ["clean", "-fd", "-e", ".kody"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
9131
+ execFileSync16("git", ["clean", "-fd", "-e", ".kody"], {
9132
+ cwd,
9133
+ env,
9134
+ stdio: ["ignore", "pipe", "pipe"],
9135
+ timeout: 3e4
9136
+ });
9069
9137
  } catch {
9070
9138
  }
9071
- execFileSync15("gh", ["pr", "checkout", String(prNumber)], {
9139
+ execFileSync16("gh", ["pr", "checkout", String(prNumber)], {
9072
9140
  cwd,
9073
9141
  env,
9074
9142
  stdio: ["ignore", "pipe", "pipe"],
@@ -9101,7 +9169,11 @@ function mergeBase(baseBranch, cwd) {
9101
9169
  }
9102
9170
  function restoreKodyAssets(cwd) {
9103
9171
  try {
9104
- execFileSync15("git", ["checkout", "HEAD", "--", ".kody"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
9172
+ execFileSync16("git", ["checkout", "HEAD", "--", ".kody"], {
9173
+ cwd,
9174
+ stdio: ["ignore", "pipe", "pipe"],
9175
+ timeout: 3e4
9176
+ });
9105
9177
  } catch {
9106
9178
  }
9107
9179
  }
@@ -9205,68 +9277,6 @@ function ensureFeatureBranchInner(issueNumber, title, defaultBranch2, cwd, baseB
9205
9277
  return { branch: branchName, created: true };
9206
9278
  }
9207
9279
 
9208
- // src/gha.ts
9209
- import { execFileSync as execFileSync16 } from "child_process";
9210
- import * as fs32 from "fs";
9211
- function getRunUrl() {
9212
- const server = process.env.GITHUB_SERVER_URL;
9213
- const repo = process.env.GITHUB_REPOSITORY;
9214
- const runId = process.env.GITHUB_RUN_ID;
9215
- if (!server || !repo || !runId) return "";
9216
- return `${server}/${repo}/actions/runs/${runId}`;
9217
- }
9218
- function reactToTriggerComment(cwd) {
9219
- if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
9220
- const eventPath = process.env.GITHUB_EVENT_PATH;
9221
- if (!eventPath || !fs32.existsSync(eventPath)) return;
9222
- let event = null;
9223
- try {
9224
- event = JSON.parse(fs32.readFileSync(eventPath, "utf-8"));
9225
- } catch {
9226
- return;
9227
- }
9228
- const commentId = event?.comment?.id;
9229
- const repo = process.env.GITHUB_REPOSITORY;
9230
- if (!commentId || !repo) return;
9231
- const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
9232
- const args = [
9233
- "api",
9234
- "-X",
9235
- "POST",
9236
- "-H",
9237
- "Accept: application/vnd.github+json",
9238
- `/repos/${repo}/issues/comments/${commentId}/reactions`,
9239
- "-f",
9240
- "content=eyes"
9241
- ];
9242
- const opts = {
9243
- cwd,
9244
- env: { ...process.env, GH_TOKEN: token ?? process.env.GH_TOKEN ?? "" },
9245
- stdio: "pipe",
9246
- timeout: 15e3
9247
- };
9248
- let lastErr = null;
9249
- for (let attempt = 0; attempt < 3; attempt++) {
9250
- if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
9251
- try {
9252
- execFileSync16("gh", args, opts);
9253
- return;
9254
- } catch (err) {
9255
- lastErr = err;
9256
- }
9257
- }
9258
- process.stderr.write(
9259
- `[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
9260
- `
9261
- );
9262
- }
9263
- function sleepMs(ms) {
9264
- try {
9265
- execFileSync16("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
9266
- } catch {
9267
- }
9268
- }
9269
-
9270
9280
  // src/scripts/fixCiFlow.ts
9271
9281
  init_issue();
9272
9282
 
@@ -9515,12 +9525,12 @@ var handleAbandonedGoal = async (ctx) => {
9515
9525
 
9516
9526
  // src/scripts/initFlow.ts
9517
9527
  import { execFileSync as execFileSync18 } from "child_process";
9518
- import * as fs33 from "fs";
9519
- import * as path30 from "path";
9528
+ import * as fs32 from "fs";
9529
+ import * as path29 from "path";
9520
9530
  function detectPackageManager(cwd) {
9521
- if (fs33.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9522
- if (fs33.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
9523
- if (fs33.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
9531
+ if (fs32.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9532
+ if (fs32.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
9533
+ if (fs32.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
9524
9534
  return "npm";
9525
9535
  }
9526
9536
  function qualityCommandsFor(pm) {
@@ -9649,36 +9659,36 @@ function performInit(cwd, force) {
9649
9659
  const pm = detectPackageManager(cwd);
9650
9660
  const ownerRepo = detectOwnerRepo(cwd);
9651
9661
  const defaultBranch2 = defaultBranchFromGit(cwd);
9652
- const configPath = path30.join(cwd, "kody.config.json");
9653
- if (fs33.existsSync(configPath) && !force) {
9662
+ const configPath = path29.join(cwd, "kody.config.json");
9663
+ if (fs32.existsSync(configPath) && !force) {
9654
9664
  skipped.push("kody.config.json");
9655
9665
  } else {
9656
9666
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
9657
- fs33.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9667
+ fs32.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9658
9668
  `);
9659
9669
  wrote.push("kody.config.json");
9660
9670
  }
9661
- const workflowDir = path30.join(cwd, ".github", "workflows");
9662
- const workflowPath = path30.join(workflowDir, "kody.yml");
9663
- if (fs33.existsSync(workflowPath) && !force) {
9671
+ const workflowDir = path29.join(cwd, ".github", "workflows");
9672
+ const workflowPath = path29.join(workflowDir, "kody.yml");
9673
+ if (fs32.existsSync(workflowPath) && !force) {
9664
9674
  skipped.push(".github/workflows/kody.yml");
9665
9675
  } else {
9666
- fs33.mkdirSync(workflowDir, { recursive: true });
9667
- fs33.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9676
+ fs32.mkdirSync(workflowDir, { recursive: true });
9677
+ fs32.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9668
9678
  wrote.push(".github/workflows/kody.yml");
9669
9679
  }
9670
9680
  const builtinJobs = listBuiltinJobs();
9671
9681
  if (builtinJobs.length > 0) {
9672
- const jobsDir = path30.join(cwd, ".kody", "duties");
9673
- fs33.mkdirSync(jobsDir, { recursive: true });
9682
+ const jobsDir = path29.join(cwd, ".kody", "duties");
9683
+ fs32.mkdirSync(jobsDir, { recursive: true });
9674
9684
  for (const job of builtinJobs) {
9675
- const rel = path30.join(".kody", "duties", `${job.slug}.md`);
9676
- const target = path30.join(cwd, rel);
9677
- if (fs33.existsSync(target) && !force) {
9685
+ const rel = path29.join(".kody", "duties", `${job.slug}.md`);
9686
+ const target = path29.join(cwd, rel);
9687
+ if (fs32.existsSync(target) && !force) {
9678
9688
  skipped.push(rel);
9679
9689
  continue;
9680
9690
  }
9681
- fs33.writeFileSync(target, fs33.readFileSync(job.filePath, "utf-8"));
9691
+ fs32.writeFileSync(target, fs32.readFileSync(job.filePath, "utf-8"));
9682
9692
  wrote.push(rel);
9683
9693
  }
9684
9694
  }
@@ -9690,12 +9700,12 @@ function performInit(cwd, force) {
9690
9700
  continue;
9691
9701
  }
9692
9702
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
9693
- const target = path30.join(workflowDir, `kody-${exe.name}.yml`);
9694
- if (fs33.existsSync(target) && !force) {
9703
+ const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
9704
+ if (fs32.existsSync(target) && !force) {
9695
9705
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
9696
9706
  continue;
9697
9707
  }
9698
- fs33.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9708
+ fs32.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9699
9709
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
9700
9710
  }
9701
9711
  let labels;
@@ -9880,8 +9890,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
9880
9890
 
9881
9891
  // src/scripts/loadJobFromFile.ts
9882
9892
  init_dutyMcp();
9883
- import * as fs34 from "fs";
9884
- import * as path31 from "path";
9893
+ import * as fs33 from "fs";
9894
+ import * as path30 from "path";
9885
9895
  var DUTY_TOOL_PALETTE = new Set(DUTY_MCP_TOOL_NAMES);
9886
9896
  var loadJobFromFile = async (ctx, profile, args) => {
9887
9897
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -9891,25 +9901,23 @@ var loadJobFromFile = async (ctx, profile, args) => {
9891
9901
  if (!slug) {
9892
9902
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
9893
9903
  }
9894
- const absPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
9895
- if (!fs34.existsSync(absPath)) {
9904
+ const absPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
9905
+ if (!fs33.existsSync(absPath)) {
9896
9906
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
9897
9907
  }
9898
- const raw = fs34.readFileSync(absPath, "utf-8");
9908
+ const raw = fs33.readFileSync(absPath, "utf-8");
9899
9909
  const { title, body } = parseJobFile(raw, slug);
9900
- const frontmatter = splitFrontmatter2(raw).frontmatter;
9910
+ const frontmatter = splitFrontmatter(raw).frontmatter;
9901
9911
  const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
9902
9912
  const workerSlug = (frontmatter.staff ?? "").trim();
9903
9913
  let workerTitle = "";
9904
9914
  let workerPersona = "";
9905
9915
  if (workerSlug) {
9906
- const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9907
- if (!fs34.existsSync(workerPath)) {
9908
- throw new Error(
9909
- `loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`
9910
- );
9916
+ const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9917
+ if (!fs33.existsSync(workerPath)) {
9918
+ throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
9911
9919
  }
9912
- const workerRaw = fs34.readFileSync(workerPath, "utf-8");
9920
+ const workerRaw = fs33.readFileSync(workerPath, "utf-8");
9913
9921
  const parsed = parseJobFile(workerRaw, workerSlug);
9914
9922
  workerTitle = parsed.title;
9915
9923
  workerPersona = parsed.body;
@@ -9993,18 +10001,18 @@ init_loadMemoryContext();
9993
10001
  init_loadPriorArt();
9994
10002
 
9995
10003
  // src/scripts/loadQaContext.ts
9996
- import * as fs36 from "fs";
9997
- import * as path33 from "path";
9998
-
9999
- // src/scripts/kodyVariables.ts
10000
10004
  import * as fs35 from "fs";
10001
10005
  import * as path32 from "path";
10006
+
10007
+ // src/scripts/kodyVariables.ts
10008
+ import * as fs34 from "fs";
10009
+ import * as path31 from "path";
10002
10010
  var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
10003
10011
  function readKodyVariables(cwd) {
10004
- const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
10012
+ const full = path31.join(cwd, KODY_VARIABLES_REL_PATH);
10005
10013
  let raw;
10006
10014
  try {
10007
- raw = fs35.readFileSync(full, "utf-8");
10015
+ raw = fs34.readFileSync(full, "utf-8");
10008
10016
  } catch {
10009
10017
  return {};
10010
10018
  }
@@ -10029,7 +10037,9 @@ var LEGACY_AUDIENCE_TO_STAFF = { chat: "kody", qa: QA_STAFF };
10029
10037
  var FRONTMATTER_RE2 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
10030
10038
  function parseSlugList(value) {
10031
10039
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
10032
- return inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, "").toLowerCase()).filter(Boolean);
10040
+ return inner.split(",").map(
10041
+ (s) => s.trim().replace(/^["']|["']$/g, "").toLowerCase()
10042
+ ).filter(Boolean);
10033
10043
  }
10034
10044
  function readProfileStaff(raw) {
10035
10045
  const m = FRONTMATTER_RE2.exec(raw);
@@ -10053,18 +10063,18 @@ function readProfileStaff(raw) {
10053
10063
  return { staff: staff ?? legacy ?? ["kody"], body };
10054
10064
  }
10055
10065
  function readProfile(cwd) {
10056
- const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
10057
- if (!fs36.existsSync(dir)) return "";
10066
+ const dir = path32.join(cwd, CONTEXT_DIR_REL_PATH);
10067
+ if (!fs35.existsSync(dir)) return "";
10058
10068
  let entries;
10059
10069
  try {
10060
- entries = fs36.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
10070
+ entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
10061
10071
  } catch {
10062
10072
  return "";
10063
10073
  }
10064
10074
  const blocks = [];
10065
10075
  for (const file of entries) {
10066
10076
  try {
10067
- const raw = fs36.readFileSync(path33.join(dir, file), "utf-8");
10077
+ const raw = fs35.readFileSync(path32.join(dir, file), "utf-8");
10068
10078
  const { staff, body } = readProfileStaff(raw);
10069
10079
  if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
10070
10080
  blocks.push(`## ${file}
@@ -10101,8 +10111,8 @@ var loadQaContext = async (ctx) => {
10101
10111
  init_events();
10102
10112
 
10103
10113
  // src/taskContext.ts
10104
- import * as fs37 from "fs";
10105
- import * as path34 from "path";
10114
+ import * as fs36 from "fs";
10115
+ import * as path33 from "path";
10106
10116
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
10107
10117
  function buildTaskContext(args) {
10108
10118
  return {
@@ -10118,10 +10128,10 @@ function buildTaskContext(args) {
10118
10128
  }
10119
10129
  function persistTaskContext(cwd, ctx) {
10120
10130
  try {
10121
- const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
10122
- fs37.mkdirSync(dir, { recursive: true });
10123
- const file = path34.join(dir, "task-context.json");
10124
- fs37.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
10131
+ const dir = path33.join(cwd, ".kody", "runs", ctx.runId);
10132
+ fs36.mkdirSync(dir, { recursive: true });
10133
+ const file = path33.join(dir, "task-context.json");
10134
+ fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
10125
10135
  `);
10126
10136
  return file;
10127
10137
  } catch (err) {
@@ -10187,19 +10197,19 @@ var loadTaskState = async (ctx) => {
10187
10197
  };
10188
10198
 
10189
10199
  // src/scripts/loadWorkerAdhoc.ts
10190
- import * as fs38 from "fs";
10191
- import * as path35 from "path";
10200
+ import * as fs37 from "fs";
10201
+ import * as path34 from "path";
10192
10202
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
10193
10203
  const workersDir = String(args?.workersDir ?? ".kody/staff");
10194
10204
  const workerSlug = String(ctx.args.worker ?? "").trim();
10195
10205
  if (!workerSlug) {
10196
10206
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
10197
10207
  }
10198
- const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10199
- if (!fs38.existsSync(workerPath)) {
10208
+ const workerPath = path34.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10209
+ if (!fs37.existsSync(workerPath)) {
10200
10210
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
10201
10211
  }
10202
- const { title, body } = parsePersona(fs38.readFileSync(workerPath, "utf-8"), workerSlug);
10212
+ const { title, body } = parsePersona(fs37.readFileSync(workerPath, "utf-8"), workerSlug);
10203
10213
  const message = resolveMessage(ctx.args.message);
10204
10214
  if (!message) {
10205
10215
  throw new Error(
@@ -10219,9 +10229,9 @@ function resolveMessage(messageArg) {
10219
10229
  }
10220
10230
  function readCommentBody() {
10221
10231
  const eventPath = process.env.GITHUB_EVENT_PATH;
10222
- if (!eventPath || !fs38.existsSync(eventPath)) return "";
10232
+ if (!eventPath || !fs37.existsSync(eventPath)) return "";
10223
10233
  try {
10224
- const event = JSON.parse(fs38.readFileSync(eventPath, "utf-8"));
10234
+ const event = JSON.parse(fs37.readFileSync(eventPath, "utf-8"));
10225
10235
  return String(event.comment?.body ?? "");
10226
10236
  } catch {
10227
10237
  return "";
@@ -10245,7 +10255,7 @@ function stripDirective(body) {
10245
10255
  return lines.slice(start).join("\n").trim();
10246
10256
  }
10247
10257
  function parsePersona(raw, slug) {
10248
- const stripped = splitFrontmatter2(raw).body;
10258
+ const stripped = splitFrontmatter(raw).body;
10249
10259
  const trimmed = stripped.trim();
10250
10260
  const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
10251
10261
  const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
@@ -10270,16 +10280,9 @@ var markFlowSuccess = async (ctx) => {
10270
10280
  // src/scripts/mergeFlow.ts
10271
10281
  init_issue();
10272
10282
  function readPr(prNumber, cwd) {
10273
- const out = gh(
10274
- [
10275
- "pr",
10276
- "view",
10277
- String(prNumber),
10278
- "--json",
10279
- "state,isDraft,mergeable,mergeStateStatus,title,url"
10280
- ],
10281
- { cwd }
10282
- );
10283
+ const out = gh(["pr", "view", String(prNumber), "--json", "state,isDraft,mergeable,mergeStateStatus,title,url"], {
10284
+ cwd
10285
+ });
10283
10286
  const p = JSON.parse(out);
10284
10287
  return {
10285
10288
  state: p.state ?? "UNKNOWN",
@@ -10356,11 +10359,7 @@ var mergeFlow = async (ctx) => {
10356
10359
  `);
10357
10360
  ctx.data.mergeAction = verdict.action;
10358
10361
  if (verdict.action === "MERGE_BLOCKED") {
10359
- commentOnIssue(
10360
- prNumber,
10361
- `\u{1F6A6} _Auto-merge held: ${verdict.reason} Kody will retry once the PR is CLEAN._`,
10362
- ctx.cwd
10363
- );
10362
+ commentOnIssue(prNumber, `\u{1F6A6} _Auto-merge held: ${verdict.reason} Kody will retry once the PR is CLEAN._`, ctx.cwd);
10364
10363
  }
10365
10364
  return;
10366
10365
  }
@@ -10766,10 +10765,7 @@ var parseReproOutput = async (ctx, _profile, agentResult) => {
10766
10765
  }
10767
10766
  }
10768
10767
  if (!signature) {
10769
- downgrade(
10770
- ctx,
10771
- "reproduce missing or malformed FAILURE_SIGNATURE JSON (must contain errorType + messageContains)"
10772
- );
10768
+ downgrade(ctx, "reproduce missing or malformed FAILURE_SIGNATURE JSON (must contain errorType + messageContains)");
10773
10769
  return;
10774
10770
  }
10775
10771
  ctx.data.reproTestPath = testPath;
@@ -10796,7 +10792,7 @@ function stripMarkdownEmphasis2(s) {
10796
10792
  }
10797
10793
  function downgrade(ctx, reason) {
10798
10794
  const action = ctx.data.action;
10799
- if (action && action.type.endsWith("_COMPLETED")) {
10795
+ if (action?.type.endsWith("_COMPLETED")) {
10800
10796
  ctx.data.action = {
10801
10797
  type: action.type.replace(/_COMPLETED$/, "_FAILED"),
10802
10798
  payload: { reason, downgradedFrom: action.type },
@@ -10855,6 +10851,102 @@ var persistFlowState = async (ctx) => {
10855
10851
  import { spawn as spawn4 } from "child_process";
10856
10852
  import { createServer as createServer2 } from "http";
10857
10853
 
10854
+ // src/github-health.ts
10855
+ var STATUS_URL = "https://www.githubstatus.com/api/v2/components.json";
10856
+ var STATUS_CACHE_TTL_MS = 3e4;
10857
+ var statusCache = null;
10858
+ async function probeActionsStatus(fetchImpl = fetch) {
10859
+ if (statusCache && statusCache.expiresAt > Date.now()) return statusCache.probe;
10860
+ try {
10861
+ const res = await fetchImpl(STATUS_URL, { headers: { "User-Agent": "kody-engine" } });
10862
+ if (!res.ok) return { degraded: false, label: `http_${res.status}` };
10863
+ const body = await res.json();
10864
+ const actions = (body.components ?? []).find((c) => (c.name ?? "").trim().toLowerCase() === "actions");
10865
+ const label = actions?.status ?? "unknown";
10866
+ const degraded = !!actions && label !== "operational";
10867
+ const probe = { degraded, label };
10868
+ statusCache = { probe, expiresAt: Date.now() + STATUS_CACHE_TTL_MS };
10869
+ return probe;
10870
+ } catch {
10871
+ return { degraded: false, label: "probe_error" };
10872
+ }
10873
+ }
10874
+ async function gitHubActionsDegraded(fetchImpl = fetch) {
10875
+ return (await probeActionsStatus(fetchImpl)).degraded;
10876
+ }
10877
+
10878
+ // src/pool/duty-fallback-tick.ts
10879
+ async function runDutyFallbackTick(deps) {
10880
+ if (!await deps.isDegraded()) {
10881
+ return { ran: false, claimed: 0 };
10882
+ }
10883
+ const repos = deps.activeRepos();
10884
+ if (repos.length === 0) {
10885
+ deps.log("GitHub Actions degraded but no active repo pools \u2014 nothing to tick");
10886
+ return { ran: true, claimed: 0 };
10887
+ }
10888
+ deps.log(`GitHub Actions degraded \u2014 running scheduled fan-out on Fly for ${repos.length} repo(s)`);
10889
+ const clock = deps.now ?? Date.now;
10890
+ let claimed = 0;
10891
+ for (const tag of repos) {
10892
+ const [owner, repo] = tag.split("/");
10893
+ if (!owner || !repo) continue;
10894
+ try {
10895
+ const res = await deps.claim(owner, repo, {
10896
+ jobId: `sched-${owner}-${repo}-${clock()}`,
10897
+ repo: tag,
10898
+ mode: "scheduled"
10899
+ });
10900
+ if (res.ok) {
10901
+ claimed++;
10902
+ deps.log(`[${tag}] scheduled fan-out claimed ${res.machineId}`);
10903
+ } else {
10904
+ deps.log(`[${tag}] scheduled fan-out skipped: ${res.reason ?? "pool unavailable"}`);
10905
+ }
10906
+ } catch (err) {
10907
+ deps.log(`[${tag}] scheduled fan-out error: ${err instanceof Error ? err.message : String(err)}`);
10908
+ }
10909
+ }
10910
+ return { ran: true, claimed };
10911
+ }
10912
+
10913
+ // src/pool/keys.ts
10914
+ import { hkdfSync } from "crypto";
10915
+ var POOL_API_KEY_INFO = "kody-pool-api:v1";
10916
+ var RUNNER_API_KEY_INFO = "kody-runner-api:v1";
10917
+ function masterKeyBytes(raw) {
10918
+ const v = raw.trim();
10919
+ if (!v) throw new Error("KODY_MASTER_KEY is empty");
10920
+ if (/^[0-9a-fA-F]+$/.test(v) && v.length === 64) {
10921
+ return Buffer.from(v, "hex");
10922
+ }
10923
+ return Buffer.from(v.replace(/-/g, "+").replace(/_/g, "/"), "base64");
10924
+ }
10925
+ function deriveKey(master, info, length = 32) {
10926
+ return Buffer.from(hkdfSync("sha256", master, Buffer.alloc(0), info, length)).toString("hex");
10927
+ }
10928
+ function derivePoolApiKey(master) {
10929
+ return deriveKey(master, POOL_API_KEY_INFO);
10930
+ }
10931
+ function deriveRunnerApiKey(master) {
10932
+ return deriveKey(master, RUNNER_API_KEY_INFO);
10933
+ }
10934
+ function bearerOk(headerAuth, xApiKey, expected) {
10935
+ const x = (xApiKey ?? "").trim();
10936
+ if (x && timingEqual(x, expected)) return true;
10937
+ const a = (headerAuth ?? "").trim();
10938
+ if (a.toLowerCase().startsWith("bearer ")) {
10939
+ return timingEqual(a.slice(7).trim(), expected);
10940
+ }
10941
+ return false;
10942
+ }
10943
+ function timingEqual(a, b) {
10944
+ if (a.length !== b.length) return false;
10945
+ let diff = 0;
10946
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
10947
+ return diff === 0;
10948
+ }
10949
+
10858
10950
  // src/pool/fly.ts
10859
10951
  var FLY_API_BASE = "https://api.machines.dev/v1";
10860
10952
  var POOL_METADATA_KEY = "kody_pool";
@@ -11231,7 +11323,7 @@ async function readVaultSecrets(opts) {
11231
11323
  async function readRepoSecret(opts) {
11232
11324
  const secrets = await readVaultSecrets(opts);
11233
11325
  const v = secrets[opts.name];
11234
- return v && v.trim() ? v : null;
11326
+ return v?.trim() ? v : null;
11235
11327
  }
11236
11328
  async function readRepoSecrets(opts) {
11237
11329
  return readVaultSecrets(opts);
@@ -11297,7 +11389,9 @@ var PoolRegistry = class {
11297
11389
  try {
11298
11390
  min = await this.resolvePoolMin(owner, repo);
11299
11391
  } catch (err) {
11300
- this.log(`registry: pool-min read failed for ${repoTag}, using default ${min}: ${err instanceof Error ? err.message : String(err)}`);
11392
+ this.log(
11393
+ `registry: pool-min read failed for ${repoTag}, using default ${min}: ${err instanceof Error ? err.message : String(err)}`
11394
+ );
11301
11395
  }
11302
11396
  const fly = new FlyClient({ token: flyToken, app: this.cfg.base.app });
11303
11397
  const pm = new PoolManager({
@@ -11325,7 +11419,9 @@ var PoolRegistry = class {
11325
11419
  Object.entries(vault).filter(([k]) => k !== "FLY_API_TOKEN" && k !== POOL_MIN_VAULT_KEY)
11326
11420
  );
11327
11421
  } catch (err) {
11328
- this.log(`[${this.key(owner, repo)}] vault secrets read failed: ${err instanceof Error ? err.message : String(err)}`);
11422
+ this.log(
11423
+ `[${this.key(owner, repo)}] vault secrets read failed: ${err instanceof Error ? err.message : String(err)}`
11424
+ );
11329
11425
  }
11330
11426
  const job = {
11331
11427
  jobId: req.jobId,
@@ -11366,102 +11462,6 @@ var PoolRegistry = class {
11366
11462
  }
11367
11463
  };
11368
11464
 
11369
- // src/pool/keys.ts
11370
- import { hkdfSync } from "crypto";
11371
- var POOL_API_KEY_INFO = "kody-pool-api:v1";
11372
- var RUNNER_API_KEY_INFO = "kody-runner-api:v1";
11373
- function masterKeyBytes(raw) {
11374
- const v = raw.trim();
11375
- if (!v) throw new Error("KODY_MASTER_KEY is empty");
11376
- if (/^[0-9a-fA-F]+$/.test(v) && v.length === 64) {
11377
- return Buffer.from(v, "hex");
11378
- }
11379
- return Buffer.from(v.replace(/-/g, "+").replace(/_/g, "/"), "base64");
11380
- }
11381
- function deriveKey(master, info, length = 32) {
11382
- return Buffer.from(hkdfSync("sha256", master, Buffer.alloc(0), info, length)).toString("hex");
11383
- }
11384
- function derivePoolApiKey(master) {
11385
- return deriveKey(master, POOL_API_KEY_INFO);
11386
- }
11387
- function deriveRunnerApiKey(master) {
11388
- return deriveKey(master, RUNNER_API_KEY_INFO);
11389
- }
11390
- function bearerOk(headerAuth, xApiKey, expected) {
11391
- const x = (xApiKey ?? "").trim();
11392
- if (x && timingEqual(x, expected)) return true;
11393
- const a = (headerAuth ?? "").trim();
11394
- if (a.toLowerCase().startsWith("bearer ")) {
11395
- return timingEqual(a.slice(7).trim(), expected);
11396
- }
11397
- return false;
11398
- }
11399
- function timingEqual(a, b) {
11400
- if (a.length !== b.length) return false;
11401
- let diff = 0;
11402
- for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
11403
- return diff === 0;
11404
- }
11405
-
11406
- // src/pool/duty-fallback-tick.ts
11407
- async function runDutyFallbackTick(deps) {
11408
- if (!await deps.isDegraded()) {
11409
- return { ran: false, claimed: 0 };
11410
- }
11411
- const repos = deps.activeRepos();
11412
- if (repos.length === 0) {
11413
- deps.log("GitHub Actions degraded but no active repo pools \u2014 nothing to tick");
11414
- return { ran: true, claimed: 0 };
11415
- }
11416
- deps.log(`GitHub Actions degraded \u2014 running scheduled fan-out on Fly for ${repos.length} repo(s)`);
11417
- const clock = deps.now ?? Date.now;
11418
- let claimed = 0;
11419
- for (const tag of repos) {
11420
- const [owner, repo] = tag.split("/");
11421
- if (!owner || !repo) continue;
11422
- try {
11423
- const res = await deps.claim(owner, repo, {
11424
- jobId: `sched-${owner}-${repo}-${clock()}`,
11425
- repo: tag,
11426
- mode: "scheduled"
11427
- });
11428
- if (res.ok) {
11429
- claimed++;
11430
- deps.log(`[${tag}] scheduled fan-out claimed ${res.machineId}`);
11431
- } else {
11432
- deps.log(`[${tag}] scheduled fan-out skipped: ${res.reason ?? "pool unavailable"}`);
11433
- }
11434
- } catch (err) {
11435
- deps.log(`[${tag}] scheduled fan-out error: ${err instanceof Error ? err.message : String(err)}`);
11436
- }
11437
- }
11438
- return { ran: true, claimed };
11439
- }
11440
-
11441
- // src/github-health.ts
11442
- var STATUS_URL = "https://www.githubstatus.com/api/v2/components.json";
11443
- var STATUS_CACHE_TTL_MS = 3e4;
11444
- var statusCache = null;
11445
- async function probeActionsStatus(fetchImpl = fetch) {
11446
- if (statusCache && statusCache.expiresAt > Date.now()) return statusCache.probe;
11447
- try {
11448
- const res = await fetchImpl(STATUS_URL, { headers: { "User-Agent": "kody-engine" } });
11449
- if (!res.ok) return { degraded: false, label: `http_${res.status}` };
11450
- const body = await res.json();
11451
- const actions = (body.components ?? []).find((c) => (c.name ?? "").trim().toLowerCase() === "actions");
11452
- const label = actions?.status ?? "unknown";
11453
- const degraded = !!actions && label !== "operational";
11454
- const probe = { degraded, label };
11455
- statusCache = { probe, expiresAt: Date.now() + STATUS_CACHE_TTL_MS };
11456
- return probe;
11457
- } catch {
11458
- return { degraded: false, label: "probe_error" };
11459
- }
11460
- }
11461
- async function gitHubActionsDegraded(fetchImpl = fetch) {
11462
- return (await probeActionsStatus(fetchImpl)).degraded;
11463
- }
11464
-
11465
11465
  // src/scripts/poolServe.ts
11466
11466
  var PERF_GUEST = {
11467
11467
  low: { cpu_kind: "shared", cpus: 2, memory_mb: 2048 },
@@ -11609,7 +11609,7 @@ var poolServe = async (ctx) => {
11609
11609
  });
11610
11610
  }
11611
11611
  const authed = bearerOk(
11612
- req.headers["authorization"],
11612
+ req.headers.authorization,
11613
11613
  req.headers["x-api-key"],
11614
11614
  poolApiKey
11615
11615
  );
@@ -11839,21 +11839,56 @@ ${plan}`;
11839
11839
  ---
11840
11840
  _Orchestrator will advance to the next step automatically._`;
11841
11841
  }
11842
- return `${head}
11843
-
11844
- ---
11845
- Comment \`kody run\` (prefixed with \`@\`) to execute this plan.`;
11846
- }
11847
-
11848
- // src/scripts/postResearchComment.ts
11849
- var postResearchComment = async (ctx) => {
11850
- postAgentSummaryComment(ctx, { issueOnly: true, render: renderResearchComment });
11842
+ return `${head}
11843
+
11844
+ ---
11845
+ Comment \`kody run\` (prefixed with \`@\`) to execute this plan.`;
11846
+ }
11847
+
11848
+ // src/scripts/postResearchComment.ts
11849
+ var postResearchComment = async (ctx) => {
11850
+ postAgentSummaryComment(ctx, { issueOnly: true, render: renderResearchComment });
11851
+ };
11852
+ function renderResearchComment(issueNumber, body) {
11853
+ return `## Research for issue #${issueNumber}
11854
+
11855
+ ${body}`;
11856
+ }
11857
+
11858
+ // src/scripts/promoteQaGoal.ts
11859
+ init_issue();
11860
+ var REPORT_JSON_OPEN2 = "<!-- KODY_QA_REPORT_JSON";
11861
+ var promoteQaGoal = async (ctx) => {
11862
+ ctx.skipAgent = true;
11863
+ const issueNum = ctx.args.issue;
11864
+ if (typeof issueNum !== "number" || issueNum <= 0) {
11865
+ ctx.output.exitCode = 2;
11866
+ ctx.output.reason = "qa-goal requires --issue <n>";
11867
+ process.stderr.write("[qa-goal] missing --issue\n");
11868
+ return;
11869
+ }
11870
+ let report;
11871
+ try {
11872
+ const issue = getIssue(issueNum, ctx.cwd);
11873
+ const reportComment = [...issue.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
11874
+ if (!reportComment) {
11875
+ ctx.output.exitCode = 3;
11876
+ ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
11877
+ process.stderr.write(`[qa-goal] ${ctx.output.reason}
11878
+ `);
11879
+ return;
11880
+ }
11881
+ report = reportComment.body;
11882
+ } catch (err) {
11883
+ const msg = err instanceof Error ? err.message : String(err);
11884
+ ctx.output.exitCode = 3;
11885
+ ctx.output.reason = `failed to read issue #${issueNum}: ${msg}`;
11886
+ process.stderr.write(`[qa-goal] ${ctx.output.reason}
11887
+ `);
11888
+ return;
11889
+ }
11890
+ await promoteReportToGoal(ctx, report, ctx.args.scope, ctx.args.goal);
11851
11891
  };
11852
- function renderResearchComment(issueNumber, body) {
11853
- return `## Research for issue #${issueNumber}
11854
-
11855
- ${body}`;
11856
- }
11857
11892
 
11858
11893
  // src/scripts/recordClassification.ts
11859
11894
  import { execFileSync as execFileSync20 } from "child_process";
@@ -11962,7 +11997,7 @@ function countActionItems(block) {
11962
11997
  }
11963
11998
 
11964
11999
  // src/scripts/requirePlanDeviations.ts
11965
- var requirePlanDeviations = async (ctx, profile) => {
12000
+ var requirePlanDeviations = async (ctx, _profile) => {
11966
12001
  if (!ctx.data.agentDone) return;
11967
12002
  const artifacts = ctx.data.artifacts ?? {};
11968
12003
  const planContent = (artifacts.plan ?? "").trim();
@@ -12167,46 +12202,6 @@ function pushEmptyCommit(branch, cwd) {
12167
12202
  }
12168
12203
  }
12169
12204
 
12170
- // src/scripts/promoteQaGoal.ts
12171
- init_issue();
12172
- var REPORT_JSON_OPEN2 = "<!-- KODY_QA_REPORT_JSON";
12173
- var promoteQaGoal = async (ctx) => {
12174
- ctx.skipAgent = true;
12175
- const issueNum = ctx.args.issue;
12176
- if (typeof issueNum !== "number" || issueNum <= 0) {
12177
- ctx.output.exitCode = 2;
12178
- ctx.output.reason = "qa-goal requires --issue <n>";
12179
- process.stderr.write("[qa-goal] missing --issue\n");
12180
- return;
12181
- }
12182
- let report;
12183
- try {
12184
- const issue = getIssue(issueNum, ctx.cwd);
12185
- const reportComment = [...issue.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
12186
- if (!reportComment) {
12187
- ctx.output.exitCode = 3;
12188
- ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
12189
- process.stderr.write(`[qa-goal] ${ctx.output.reason}
12190
- `);
12191
- return;
12192
- }
12193
- report = reportComment.body;
12194
- } catch (err) {
12195
- const msg = err instanceof Error ? err.message : String(err);
12196
- ctx.output.exitCode = 3;
12197
- ctx.output.reason = `failed to read issue #${issueNum}: ${msg}`;
12198
- process.stderr.write(`[qa-goal] ${ctx.output.reason}
12199
- `);
12200
- return;
12201
- }
12202
- await promoteReportToGoal(
12203
- ctx,
12204
- report,
12205
- ctx.args.scope,
12206
- ctx.args.goal
12207
- );
12208
- };
12209
-
12210
12205
  // src/deployments.ts
12211
12206
  init_issue();
12212
12207
  function findPreviewDeploymentUrl(prNumber, cwd) {
@@ -12527,7 +12522,13 @@ var runFlow = async (ctx) => {
12527
12522
  process.stderr.write(`[kody runFlow] resolved base branch: ${base} (from --base)
12528
12523
  `);
12529
12524
  }
12530
- const branchInfo = ensureFeatureBranch(issueNumber, issue.title, ctx.config.git.defaultBranch, ctx.cwd, base ?? void 0);
12525
+ const branchInfo = ensureFeatureBranch(
12526
+ issueNumber,
12527
+ issue.title,
12528
+ ctx.config.git.defaultBranch,
12529
+ ctx.cwd,
12530
+ base ?? void 0
12531
+ );
12531
12532
  ctx.data.branch = branchInfo.branch;
12532
12533
  const runUrl = getRunUrl();
12533
12534
  const startMsg = runUrl ? `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\`, run ${runUrl}` : `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\``;
@@ -12549,24 +12550,22 @@ function resolveBaseOverride(value) {
12549
12550
 
12550
12551
  // src/scripts/runnerServe.ts
12551
12552
  import { spawn as spawn5 } from "child_process";
12553
+ import * as fs38 from "fs";
12552
12554
  import { createServer as createServer3 } from "http";
12553
- import * as fs39 from "fs";
12554
12555
  var DEFAULT_PORT2 = 8080;
12555
12556
  var DEFAULT_WORKDIR = "/workspace/repo";
12556
12557
  function getApiKey2() {
12557
12558
  const key = (process.env.RUNNER_API_KEY ?? "").trim();
12558
12559
  if (!key) {
12559
- throw new Error(
12560
- "RUNNER_API_KEY env var is required \u2014 set it on the pooled machine before boot."
12561
- );
12560
+ throw new Error("RUNNER_API_KEY env var is required \u2014 set it on the pooled machine before boot.");
12562
12561
  }
12563
12562
  return key;
12564
12563
  }
12565
12564
  function authOk2(req, expected) {
12566
12565
  const xApiKey = req.headers["x-api-key"]?.trim();
12567
12566
  if (xApiKey && xApiKey === expected) return true;
12568
- const auth = req.headers["authorization"]?.trim();
12569
- if (auth && auth.toLowerCase().startsWith("bearer ")) {
12567
+ const auth = req.headers.authorization?.trim();
12568
+ if (auth?.toLowerCase().startsWith("bearer ")) {
12570
12569
  return auth.slice(7).trim() === expected;
12571
12570
  }
12572
12571
  return false;
@@ -12631,8 +12630,8 @@ async function defaultRunJob(job) {
12631
12630
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
12632
12631
  const branch = job.ref ?? "main";
12633
12632
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
12634
- fs39.rmSync(workdir, { recursive: true, force: true });
12635
- fs39.mkdirSync(workdir, { recursive: true });
12633
+ fs38.rmSync(workdir, { recursive: true, force: true });
12634
+ fs38.mkdirSync(workdir, { recursive: true });
12636
12635
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
12637
12636
  const interactive = job.mode === "interactive";
12638
12637
  const scheduled = job.mode === "scheduled";
@@ -12672,15 +12671,7 @@ async function defaultRunJob(job) {
12672
12671
  });
12673
12672
  process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
12674
12673
  `);
12675
- const cloneCode = await run("git", [
12676
- "clone",
12677
- "--depth=1",
12678
- "--single-branch",
12679
- "--branch",
12680
- branch,
12681
- authUrl,
12682
- workdir
12683
- ]);
12674
+ const cloneCode = await run("git", ["clone", "--depth=1", "--single-branch", "--branch", branch, authUrl, workdir]);
12684
12675
  if (cloneCode !== 0) {
12685
12676
  process.stderr.write(`[runner-serve] job ${job.jobId}: clone failed (${cloneCode})
12686
12677
  `);
@@ -12773,7 +12764,7 @@ var runnerServe = async (ctx) => {
12773
12764
 
12774
12765
  // src/scripts/runPreviewBuild.ts
12775
12766
  import { copyFile, writeFile } from "fs/promises";
12776
- import * as path36 from "path";
12767
+ import * as path35 from "path";
12777
12768
  import { fileURLToPath } from "url";
12778
12769
 
12779
12770
  // src/scripts/previewBuildHelpers.ts
@@ -12901,29 +12892,12 @@ async function setupNamespaceBuilder(opts) {
12901
12892
  await runCmd("bash", ["-c", NSC_INSTALL]);
12902
12893
  const jwt = await fetchGithubOidcToken(NSC_OIDC_AUDIENCE);
12903
12894
  if (!jwt) {
12904
- console.warn(
12905
- "[preview-build] no GitHub OIDC token (id-token: write missing?) \u2014 local docker build"
12906
- );
12895
+ console.warn("[preview-build] no GitHub OIDC token (id-token: write missing?) \u2014 local docker build");
12907
12896
  return null;
12908
12897
  }
12909
- await runCmd("nsc", [
12910
- "auth",
12911
- "exchange-oidc-token",
12912
- "--tenant_id",
12913
- opts.tenantId,
12914
- "--token",
12915
- jwt
12916
- ]);
12917
- await runCmd("nsc", [
12918
- "docker",
12919
- "buildx",
12920
- "setup",
12921
- "--name",
12922
- opts.builderName
12923
- ]);
12924
- console.log(
12925
- `[preview-build] Namespace remote builder ready (${opts.builderName})`
12926
- );
12898
+ await runCmd("nsc", ["auth", "exchange-oidc-token", "--tenant_id", opts.tenantId, "--token", jwt]);
12899
+ await runCmd("nsc", ["docker", "buildx", "setup", "--name", opts.builderName]);
12900
+ console.log(`[preview-build] Namespace remote builder ready (${opts.builderName})`);
12927
12901
  return opts.builderName;
12928
12902
  } catch (err) {
12929
12903
  console.warn(
@@ -12939,9 +12913,9 @@ var FLY_MACHINES = "https://api.machines.dev/v1";
12939
12913
  var FLY_GRAPHQL = "https://api.fly.io/graphql";
12940
12914
  var REQ_TIMEOUT_MS2 = 3e4;
12941
12915
  function bundledDockerfilePath(mode) {
12942
- const here = path36.dirname(fileURLToPath(import.meta.url));
12916
+ const here = path35.dirname(fileURLToPath(import.meta.url));
12943
12917
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
12944
- return path36.join(here, "preview-build-templates", file);
12918
+ return path35.join(here, "preview-build-templates", file);
12945
12919
  }
12946
12920
  function required(name) {
12947
12921
  const v = (process.env[name] ?? "").trim();
@@ -13026,13 +13000,10 @@ async function flyAllocateSharedIps(appName, token) {
13026
13000
  }
13027
13001
  }
13028
13002
  async function flyListMachines(appName, token) {
13029
- const res = await fetch(
13030
- `${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines`,
13031
- {
13032
- headers: flyHeaders(token),
13033
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13034
- }
13035
- );
13003
+ const res = await fetch(`${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines`, {
13004
+ headers: flyHeaders(token),
13005
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13006
+ });
13036
13007
  if (res.status === 404) return [];
13037
13008
  if (!res.ok) {
13038
13009
  throw new Error(`listMachines ${appName}: ${res.status}`);
@@ -13041,14 +13012,11 @@ async function flyListMachines(appName, token) {
13041
13012
  return data.map((m) => ({ id: m.id, state: m.state }));
13042
13013
  }
13043
13014
  async function flyDestroyMachine(appName, machineId, token) {
13044
- await fetch(
13045
- `${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines/${encodeURIComponent(machineId)}/stop`,
13046
- {
13047
- method: "POST",
13048
- headers: flyHeaders(token),
13049
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13050
- }
13051
- ).catch(() => void 0);
13015
+ await fetch(`${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines/${encodeURIComponent(machineId)}/stop`, {
13016
+ method: "POST",
13017
+ headers: flyHeaders(token),
13018
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13019
+ }).catch(() => void 0);
13052
13020
  const res = await fetch(
13053
13021
  `${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines/${encodeURIComponent(machineId)}?force=true`,
13054
13022
  {
@@ -13101,23 +13069,18 @@ async function flyCreatePreviewMachine(args, token) {
13101
13069
  };
13102
13070
  let lastErr = null;
13103
13071
  for (let attempt = 0; attempt < 6; attempt++) {
13104
- const res = await fetch(
13105
- `${FLY_MACHINES}/apps/${encodeURIComponent(args.appName)}/machines`,
13106
- {
13107
- method: "POST",
13108
- headers: flyHeaders(token),
13109
- body: JSON.stringify(body),
13110
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13111
- }
13112
- );
13072
+ const res = await fetch(`${FLY_MACHINES}/apps/${encodeURIComponent(args.appName)}/machines`, {
13073
+ method: "POST",
13074
+ headers: flyHeaders(token),
13075
+ body: JSON.stringify(body),
13076
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13077
+ });
13113
13078
  if (res.ok) {
13114
13079
  const { id } = await res.json();
13115
13080
  return id;
13116
13081
  }
13117
13082
  const text = await res.text().catch(() => "");
13118
- lastErr = new Error(
13119
- `createPreviewMachine ${res.status}: ${text.slice(0, 300)}`
13120
- );
13083
+ lastErr = new Error(`createPreviewMachine ${res.status}: ${text.slice(0, 300)}`);
13121
13084
  if (!/MANIFEST_UNKNOWN|manifest unknown/i.test(text)) break;
13122
13085
  await new Promise((r) => setTimeout(r, 2e3 * (attempt + 1)));
13123
13086
  }
@@ -13137,21 +13100,18 @@ async function postOrUpdatePreviewComment(args) {
13137
13100
  signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13138
13101
  }).catch(() => null);
13139
13102
  let existingId = null;
13140
- if (listRes && listRes.ok) {
13103
+ if (listRes?.ok) {
13141
13104
  const comments = await listRes.json().catch(() => []);
13142
13105
  const hit = comments.find((c) => (c.body ?? "").includes(MARKER));
13143
13106
  if (hit) existingId = hit.id;
13144
13107
  }
13145
13108
  if (existingId) {
13146
- await fetch(
13147
- `https://api.github.com/repos/${args.repo}/issues/comments/${existingId}`,
13148
- {
13149
- method: "PATCH",
13150
- headers,
13151
- body: JSON.stringify({ body: args.body }),
13152
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13153
- }
13154
- );
13109
+ await fetch(`https://api.github.com/repos/${args.repo}/issues/comments/${existingId}`, {
13110
+ method: "PATCH",
13111
+ headers,
13112
+ body: JSON.stringify({ body: args.body }),
13113
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
13114
+ });
13155
13115
  return;
13156
13116
  }
13157
13117
  await fetch(base, {
@@ -13179,9 +13139,7 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13179
13139
  masterKey = required("KODY_MASTER_KEY");
13180
13140
  ghToken4 = (process.env.KODY_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.GH_PAT ?? "").trim();
13181
13141
  if (!ghToken4) {
13182
- throw new Error(
13183
- "GitHub auth token missing (KODY_TOKEN / GH_TOKEN / GITHUB_TOKEN / GH_PAT all empty)"
13184
- );
13142
+ throw new Error("GitHub auth token missing (KODY_TOKEN / GH_TOKEN / GITHUB_TOKEN / GH_PAT all empty)");
13185
13143
  }
13186
13144
  } catch (err) {
13187
13145
  ctx.output.exitCode = 99;
@@ -13203,20 +13161,13 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13203
13161
  const orgSlug = doc.secrets?.FLY_ORG_SLUG?.value?.trim() || (process.env.FLY_ORG_SLUG ?? "personal").trim();
13204
13162
  const region = doc.secrets?.FLY_DEFAULT_REGION?.value?.trim() || (process.env.FLY_REGION ?? "fra").trim();
13205
13163
  const nscTenantId = doc.secrets?.NSC_TENANT_ID?.value?.trim() || "";
13206
- console.log(
13207
- `[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`
13208
- );
13164
+ console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
13209
13165
  if (Object.keys(buildEnv).length > 0) {
13210
- const lines = Object.entries(buildEnv).map(
13211
- ([k, v]) => `${k}=${JSON.stringify(v)}`
13212
- );
13213
- await writeFile(
13214
- path36.join(ctx.cwd, ".env.production.local"),
13215
- lines.join("\n") + "\n",
13216
- "utf8"
13217
- );
13166
+ const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
13167
+ await writeFile(path35.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
13168
+ `, "utf8");
13218
13169
  }
13219
- const consumerDockerfile = path36.join(ctx.cwd, "Dockerfile.preview");
13170
+ const consumerDockerfile = path35.join(ctx.cwd, "Dockerfile.preview");
13220
13171
  const { stat } = await import("fs/promises");
13221
13172
  let hasConsumerDockerfile = false;
13222
13173
  try {
@@ -13228,19 +13179,16 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13228
13179
  if (!hasConsumerDockerfile) {
13229
13180
  const bundled = bundledDockerfilePath(buildMode);
13230
13181
  await copyFile(bundled, consumerDockerfile);
13231
- console.log(
13232
- `[preview-build] using bundled Dockerfile.preview.${buildMode} (from ${bundled})`
13233
- );
13182
+ console.log(`[preview-build] using bundled Dockerfile.preview.${buildMode} (from ${bundled})`);
13234
13183
  } else {
13235
13184
  console.log("[preview-build] using repo Dockerfile.preview");
13236
13185
  }
13237
13186
  let baseImage = null;
13238
13187
  if (ghcrOwner) {
13239
13188
  const baseRef = `${ghcrOwner.toLowerCase()}/${basePreviewAppName(repo)}`;
13240
- const tok = await fetch(
13241
- `https://ghcr.io/token?scope=repository:${baseRef}:pull&service=ghcr.io`,
13242
- { signal: AbortSignal.timeout(15e3) }
13243
- ).catch(() => null);
13189
+ const tok = await fetch(`https://ghcr.io/token?scope=repository:${baseRef}:pull&service=ghcr.io`, {
13190
+ signal: AbortSignal.timeout(15e3)
13191
+ }).catch(() => null);
13244
13192
  if (tok?.ok) {
13245
13193
  const { token: bearer } = await tok.json();
13246
13194
  const probe = await fetch(`https://ghcr.io/v2/${baseRef}/manifests/latest`, {
@@ -13261,39 +13209,22 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13261
13209
  await flyCreateApp(appName, orgSlug, flyToken);
13262
13210
  }
13263
13211
  await flyAllocateSharedIps(appName, flyToken);
13264
- await runCmd(
13265
- "docker",
13266
- ["login", "registry.fly.io", "-u", "x", "--password-stdin"],
13267
- { input: flyToken, cwd: ctx.cwd }
13268
- );
13212
+ await runCmd("docker", ["login", "registry.fly.io", "-u", "x", "--password-stdin"], {
13213
+ input: flyToken,
13214
+ cwd: ctx.cwd
13215
+ });
13269
13216
  const imageRef = `registry.fly.io/${appName}:${tag}`;
13270
13217
  const nsBuilder = nscTenantId ? await setupNamespaceBuilder({
13271
13218
  tenantId: nscTenantId,
13272
13219
  builderName: `kody-preview-${pr}`
13273
13220
  }) : null;
13274
13221
  if (nsBuilder) {
13275
- const a = [
13276
- "buildx",
13277
- "build",
13278
- "--builder",
13279
- nsBuilder,
13280
- "-f",
13281
- "Dockerfile.preview",
13282
- "-t",
13283
- imageRef,
13284
- "--push"
13285
- ];
13222
+ const a = ["buildx", "build", "--builder", nsBuilder, "-f", "Dockerfile.preview", "-t", imageRef, "--push"];
13286
13223
  if (baseImage) a.push("--build-arg", `BASE_IMAGE=${baseImage}`);
13287
13224
  a.push(".");
13288
13225
  await runCmd("docker", a, { cwd: ctx.cwd });
13289
13226
  } else {
13290
- const buildArgs = [
13291
- "build",
13292
- "-f",
13293
- "Dockerfile.preview",
13294
- "-t",
13295
- imageRef
13296
- ];
13227
+ const buildArgs = ["build", "-f", "Dockerfile.preview", "-t", imageRef];
13297
13228
  if (baseImage) buildArgs.push("--build-arg", `BASE_IMAGE=${baseImage}`);
13298
13229
  buildArgs.push(".");
13299
13230
  await runCmd("docker", buildArgs, {
@@ -13315,9 +13246,7 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13315
13246
  },
13316
13247
  flyToken
13317
13248
  );
13318
- console.log(
13319
- `[preview-build] done \u2014 machine ${machineId} at https://${appName}.fly.dev`
13320
- );
13249
+ console.log(`[preview-build] done \u2014 machine ${machineId} at https://${appName}.fly.dev`);
13321
13250
  await postOrUpdatePreviewComment({
13322
13251
  repo,
13323
13252
  pr,
@@ -13338,8 +13267,8 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13338
13267
 
13339
13268
  // src/scripts/runTickScript.ts
13340
13269
  import { spawnSync as spawnSync2 } from "child_process";
13341
- import * as fs40 from "fs";
13342
- import * as path37 from "path";
13270
+ import * as fs39 from "fs";
13271
+ import * as path36 from "path";
13343
13272
  var runTickScript = async (ctx, _profile, args) => {
13344
13273
  ctx.skipAgent = true;
13345
13274
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -13351,22 +13280,22 @@ var runTickScript = async (ctx, _profile, args) => {
13351
13280
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
13352
13281
  return;
13353
13282
  }
13354
- const jobPath = path37.join(ctx.cwd, jobsDir, `${slug}.md`);
13355
- if (!fs40.existsSync(jobPath)) {
13283
+ const jobPath = path36.join(ctx.cwd, jobsDir, `${slug}.md`);
13284
+ if (!fs39.existsSync(jobPath)) {
13356
13285
  ctx.output.exitCode = 99;
13357
13286
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
13358
13287
  return;
13359
13288
  }
13360
- const raw = fs40.readFileSync(jobPath, "utf-8");
13361
- const { frontmatter } = splitFrontmatter2(raw);
13289
+ const raw = fs39.readFileSync(jobPath, "utf-8");
13290
+ const { frontmatter } = splitFrontmatter(raw);
13362
13291
  const tickScript = frontmatter.tickScript;
13363
13292
  if (!tickScript) {
13364
13293
  ctx.output.exitCode = 99;
13365
13294
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
13366
13295
  return;
13367
13296
  }
13368
- const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
13369
- if (!fs40.existsSync(scriptPath)) {
13297
+ const scriptPath = path36.isAbsolute(tickScript) ? tickScript : path36.join(ctx.cwd, tickScript);
13298
+ if (!fs39.existsSync(scriptPath)) {
13370
13299
  ctx.output.exitCode = 99;
13371
13300
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
13372
13301
  return;
@@ -13543,8 +13472,10 @@ var serveFlow = async (ctx) => {
13543
13472
  process.stdout.write(`[kody serve] LiteLLM ready at ${handle?.url ?? LITELLM_DEFAULT_URL}
13544
13473
  `);
13545
13474
  } else {
13546
- process.stdout.write(`[kody serve] model ${model.provider}/${model.model} routes to Anthropic directly \u2014 no proxy needed
13547
- `);
13475
+ process.stdout.write(
13476
+ `[kody serve] model ${model.provider}/${model.model} routes to Anthropic directly \u2014 no proxy needed
13477
+ `
13478
+ );
13548
13479
  }
13549
13480
  const url = handle?.url ?? LITELLM_DEFAULT_URL;
13550
13481
  const editorEnv = usesProxy ? buildProxyEnv(url) : { ...process.env };
@@ -13589,8 +13520,10 @@ var serveFlow = async (ctx) => {
13589
13520
  code.on("error", (err) => {
13590
13521
  process.stderr.write(`[kody serve] failed to launch VS Code: ${err.message}
13591
13522
  `);
13592
- process.stderr.write(` Install the 'code' CLI: VS Code \u2192 Command Palette \u2192 "Shell Command: Install 'code' command in PATH"
13593
- `);
13523
+ process.stderr.write(
13524
+ ` Install the 'code' CLI: VS Code \u2192 Command Palette \u2192 "Shell Command: Install 'code' command in PATH"
13525
+ `
13526
+ );
13594
13527
  });
13595
13528
  code.unref();
13596
13529
  } catch (err) {
@@ -13817,10 +13750,8 @@ var verify = async (ctx) => {
13817
13750
  ctx.data.verifyReason = result.ok ? "" : summarizeFailure(result);
13818
13751
  ctx.data.verifyRecovered = result.recovered ?? [];
13819
13752
  if (result.recovered && result.recovered.length > 0) {
13820
- process.stderr.write(
13821
- `[kody verify] caught flake on: ${result.recovered.join(", ")} (passed on retry)
13822
- `
13823
- );
13753
+ process.stderr.write(`[kody verify] caught flake on: ${result.recovered.join(", ")} (passed on retry)
13754
+ `);
13824
13755
  }
13825
13756
  } catch (err) {
13826
13757
  ctx.data.verifyOk = false;
@@ -13828,7 +13759,7 @@ var verify = async (ctx) => {
13828
13759
  }
13829
13760
  if (ctx.data.verifyOk === false) {
13830
13761
  const action = ctx.data.action;
13831
- if (action && action.type.endsWith("_COMPLETED")) {
13762
+ if (action?.type.endsWith("_COMPLETED")) {
13832
13763
  const reason = ctx.data.verifyReason || "verify failed";
13833
13764
  ctx.data.action = {
13834
13765
  type: action.type.replace(/_COMPLETED$/, "_FAILED"),
@@ -13945,7 +13876,7 @@ function runCommand2(command, cwd) {
13945
13876
  }
13946
13877
  function downgrade2(ctx, reason) {
13947
13878
  const action = ctx.data.action;
13948
- if (action && action.type.endsWith("_COMPLETED")) {
13879
+ if (action?.type.endsWith("_COMPLETED")) {
13949
13880
  ctx.data.action = {
13950
13881
  type: action.type.replace(/_COMPLETED$/, "_FAILED"),
13951
13882
  payload: { reason, downgradedFrom: action.type },
@@ -13965,10 +13896,8 @@ async function runVerify(ctx) {
13965
13896
  ctx.data.verifyReason = result.ok ? "" : summarizeFailure(result);
13966
13897
  ctx.data.verifyRecovered = result.recovered ?? [];
13967
13898
  if (result.recovered && result.recovered.length > 0) {
13968
- process.stderr.write(
13969
- `[kody verify] caught flake on: ${result.recovered.join(", ")} (passed on retry)
13970
- `
13971
- );
13899
+ process.stderr.write(`[kody verify] caught flake on: ${result.recovered.join(", ")} (passed on retry)
13900
+ `);
13972
13901
  }
13973
13902
  } catch (err) {
13974
13903
  ctx.data.verifyOk = false;
@@ -13978,7 +13907,7 @@ async function runVerify(ctx) {
13978
13907
  function downgradeActionOnFailure(ctx) {
13979
13908
  if (ctx.data.verifyOk !== false) return;
13980
13909
  const action = ctx.data.action;
13981
- if (!action || !action.type.endsWith("_COMPLETED")) return;
13910
+ if (!action?.type.endsWith("_COMPLETED")) return;
13982
13911
  const reason = ctx.data.verifyReason || "verify failed";
13983
13912
  ctx.data.action = {
13984
13913
  type: action.type.replace(/_COMPLETED$/, "_FAILED"),
@@ -13989,9 +13918,9 @@ function downgradeActionOnFailure(ctx) {
13989
13918
  function upgradeActionOnPass(ctx) {
13990
13919
  if (ctx.data.verifyOk !== true) return;
13991
13920
  const action = ctx.data.action;
13992
- if (!action || !action.type.endsWith("_FAILED")) return;
13921
+ if (!action?.type.endsWith("_FAILED")) return;
13993
13922
  const downgradedFrom = action.payload?.downgradedFrom;
13994
- if (!downgradedFrom || !downgradedFrom.endsWith("_COMPLETED")) return;
13923
+ if (!downgradedFrom?.endsWith("_COMPLETED")) return;
13995
13924
  ctx.data.action = {
13996
13925
  type: downgradedFrom,
13997
13926
  payload: {},
@@ -14171,74 +14100,6 @@ function sleep3(ms) {
14171
14100
  return new Promise((res) => setTimeout(res, ms));
14172
14101
  }
14173
14102
 
14174
- // src/scripts/appendCompanyActivity.ts
14175
- init_issue();
14176
- function resolveTrigger(force) {
14177
- const event = process.env.GITHUB_EVENT_NAME ?? "";
14178
- if (event === "schedule") return "schedule";
14179
- if (force || event === "issue_comment" || event === "workflow_dispatch")
14180
- return "manual";
14181
- return "event";
14182
- }
14183
- function appendLine(owner, repo, cwd, record) {
14184
- const filePath = `.kody/activity/${record.ts.slice(0, 10)}.jsonl`;
14185
- let existing = "";
14186
- let sha;
14187
- try {
14188
- const out = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
14189
- const json = JSON.parse(out);
14190
- if (json.sha) sha = json.sha;
14191
- if (json.content) existing = Buffer.from(json.content, "base64").toString("utf-8");
14192
- } catch {
14193
- }
14194
- const body = `${existing}${JSON.stringify(record)}
14195
- `;
14196
- const payload = {
14197
- message: `chore(activity): ${record.action}`,
14198
- content: Buffer.from(body, "utf-8").toString("base64"),
14199
- // Keep this high-frequency feed off the default branch.
14200
- branch: STATE_BRANCH
14201
- };
14202
- if (sha) payload.sha = sha;
14203
- ensureStateBranch(owner, repo, cwd);
14204
- gh(
14205
- ["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"],
14206
- { cwd, input: JSON.stringify(payload) }
14207
- );
14208
- }
14209
- var appendCompanyActivity = async (ctx, _profile, agentResult) => {
14210
- try {
14211
- const owner = ctx.config?.github?.owner;
14212
- const repo = ctx.config?.github?.repo;
14213
- const duty = String(ctx.data.jobSlug ?? ctx.args?.job ?? "").trim();
14214
- if (!owner || !repo || !duty) return;
14215
- const dutyTitle = ctx.data.jobTitle ?? null;
14216
- const staff = ctx.data.workerSlug || null;
14217
- const staffTitle = ctx.data.workerTitle || null;
14218
- const force = ctx.args?.force === true;
14219
- const record = {
14220
- ts: (/* @__PURE__ */ new Date()).toISOString(),
14221
- action: `Ran duty: ${dutyTitle ?? duty}`,
14222
- duty,
14223
- dutyTitle,
14224
- staff,
14225
- staffTitle,
14226
- trigger: resolveTrigger(force),
14227
- outcome: agentResult?.outcome ?? "unknown",
14228
- outcomeKind: agentResult?.outcomeKind ?? null,
14229
- reason: agentResult?.error ?? null,
14230
- durationMs: agentResult?.durationMs ?? null,
14231
- runUrl: getRunUrl() || null
14232
- };
14233
- appendLine(owner, repo, ctx.cwd, record);
14234
- } catch (err) {
14235
- process.stderr.write(
14236
- `[activity] company-activity append failed: ${err instanceof Error ? err.message : String(err)}
14237
- `
14238
- );
14239
- }
14240
- };
14241
-
14242
14103
  // src/scripts/warmupMcp.ts
14243
14104
  import { spawn as spawn9 } from "child_process";
14244
14105
  var PER_SERVER_TIMEOUT_MS = 6e4;
@@ -14276,12 +14137,14 @@ async function warmupOne(command, args, env) {
14276
14137
  let nextId = 1;
14277
14138
  const send = (method, params) => {
14278
14139
  const id = nextId++;
14279
- const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
14140
+ const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
14141
+ `;
14280
14142
  child.stdin.write(payload);
14281
14143
  return id;
14282
14144
  };
14283
14145
  const notify = (method, params) => {
14284
- const payload = JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n";
14146
+ const payload = `${JSON.stringify({ jsonrpc: "2.0", method, params })}
14147
+ `;
14285
14148
  child.stdin.write(payload);
14286
14149
  };
14287
14150
  const awaitResponse = async (id) => {
@@ -14346,11 +14209,12 @@ function lineStream(stream) {
14346
14209
  };
14347
14210
  stream.on("data", (chunk) => {
14348
14211
  buf += typeof chunk === "string" ? chunk : chunk.toString("utf8");
14349
- let idx;
14350
- while ((idx = buf.indexOf("\n")) >= 0) {
14212
+ let idx = buf.indexOf("\n");
14213
+ while (idx >= 0) {
14351
14214
  const line = buf.slice(0, idx).replace(/\r$/, "");
14352
14215
  buf = buf.slice(idx + 1);
14353
14216
  if (line.length > 0) queue.push(line);
14217
+ idx = buf.indexOf("\n");
14354
14218
  }
14355
14219
  tryDeliver();
14356
14220
  });
@@ -14373,12 +14237,15 @@ function lineStream(stream) {
14373
14237
  return;
14374
14238
  }
14375
14239
  waiter = resolve6;
14376
- const t = setTimeout(() => {
14377
- if (waiter === resolve6) {
14378
- waiter = null;
14379
- resolve6(null);
14380
- }
14381
- }, Math.max(0, timeoutMs));
14240
+ const t = setTimeout(
14241
+ () => {
14242
+ if (waiter === resolve6) {
14243
+ waiter = null;
14244
+ resolve6(null);
14245
+ }
14246
+ },
14247
+ Math.max(0, timeoutMs)
14248
+ );
14382
14249
  t.unref?.();
14383
14250
  })
14384
14251
  };
@@ -14474,7 +14341,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
14474
14341
  };
14475
14342
 
14476
14343
  // src/scripts/writeRunSummary.ts
14477
- import * as fs41 from "fs";
14344
+ import * as fs40 from "fs";
14478
14345
  var writeRunSummary = async (ctx, profile) => {
14479
14346
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
14480
14347
  if (!summaryPath) return;
@@ -14496,7 +14363,7 @@ var writeRunSummary = async (ctx, profile) => {
14496
14363
  if (reason) lines.push(`- **Reason:** ${reason}`);
14497
14364
  lines.push("");
14498
14365
  try {
14499
- fs41.appendFileSync(summaryPath, `${lines.join("\n")}
14366
+ fs40.appendFileSync(summaryPath, `${lines.join("\n")}
14500
14367
  `);
14501
14368
  } catch {
14502
14369
  }
@@ -14603,6 +14470,48 @@ var allScriptNames = /* @__PURE__ */ new Set([
14603
14470
  ...Object.keys(postflightScripts)
14604
14471
  ]);
14605
14472
 
14473
+ // src/subagents.ts
14474
+ import * as fs41 from "fs";
14475
+ import * as path37 from "path";
14476
+ function splitFrontmatter2(raw) {
14477
+ const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
14478
+ if (!match) return { fm: {}, body: raw.trim() };
14479
+ const fm = {};
14480
+ for (const line of match[1].split("\n")) {
14481
+ const idx = line.indexOf(":");
14482
+ if (idx === -1) continue;
14483
+ fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
14484
+ }
14485
+ return { fm, body: (match[2] ?? "").trim() };
14486
+ }
14487
+ function resolveAgentFile(profileDir, name) {
14488
+ const local = path37.join(profileDir, "agents", `${name}.md`);
14489
+ if (fs41.existsSync(local)) return local;
14490
+ const central = path37.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
14491
+ if (fs41.existsSync(central)) return central;
14492
+ throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
14493
+ }
14494
+ function loadSubagents(profile) {
14495
+ const names = profile.claudeCode.subagents;
14496
+ if (!names || names.length === 0) return void 0;
14497
+ const agents = {};
14498
+ for (const name of names) {
14499
+ const { fm, body } = splitFrontmatter2(fs41.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
14500
+ if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
14501
+ const def = {
14502
+ description: fm.description ?? `Subagent ${name}`,
14503
+ prompt: body
14504
+ };
14505
+ if (fm.tools) {
14506
+ const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
14507
+ if (tools.length > 0) def.tools = tools;
14508
+ }
14509
+ if (fm.model) def.model = fm.model;
14510
+ agents[fm.name || name] = def;
14511
+ }
14512
+ return agents;
14513
+ }
14514
+
14606
14515
  // src/tools.ts
14607
14516
  import { execFileSync as execFileSync27 } from "child_process";
14608
14517
  function verifyCliTools(tools, cwd) {
@@ -15036,7 +14945,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
15036
14945
  for (const entry of profile.scripts.preflight) {
15037
14946
  if (entry.script !== "setLifecycleLabel") continue;
15038
14947
  const label = typeof entry.with?.label === "string" ? entry.with.label : void 0;
15039
- if (!label || !label.startsWith(KODY_NAMESPACE)) continue;
14948
+ if (!label?.startsWith(KODY_NAMESPACE)) continue;
15040
14949
  try {
15041
14950
  removeLabel(target, label, ctx.cwd);
15042
14951
  } catch {
@@ -15600,10 +15509,8 @@ ${CI_HELP}`);
15600
15509
  `);
15601
15510
  const buildOnly = dispatch2.executable === "preview-build";
15602
15511
  if (args.skipInstall || buildOnly) {
15603
- process.stdout.write(
15604
- `\u2192 kody: skipping dep install (${buildOnly ? "build-only executable" : "--skip-install"})
15605
- `
15606
- );
15512
+ process.stdout.write(`\u2192 kody: skipping dep install (${buildOnly ? "build-only executable" : "--skip-install"})
15513
+ `);
15607
15514
  } else {
15608
15515
  const code = installDeps(pm, cwd);
15609
15516
  if (code !== 0) {
@@ -16093,15 +16000,19 @@ Kody run statistics \u2014 ${totalRuns} runs
16093
16000
  const totalIn = runs.reduce((s, r) => s + r.totalInputTokens, 0);
16094
16001
  const totalOut = runs.reduce((s, r) => s + r.totalOutputTokens, 0);
16095
16002
  const totalCacheR = runs.reduce((s, r) => s + r.totalCacheReadTokens, 0);
16096
- process.stdout.write(` total tokens : ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out / ${totalCacheR.toLocaleString()} cache-read
16097
- `);
16003
+ process.stdout.write(
16004
+ ` total tokens : ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out / ${totalCacheR.toLocaleString()} cache-read
16005
+ `
16006
+ );
16098
16007
  process.stdout.write(`
16099
16008
  Per-executable (stage_end events)
16100
16009
  `);
16101
16010
  const headers = ["executable", "runs", "ok", "failed", "p50", "p95", "mean", "tok-in", "tok-out", "cache-r"];
16102
16011
  const widths = [22, 6, 6, 7, 9, 9, 9, 10, 10, 10];
16103
- process.stdout.write(headers.map((h, i) => h.padEnd(widths[i])).join("") + "\n");
16104
- process.stdout.write(widths.map((w) => "-".repeat(w - 1) + " ").join("") + "\n");
16012
+ process.stdout.write(`${headers.map((h, i) => h.padEnd(widths[i])).join("")}
16013
+ `);
16014
+ process.stdout.write(`${widths.map((w) => `${"-".repeat(w - 1)} `).join("")}
16015
+ `);
16105
16016
  for (const r of rollups) {
16106
16017
  const row = [
16107
16018
  r.executable,
@@ -16115,7 +16026,8 @@ Per-executable (stage_end events)
16115
16026
  r.totalOutputTokens.toLocaleString(),
16116
16027
  r.totalCacheReadTokens.toLocaleString()
16117
16028
  ];
16118
- process.stdout.write(row.map((c, i) => c.padEnd(widths[i])).join("") + "\n");
16029
+ process.stdout.write(`${row.map((c, i) => c.padEnd(widths[i])).join("")}
16030
+ `);
16119
16031
  }
16120
16032
  process.stdout.write("\n");
16121
16033
  }