@kody-ade/kody-engine 0.4.139 → 0.4.141

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
@@ -93,7 +93,7 @@ var init_events = __esm({
93
93
  // src/verify.ts
94
94
  import { spawn } from "child_process";
95
95
  function runCommand(command, cwd) {
96
- return new Promise((resolve4) => {
96
+ return new Promise((resolve5) => {
97
97
  const start = Date.now();
98
98
  const child = spawn(command, {
99
99
  cwd,
@@ -122,11 +122,11 @@ function runCommand(command, cwd) {
122
122
  child.on("exit", (code) => {
123
123
  clearTimeout(timer);
124
124
  const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
125
- resolve4({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
125
+ resolve5({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
126
126
  });
127
127
  child.on("error", (err) => {
128
128
  clearTimeout(timer);
129
- resolve4({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
129
+ resolve5({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
130
130
  });
131
131
  });
132
132
  }
@@ -672,28 +672,28 @@ var loadMemoryContext_exports = {};
672
672
  __export(loadMemoryContext_exports, {
673
673
  loadMemoryContext: () => loadMemoryContext
674
674
  });
675
- import * as fs32 from "fs";
676
- import * as path31 from "path";
675
+ import * as fs31 from "fs";
676
+ import * as path29 from "path";
677
677
  function collectPages(memoryAbs) {
678
678
  const out = [];
679
679
  walkMd(memoryAbs, (file) => {
680
680
  let stat;
681
681
  try {
682
- stat = fs32.statSync(file);
682
+ stat = fs31.statSync(file);
683
683
  } catch {
684
684
  return;
685
685
  }
686
686
  let raw;
687
687
  try {
688
- raw = fs32.readFileSync(file, "utf-8");
688
+ raw = fs31.readFileSync(file, "utf-8");
689
689
  } catch {
690
690
  return;
691
691
  }
692
692
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
693
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path31.basename(file, ".md");
693
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path29.basename(file, ".md");
694
694
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
695
695
  out.push({
696
- relPath: path31.relative(memoryAbs, file),
696
+ relPath: path29.relative(memoryAbs, file),
697
697
  title,
698
698
  updated,
699
699
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
@@ -761,16 +761,16 @@ function walkMd(root, visit) {
761
761
  const dir = stack.pop();
762
762
  let names;
763
763
  try {
764
- names = fs32.readdirSync(dir);
764
+ names = fs31.readdirSync(dir);
765
765
  } catch {
766
766
  continue;
767
767
  }
768
768
  for (const name of names) {
769
769
  if (name.startsWith(".")) continue;
770
- const full = path31.join(dir, name);
770
+ const full = path29.join(dir, name);
771
771
  let stat;
772
772
  try {
773
- stat = fs32.statSync(full);
773
+ stat = fs31.statSync(full);
774
774
  } catch {
775
775
  continue;
776
776
  }
@@ -793,8 +793,8 @@ var init_loadMemoryContext = __esm({
793
793
  TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
794
794
  loadMemoryContext = async (ctx) => {
795
795
  if (typeof ctx.data.memoryContext === "string") return;
796
- const memoryAbs = path31.join(ctx.cwd, MEMORY_DIR_RELATIVE);
797
- if (!fs32.existsSync(memoryAbs)) {
796
+ const memoryAbs = path29.join(ctx.cwd, MEMORY_DIR_RELATIVE);
797
+ if (!fs31.existsSync(memoryAbs)) {
798
798
  ctx.data.memoryContext = "";
799
799
  return;
800
800
  }
@@ -926,7 +926,7 @@ var init_loadPriorArt = __esm({
926
926
  // package.json
927
927
  var package_default = {
928
928
  name: "@kody-ade/kody-engine",
929
- version: "0.4.139",
929
+ version: "0.4.141",
930
930
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
931
931
  license: "MIT",
932
932
  type: "module",
@@ -982,9 +982,9 @@ var package_default = {
982
982
  };
983
983
 
984
984
  // src/chat-cli.ts
985
- import { execFileSync as execFileSync32 } from "child_process";
986
- import * as fs42 from "fs";
987
- import * as path39 from "path";
985
+ import { execFileSync as execFileSync30 } from "child_process";
986
+ import * as fs41 from "fs";
987
+ import * as path37 from "path";
988
988
 
989
989
  // src/chat/events.ts
990
990
  import * as fs from "fs";
@@ -1641,10 +1641,10 @@ async function runAgent(opts) {
1641
1641
  let timer;
1642
1642
  let next;
1643
1643
  if (turnTimeoutMs > 0) {
1644
- const timeoutPromise = new Promise((resolve4) => {
1644
+ const timeoutPromise = new Promise((resolve5) => {
1645
1645
  timer = setTimeout(() => {
1646
1646
  timedOut = true;
1647
- resolve4({ done: true, value: void 0 });
1647
+ resolve5({ done: true, value: void 0 });
1648
1648
  }, turnTimeoutMs);
1649
1649
  });
1650
1650
  next = await Promise.race([nextPromise, timeoutPromise]);
@@ -2318,7 +2318,7 @@ async function waitForNextUserMessage(opts) {
2318
2318
  }
2319
2319
  }
2320
2320
  function sleep(ms) {
2321
- return new Promise((resolve4) => setTimeout(resolve4, ms));
2321
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
2322
2322
  }
2323
2323
  function currentBranch(cwd) {
2324
2324
  try {
@@ -2533,9 +2533,9 @@ async function emit2(sink, type, sessionId, suffix, payload) {
2533
2533
  }
2534
2534
 
2535
2535
  // src/kody-cli.ts
2536
- import { execFileSync as execFileSync31 } from "child_process";
2537
- import * as fs41 from "fs";
2538
- import * as path38 from "path";
2536
+ import { execFileSync as execFileSync29 } from "child_process";
2537
+ import * as fs40 from "fs";
2538
+ import * as path36 from "path";
2539
2539
 
2540
2540
  // src/dispatch.ts
2541
2541
  import * as fs11 from "fs";
@@ -2867,9 +2867,9 @@ function coerceBare(spec, value) {
2867
2867
  init_issue();
2868
2868
 
2869
2869
  // src/executor.ts
2870
- import { execFileSync as execFileSync30, spawn as spawn8 } from "child_process";
2871
- import * as fs40 from "fs";
2872
- import * as path37 from "path";
2870
+ import { execFileSync as execFileSync28, spawn as spawn9 } from "child_process";
2871
+ import * as fs39 from "fs";
2872
+ import * as path35 from "path";
2873
2873
 
2874
2874
  // src/discipline.ts
2875
2875
  var DISCIPLINE = `# Working discipline (applies to this entire task)
@@ -4291,6 +4291,7 @@ var advanceFlow = async (ctx, profile) => {
4291
4291
 
4292
4292
  // src/scripts/brainServe.ts
4293
4293
  import { createServer } from "http";
4294
+ import { spawn as spawn3, spawnSync } from "child_process";
4294
4295
  import * as fs18 from "fs";
4295
4296
  import * as path17 from "path";
4296
4297
 
@@ -4473,17 +4474,17 @@ function authOk(req, expected) {
4473
4474
  return false;
4474
4475
  }
4475
4476
  function readJsonBody(req) {
4476
- return new Promise((resolve4, reject) => {
4477
+ return new Promise((resolve5, reject) => {
4477
4478
  const chunks = [];
4478
4479
  req.on("data", (c) => chunks.push(c));
4479
4480
  req.on("end", () => {
4480
4481
  const raw = Buffer.concat(chunks).toString("utf-8");
4481
4482
  if (!raw.trim()) {
4482
- resolve4({});
4483
+ resolve5({});
4483
4484
  return;
4484
4485
  }
4485
4486
  try {
4486
- resolve4(JSON.parse(raw));
4487
+ resolve5(JSON.parse(raw));
4487
4488
  } catch (err) {
4488
4489
  reject(err instanceof Error ? err : new Error(String(err)));
4489
4490
  }
@@ -4491,6 +4492,13 @@ function readJsonBody(req) {
4491
4492
  req.on("error", reject);
4492
4493
  });
4493
4494
  }
4495
+ function strField(body, key) {
4496
+ if (typeof body === "object" && body !== null && key in body) {
4497
+ const v = body[key];
4498
+ if (typeof v === "string" && v.trim()) return v.trim();
4499
+ }
4500
+ return void 0;
4501
+ }
4494
4502
  function sendJson(res, status, body) {
4495
4503
  res.writeHead(status, { "content-type": "application/json" });
4496
4504
  res.end(JSON.stringify(body));
@@ -4588,6 +4596,51 @@ function streamToRes(res, dir, chatId, since) {
4588
4596
  );
4589
4597
  res.on("close", unsubscribe);
4590
4598
  }
4599
+ var REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
4600
+ var repoClones = /* @__PURE__ */ new Map();
4601
+ async function ensureRepoCwd(opts) {
4602
+ const repo = opts.repo?.trim();
4603
+ if (!repo || !REPO_RE.test(repo)) return opts.baseCwd;
4604
+ const root = path17.resolve(opts.reposRoot);
4605
+ const dir = path17.resolve(root, repo);
4606
+ if (dir !== root && !dir.startsWith(root + path17.sep)) return opts.baseCwd;
4607
+ if (fs18.existsSync(path17.join(dir, ".git"))) return dir;
4608
+ const inflight = repoClones.get(dir);
4609
+ if (inflight) {
4610
+ await inflight;
4611
+ return dir;
4612
+ }
4613
+ const p = opts.cloneRepo(repo, opts.repoToken, dir).finally(() => {
4614
+ if (repoClones.get(dir) === p) repoClones.delete(dir);
4615
+ });
4616
+ repoClones.set(dir, p);
4617
+ await p;
4618
+ return dir;
4619
+ }
4620
+ var defaultCloneRepo = (repo, token, dir) => {
4621
+ fs18.mkdirSync(path17.dirname(dir), { recursive: true });
4622
+ const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
4623
+ return new Promise((resolve5, reject) => {
4624
+ const child = spawn3("git", ["clone", "--depth=1", authUrl, dir], {
4625
+ stdio: "inherit"
4626
+ });
4627
+ child.on("exit", (code) => {
4628
+ if (code !== 0) {
4629
+ reject(new Error(`git clone ${repo} failed (exit ${code})`));
4630
+ return;
4631
+ }
4632
+ try {
4633
+ const name = process.env.GIT_AUTHOR_NAME ?? "Kody Bot";
4634
+ const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
4635
+ spawnSync("git", ["-C", dir, "config", "user.name", name]);
4636
+ spawnSync("git", ["-C", dir, "config", "user.email", email]);
4637
+ } catch {
4638
+ }
4639
+ resolve5();
4640
+ });
4641
+ child.on("error", reject);
4642
+ });
4643
+ };
4591
4644
  async function handleChatTurn(req, res, chatId, opts) {
4592
4645
  let body;
4593
4646
  try {
@@ -4601,6 +4654,8 @@ async function handleChatTurn(req, res, chatId, opts) {
4601
4654
  sendJson(res, 400, { error: "message required" });
4602
4655
  return;
4603
4656
  }
4657
+ const repo = strField(body, "repo");
4658
+ const repoToken = strField(body, "repoToken");
4604
4659
  const sessionFile = sessionFilePath(opts.cwd, chatId);
4605
4660
  fs18.mkdirSync(path17.dirname(sessionFile), { recursive: true });
4606
4661
  appendTurn(sessionFile, {
@@ -4611,32 +4666,42 @@ async function handleChatTurn(req, res, chatId, opts) {
4611
4666
  const sinceFloor = getLastSeq(opts.cwd, chatId);
4612
4667
  const emitToLog = beginTurn(opts.cwd, chatId);
4613
4668
  const sink = new BrokerSink(emitToLog, chatId);
4614
- void enqueue(
4615
- chatId,
4616
- () => opts.runTurn({
4617
- sessionId: chatId,
4618
- sessionFile,
4619
- cwd: opts.cwd,
4620
- model: opts.model,
4621
- litellmUrl: opts.litellmUrl,
4622
- sink
4623
- }).catch((err) => {
4669
+ void enqueue(chatId, async () => {
4670
+ try {
4671
+ const agentCwd = await ensureRepoCwd({
4672
+ baseCwd: opts.cwd,
4673
+ reposRoot: opts.reposRoot,
4674
+ repo,
4675
+ repoToken,
4676
+ cloneRepo: opts.cloneRepo
4677
+ });
4678
+ await opts.runTurn({
4679
+ sessionId: chatId,
4680
+ sessionFile,
4681
+ cwd: agentCwd,
4682
+ model: opts.model,
4683
+ litellmUrl: opts.litellmUrl,
4684
+ sink
4685
+ });
4686
+ } catch (err) {
4624
4687
  const errMsg3 = err instanceof Error ? err.message : String(err);
4625
4688
  process.stderr.write(`[brain-serve] chat turn failed: ${errMsg3}
4626
4689
  `);
4627
4690
  endTurnIfUnterminated(opts.cwd, chatId, errMsg3);
4628
- }).finally(() => {
4691
+ } finally {
4629
4692
  endTurnIfUnterminated(
4630
4693
  opts.cwd,
4631
4694
  chatId,
4632
4695
  "Brain turn ended without a reply (the machine may have restarted mid-turn) \u2014 please resend your message"
4633
4696
  );
4634
- })
4635
- );
4697
+ }
4698
+ });
4636
4699
  streamToRes(res, opts.cwd, chatId, sinceFloor);
4637
4700
  }
4638
4701
  function buildServer(opts) {
4639
4702
  const runTurn = opts.runTurn ?? runChatTurn;
4703
+ const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
4704
+ const reposRoot = opts.reposRoot ?? path17.join(path17.dirname(path17.resolve(opts.cwd)), "repos");
4640
4705
  return createServer(async (req, res) => {
4641
4706
  if (!req.method || !req.url) {
4642
4707
  sendJson(res, 400, { error: "bad request" });
@@ -4660,6 +4725,8 @@ function buildServer(opts) {
4660
4725
  }
4661
4726
  await handleChatTurn(req, res, chatId, {
4662
4727
  cwd: opts.cwd,
4728
+ reposRoot,
4729
+ cloneRepo,
4663
4730
  model: opts.model,
4664
4731
  litellmUrl: opts.litellmUrl,
4665
4732
  runTurn
@@ -4710,16 +4777,18 @@ var brainServe = async (ctx) => {
4710
4777
  const server = buildServer({
4711
4778
  apiKey,
4712
4779
  cwd: ctx.cwd,
4780
+ // Per-repo clones live here; defaults to a `repos` sibling of cwd.
4781
+ reposRoot: process.env.BRAIN_REPOS_ROOT?.trim() || void 0,
4713
4782
  model,
4714
4783
  litellmUrl
4715
4784
  });
4716
- await new Promise((resolve4) => {
4785
+ await new Promise((resolve5) => {
4717
4786
  server.listen(port, "0.0.0.0", () => {
4718
4787
  process.stdout.write(
4719
4788
  `[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
4720
4789
  `
4721
4790
  );
4722
- resolve4();
4791
+ resolve5();
4723
4792
  });
4724
4793
  });
4725
4794
  const shutdown = (signal) => {
@@ -4978,43 +5047,181 @@ var commitAndPush2 = async (ctx, profile) => {
4978
5047
  }
4979
5048
  };
4980
5049
 
4981
- // src/scripts/commitGoalState.ts
4982
- import { execFileSync as execFileSync10 } from "child_process";
4983
- import * as path20 from "path";
4984
- var commitGoalState = async (ctx) => {
4985
- const goal = ctx.data.goal;
4986
- if (!goal) return;
4987
- const stateRel = path20.posix.join(".kody", "goals", goal.id, "state.json");
5050
+ // src/goal/stateStore.ts
5051
+ init_issue();
5052
+
5053
+ // src/stateBranch.ts
5054
+ init_issue();
5055
+ var STATE_BRANCH = "kody-state";
5056
+ function is404(err) {
5057
+ const msg = err instanceof Error ? err.message : String(err);
5058
+ return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
5059
+ }
5060
+ function ensureStateBranch(owner, repo, cwd) {
4988
5061
  try {
4989
- execFileSync10("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
5062
+ gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
5063
+ return;
4990
5064
  } catch (err) {
4991
- process.stderr.write(
4992
- `[goal-tick] commitGoalState: git add failed: ${err instanceof Error ? err.message : String(err)}
4993
- `
5065
+ if (!is404(err)) throw err;
5066
+ }
5067
+ const repoInfo = JSON.parse(gh(["api", `/repos/${owner}/${repo}`], { cwd }));
5068
+ const defaultBranch2 = repoInfo.default_branch;
5069
+ if (!defaultBranch2) {
5070
+ throw new Error(`ensureStateBranch: could not resolve default branch for ${owner}/${repo}`);
5071
+ }
5072
+ const headRef = JSON.parse(
5073
+ gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${defaultBranch2}`], { cwd })
5074
+ );
5075
+ const sha = headRef.object?.sha;
5076
+ if (!sha) {
5077
+ throw new Error(`ensureStateBranch: could not resolve head sha for ${owner}/${repo}@${defaultBranch2}`);
5078
+ }
5079
+ try {
5080
+ gh(["api", "--method", "POST", `/repos/${owner}/${repo}/git/refs`, "--input", "-"], {
5081
+ cwd,
5082
+ input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
5083
+ });
5084
+ } catch (err) {
5085
+ const msg = err instanceof Error ? err.message : String(err);
5086
+ if (/already exists/i.test(msg) || /HTTP 422/i.test(msg)) return;
5087
+ throw err;
5088
+ }
5089
+ }
5090
+
5091
+ // src/goal/state.ts
5092
+ import * as fs21 from "fs";
5093
+ import * as path20 from "path";
5094
+ var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
5095
+ var GoalStateError = class extends Error {
5096
+ constructor(path38, message) {
5097
+ super(`Invalid goal state at ${path38}:
5098
+ ${message}`);
5099
+ this.path = path38;
5100
+ this.name = "GoalStateError";
5101
+ }
5102
+ path;
5103
+ };
5104
+ function parseGoalState(filePath, raw) {
5105
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
5106
+ throw new GoalStateError(filePath, "must be a JSON object");
5107
+ }
5108
+ const r = raw;
5109
+ const stateValue = r.state;
5110
+ if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
5111
+ throw new GoalStateError(
5112
+ filePath,
5113
+ `"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
4994
5114
  );
4995
- return;
4996
5115
  }
5116
+ const parsed = {
5117
+ state: stateValue,
5118
+ extra: {}
5119
+ };
5120
+ if (typeof r.mergeApproved === "boolean") {
5121
+ parsed.mergeApproved = r.mergeApproved;
5122
+ }
5123
+ if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
5124
+ parsed.lastDispatchedIssue = r.lastDispatchedIssue;
5125
+ }
5126
+ for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
5127
+ const v = r[ts];
5128
+ if (typeof v === "string" && v.length > 0) parsed[ts] = v;
5129
+ }
5130
+ const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
5131
+ for (const [k, v] of Object.entries(r)) {
5132
+ if (!known.has(k)) parsed.extra[k] = v;
5133
+ }
5134
+ return parsed;
5135
+ }
5136
+ function serializeGoalState(s) {
5137
+ const obj = { ...s.extra, state: s.state };
5138
+ if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
5139
+ if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
5140
+ if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
5141
+ if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
5142
+ if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
5143
+ return `${JSON.stringify(obj, null, 2)}
5144
+ `;
5145
+ }
5146
+ function nowIso() {
5147
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
5148
+ }
5149
+
5150
+ // src/goal/stateStore.ts
5151
+ function statePath(goalId) {
5152
+ return `.kody/goals/${goalId}/state.json`;
5153
+ }
5154
+ function is4042(err) {
5155
+ const msg = err instanceof Error ? err.message : String(err);
5156
+ return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
5157
+ }
5158
+ function fetchGoalState(owner, repo, goalId, cwd) {
5159
+ const filePath = statePath(goalId);
5160
+ let raw;
4997
5161
  try {
4998
- execFileSync10("git", ["diff", "--cached", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
5162
+ raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
5163
+ } catch (err) {
5164
+ if (is4042(err)) return null;
5165
+ throw err;
5166
+ }
5167
+ const o = JSON.parse(raw);
5168
+ if (!o.content) return null;
5169
+ const decoded = Buffer.from(o.content, "base64").toString("utf-8");
5170
+ return parseGoalState(filePath, JSON.parse(decoded));
5171
+ }
5172
+ function putGoalState(owner, repo, goalId, state, message, cwd) {
5173
+ ensureStateBranch(owner, repo, cwd);
5174
+ const filePath = statePath(goalId);
5175
+ const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
5176
+ for (let attempt = 1; attempt <= 3; attempt++) {
5177
+ let sha;
5178
+ try {
5179
+ const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
5180
+ const o = JSON.parse(cur);
5181
+ if (o.sha) sha = o.sha;
5182
+ } catch (err) {
5183
+ if (!is4042(err)) throw err;
5184
+ }
5185
+ const payload = { message, content, branch: STATE_BRANCH };
5186
+ if (sha) payload.sha = sha;
5187
+ try {
5188
+ gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
5189
+ cwd,
5190
+ input: JSON.stringify(payload)
5191
+ });
5192
+ return;
5193
+ } catch (err) {
5194
+ const msg = err instanceof Error ? err.message : String(err);
5195
+ const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
5196
+ if (!conflict || attempt === 3) throw err;
5197
+ }
5198
+ }
5199
+ }
5200
+
5201
+ // src/scripts/commitGoalState.ts
5202
+ var commitGoalState = async (ctx) => {
5203
+ const goal = ctx.data.goal;
5204
+ if (!goal) return;
5205
+ if (ctx.data.goalPersistChanged !== true) return;
5206
+ const updated = ctx.data.goalPersistState;
5207
+ if (!updated) return;
5208
+ const owner = ctx.config.github?.owner;
5209
+ const repo = ctx.config.github?.repo;
5210
+ if (!owner || !repo) {
5211
+ process.stderr.write(`[goal-tick] commitGoalState: missing github owner/repo; cannot persist ${goal.id}
5212
+ `);
4999
5213
  return;
5000
- } catch {
5001
5214
  }
5002
- const msg = describeCommitMessage(goal);
5003
5215
  try {
5004
- execFileSync10("git", ["commit", "-m", msg, "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
5216
+ putGoalState(owner, repo, goal.id, updated, describeCommitMessage(goal), ctx.cwd);
5005
5217
  } catch (err) {
5006
5218
  process.stderr.write(
5007
- `[goal-tick] commitGoalState: git commit failed: ${err instanceof Error ? err.message : String(err)}
5219
+ `[goal-tick] commitGoalState: persist to ${STATE_BRANCH_LABEL} failed (${err instanceof Error ? err.message : String(err)}); will retry next tick
5008
5220
  `
5009
5221
  );
5010
- return;
5011
- }
5012
- const result = pushWithRetry({ cwd: ctx.cwd });
5013
- if (!result.ok) {
5014
- process.stderr.write(`[goal-tick] commitGoalState: push failed (${result.reason}); will retry next tick
5015
- `);
5016
5222
  }
5017
5223
  };
5224
+ var STATE_BRANCH_LABEL = "kody-state";
5018
5225
  function describeCommitMessage(goal) {
5019
5226
  if (goal.state === "closed") return `chore(goals): abandon ${goal.id} (cleanup complete)`;
5020
5227
  if (goal.state === "awaiting-merge") return `chore(goals): park ${goal.id} awaiting merge`;
@@ -5029,7 +5236,7 @@ function describeCommitMessage(goal) {
5029
5236
  }
5030
5237
 
5031
5238
  // src/scripts/composePrompt.ts
5032
- import * as fs21 from "fs";
5239
+ import * as fs22 from "fs";
5033
5240
  import * as path21 from "path";
5034
5241
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
5035
5242
  var composePrompt = async (ctx, profile) => {
@@ -5042,7 +5249,7 @@ var composePrompt = async (ctx, profile) => {
5042
5249
  ].filter(Boolean);
5043
5250
  let templatePath = "";
5044
5251
  for (const c of candidates) {
5045
- if (fs21.existsSync(c)) {
5252
+ if (fs22.existsSync(c)) {
5046
5253
  templatePath = c;
5047
5254
  break;
5048
5255
  }
@@ -5050,7 +5257,7 @@ var composePrompt = async (ctx, profile) => {
5050
5257
  if (!templatePath) {
5051
5258
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
5052
5259
  }
5053
- const template = fs21.readFileSync(templatePath, "utf-8");
5260
+ const template = fs22.readFileSync(templatePath, "utf-8");
5054
5261
  const tokens = {
5055
5262
  ...stringifyAll(ctx.args, "args."),
5056
5263
  ...stringifyAll(ctx.data, ""),
@@ -5128,9 +5335,6 @@ function formatToolsUsage(profile) {
5128
5335
 
5129
5336
  // src/scripts/createQaGoal.ts
5130
5337
  init_issue();
5131
- import { execFileSync as execFileSync11 } from "child_process";
5132
- import * as fs22 from "fs";
5133
- import * as path22 from "path";
5134
5338
 
5135
5339
  // src/scripts/postReviewResult.ts
5136
5340
  init_issue();
@@ -5382,104 +5586,6 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
5382
5586
  if (!m) throw new Error(`gh issue create returned unexpected output: ${out}`);
5383
5587
  return { number: Number(m[1]), created: true };
5384
5588
  }
5385
- function writeStateFile(cwd, goalId, lastDispatchedIssue) {
5386
- const dir = path22.join(cwd, ".kody", "goals", goalId);
5387
- fs22.mkdirSync(dir, { recursive: true });
5388
- const state = {
5389
- version: 1,
5390
- state: "active",
5391
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5392
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5393
- ...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
5394
- };
5395
- const filePath = path22.join(dir, "state.json");
5396
- fs22.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
5397
- `);
5398
- return filePath;
5399
- }
5400
- function gitTry(args, cwd) {
5401
- const env = { ...process.env, SKIP_HOOKS: "1", HUSKY: "0" };
5402
- try {
5403
- execFileSync11("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], env });
5404
- return { ok: true, stderr: "" };
5405
- } catch (err) {
5406
- const e = err;
5407
- const stderr = typeof e?.stderr === "string" ? e.stderr : Buffer.isBuffer(e?.stderr) ? e.stderr.toString("utf8") : e?.message ?? "";
5408
- return { ok: false, stderr: stderr.trim() };
5409
- }
5410
- }
5411
- function commitAndPushState(filePath, goalId, cwd) {
5412
- const add = gitTry(["add", filePath], cwd);
5413
- if (!add.ok) {
5414
- process.stderr.write(`[createQaGoal] git add failed: ${add.stderr.slice(-400) || "(no stderr)"}
5415
- `);
5416
- return;
5417
- }
5418
- const diff = gitTry(["diff", "--cached", "--quiet"], cwd);
5419
- if (diff.ok) {
5420
- process.stderr.write(`[createQaGoal] state.json unchanged \u2014 nothing to commit
5421
- `);
5422
- return;
5423
- }
5424
- const commit = gitTry(["commit", "-m", `chore(goals): activate ${goalId}`, "--quiet"], cwd);
5425
- if (!commit.ok) {
5426
- process.stderr.write(`[createQaGoal] git commit failed: ${commit.stderr.slice(-400) || "(no stderr)"}
5427
- `);
5428
- return;
5429
- }
5430
- const push = gitTry(["push", "--quiet"], cwd);
5431
- if (push.ok) return;
5432
- const stderr = push.stderr;
5433
- const tail = stderr.slice(-400) || "(no stderr captured)";
5434
- if (/non-fast-forward|rejected|fetch first|behind/i.test(stderr)) {
5435
- process.stderr.write(`[createQaGoal] push rejected (non-fast-forward) \u2014 pulling --rebase and retrying
5436
- `);
5437
- const rebase = gitTry(["pull", "--rebase", "--autostash", "--quiet"], cwd);
5438
- if (!rebase.ok) {
5439
- process.stderr.write(
5440
- `[createQaGoal] rebase failed (manual recovery required): ${rebase.stderr.slice(-400) || "(no stderr)"}
5441
- `
5442
- );
5443
- return;
5444
- }
5445
- const retryPush = gitTry(["push", "--quiet"], cwd);
5446
- if (retryPush.ok) {
5447
- process.stderr.write(`[createQaGoal] push succeeded after rebase
5448
- `);
5449
- return;
5450
- }
5451
- process.stderr.write(
5452
- `[createQaGoal] push still failed after rebase: ${retryPush.stderr.slice(-400) || "(no stderr)"}
5453
- `
5454
- );
5455
- return;
5456
- }
5457
- if (/pre-push|hook|husky/i.test(stderr)) {
5458
- process.stderr.write(`[createQaGoal] push rejected by pre-push hook \u2014 retrying with --no-verify
5459
- `);
5460
- process.stderr.write(`[createQaGoal] hook output:
5461
- ${tail}
5462
- `);
5463
- const noVerify = gitTry(["push", "--no-verify", "--quiet"], cwd);
5464
- if (noVerify.ok) {
5465
- process.stderr.write(`[createQaGoal] push succeeded with --no-verify (consider adding kody artifacts to ignore configs)
5466
- `);
5467
- return;
5468
- }
5469
- process.stderr.write(
5470
- `[createQaGoal] --no-verify push also failed: ${noVerify.stderr.slice(-400) || "(no stderr)"}
5471
- `
5472
- );
5473
- return;
5474
- }
5475
- process.stderr.write(
5476
- `[createQaGoal] state.json commit landed but push failed.
5477
- [createQaGoal] The goal will not be visible to goal-scheduler in CI until you run 'git push' manually.
5478
- [createQaGoal] git stderr:
5479
- ${tail}
5480
- `
5481
- );
5482
- }
5483
5589
  function createTaskIssue(finding, goalId, manifestNumber, cwd) {
5484
5590
  const labels = [`goal:${goalId}`, severityLabel(finding.severity), FINDING_LABEL];
5485
5591
  ensureLabel(`goal:${goalId}`, "1d76db", `goal: ${goalId}`, cwd);
@@ -5514,36 +5620,46 @@ var createQaGoal = async (ctx, _profile, agentResult) => {
5514
5620
  ctx.data.action = failedAction2("empty report body");
5515
5621
  return;
5516
5622
  }
5623
+ const { markdown } = splitReport(finalText);
5624
+ const verdict = detectVerdict(markdown);
5625
+ const existingIssue = ctx.args.issue;
5626
+ if (typeof existingIssue === "number" && existingIssue > 0) {
5627
+ try {
5628
+ postIssueComment(existingIssue, finalText, ctx.cwd);
5629
+ } catch (err) {
5630
+ const msg = err instanceof Error ? err.message : String(err);
5631
+ ctx.output.exitCode = 4;
5632
+ ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
5633
+ ctx.data.action = failedAction2(ctx.output.reason);
5634
+ return;
5635
+ }
5636
+ process.stdout.write(
5637
+ `
5638
+ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
5639
+ `
5640
+ );
5641
+ ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
5642
+ ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
5643
+ return;
5644
+ }
5645
+ await promoteReportToGoal(
5646
+ ctx,
5647
+ finalText,
5648
+ ctx.args.scope,
5649
+ ctx.args.goal
5650
+ );
5651
+ };
5652
+ async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
5517
5653
  const { markdown, data, jsonError } = splitReport(finalText);
5518
5654
  const verdict = detectVerdict(markdown);
5519
5655
  const findings = data?.findings ?? [];
5520
- const existingIssue = ctx.args.issue;
5521
5656
  if (findings.length === 0 || jsonError) {
5522
5657
  if (jsonError) {
5523
- process.stderr.write(`[createQaGoal] JSON parse: ${jsonError} \u2014 falling back to single-issue mode
5658
+ process.stderr.write(`[promoteReportToGoal] JSON parse: ${jsonError} \u2014 falling back to single-issue mode
5524
5659
  `);
5525
5660
  }
5526
- if (typeof existingIssue === "number" && existingIssue > 0) {
5527
- try {
5528
- postIssueComment(existingIssue, finalText, ctx.cwd);
5529
- } catch (err) {
5530
- const msg = err instanceof Error ? err.message : String(err);
5531
- ctx.output.exitCode = 4;
5532
- ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
5533
- ctx.data.action = failedAction2(ctx.output.reason);
5534
- return;
5535
- }
5536
- process.stdout.write(
5537
- `
5538
- QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
5539
- `
5540
- );
5541
- ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
5542
- ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
5543
- return;
5544
- }
5545
5661
  ensureLabel(FINDING_LABEL, "ededed", "kody: QA finding", ctx.cwd);
5546
- const scope2 = ctx.args.scope;
5662
+ const scope2 = scopeArg;
5547
5663
  const title = `QA [${verdict}]: ${scope2?.trim() || "smoke"} \u2014 ${todayIso()}`.slice(0, 240);
5548
5664
  let url = "";
5549
5665
  try {
@@ -5571,8 +5687,8 @@ QA_REPORT_POSTED=${url} (verdict: ${verdict})
5571
5687
  ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
5572
5688
  return;
5573
5689
  }
5574
- const explicitGoal = ctx.args.goal?.trim();
5575
- const scope = ctx.args.scope;
5690
+ const explicitGoal = explicitGoalArg?.trim();
5691
+ const scope = scopeArg;
5576
5692
  let goalId;
5577
5693
  let manifestIssueNumber = null;
5578
5694
  let manifestCreated = false;
@@ -5639,8 +5755,17 @@ ${markdown}`,
5639
5755
  `);
5640
5756
  }
5641
5757
  }
5642
- const stateFile = writeStateFile(ctx.cwd, goalId);
5643
- commitAndPushState(stateFile, goalId, ctx.cwd);
5758
+ const now = nowIso();
5759
+ const goalState = { state: "active", startedAt: now, updatedAt: now, extra: { version: 1 } };
5760
+ try {
5761
+ putGoalState(ctx.config.github.owner, ctx.config.github.repo, goalId, goalState, `chore(goals): activate ${goalId}`, ctx.cwd);
5762
+ } catch (err) {
5763
+ process.stderr.write(
5764
+ `[createQaGoal] failed to persist goal state to kody-state: ${err instanceof Error ? err.message : String(err)}
5765
+ [createQaGoal] goal-scheduler will not see ${goalId} until this succeeds.
5766
+ `
5767
+ );
5768
+ }
5644
5769
  const repoUrl = `https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}`;
5645
5770
  if (manifestIssueNumber !== null) {
5646
5771
  const verb = manifestUpdated ? manifestCreated ? "OPENED" : "UPDATED" : "TARGETED";
@@ -5670,7 +5795,7 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
5670
5795
  mode: explicitGoal ? "goal-attach" : manifestCreated ? "goal-create" : "goal-append"
5671
5796
  });
5672
5797
  ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
5673
- };
5798
+ }
5674
5799
 
5675
5800
  // src/goal/operations.ts
5676
5801
  init_issue();
@@ -5909,13 +6034,13 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
5909
6034
  }
5910
6035
 
5911
6036
  // src/scripts/diagMcp.ts
5912
- import { execFileSync as execFileSync12 } from "child_process";
6037
+ import { execFileSync as execFileSync10 } from "child_process";
5913
6038
  import * as fs23 from "fs";
5914
6039
  import * as os4 from "os";
5915
- import * as path23 from "path";
6040
+ import * as path22 from "path";
5916
6041
  var diagMcp = async (_ctx) => {
5917
6042
  const home = os4.homedir();
5918
- const cacheDir = path23.join(home, ".cache", "ms-playwright");
6043
+ const cacheDir = path22.join(home, ".cache", "ms-playwright");
5919
6044
  let entries = [];
5920
6045
  try {
5921
6046
  entries = fs23.readdirSync(cacheDir);
@@ -5929,7 +6054,7 @@ var diagMcp = async (_ctx) => {
5929
6054
  process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
5930
6055
  `);
5931
6056
  try {
5932
- const v = execFileSync12("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
6057
+ const v = execFileSync10("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
5933
6058
  stdio: "pipe",
5934
6059
  timeout: 6e4,
5935
6060
  encoding: "utf8"
@@ -5945,16 +6070,16 @@ var diagMcp = async (_ctx) => {
5945
6070
 
5946
6071
  // src/scripts/discoverQaContext.ts
5947
6072
  import * as fs25 from "fs";
5948
- import * as path25 from "path";
6073
+ import * as path24 from "path";
5949
6074
 
5950
6075
  // src/scripts/frameworkDetectors.ts
5951
6076
  import * as fs24 from "fs";
5952
- import * as path24 from "path";
6077
+ import * as path23 from "path";
5953
6078
  function detectFrameworks(cwd) {
5954
6079
  const out = [];
5955
6080
  let deps = {};
5956
6081
  try {
5957
- const pkg = JSON.parse(fs24.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
6082
+ const pkg = JSON.parse(fs24.readFileSync(path23.join(cwd, "package.json"), "utf-8"));
5958
6083
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
5959
6084
  } catch {
5960
6085
  return out;
@@ -5991,7 +6116,7 @@ function detectFrameworks(cwd) {
5991
6116
  }
5992
6117
  function findFile(cwd, candidates) {
5993
6118
  for (const c of candidates) {
5994
- if (fs24.existsSync(path24.join(cwd, c))) return c;
6119
+ if (fs24.existsSync(path23.join(cwd, c))) return c;
5995
6120
  }
5996
6121
  return null;
5997
6122
  }
@@ -6004,7 +6129,7 @@ var COLLECTION_DIRS = [
6004
6129
  function discoverPayloadCollections(cwd) {
6005
6130
  const out = [];
6006
6131
  for (const dir of COLLECTION_DIRS) {
6007
- const full = path24.join(cwd, dir);
6132
+ const full = path23.join(cwd, dir);
6008
6133
  if (!fs24.existsSync(full)) continue;
6009
6134
  let files;
6010
6135
  try {
@@ -6014,7 +6139,7 @@ function discoverPayloadCollections(cwd) {
6014
6139
  }
6015
6140
  for (const file of files) {
6016
6141
  try {
6017
- const filePath = path24.join(full, file);
6142
+ const filePath = path23.join(full, file);
6018
6143
  const content = fs24.readFileSync(filePath, "utf-8").slice(0, 1e4);
6019
6144
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
6020
6145
  if (!slugMatch) continue;
@@ -6029,7 +6154,7 @@ function discoverPayloadCollections(cwd) {
6029
6154
  out.push({
6030
6155
  name,
6031
6156
  slug,
6032
- filePath: path24.relative(cwd, filePath),
6157
+ filePath: path23.relative(cwd, filePath),
6033
6158
  fields: fields.slice(0, 20),
6034
6159
  hasAdmin
6035
6160
  });
@@ -6043,7 +6168,7 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
6043
6168
  function discoverAdminComponents(cwd, collections) {
6044
6169
  const out = [];
6045
6170
  for (const dir of ADMIN_COMPONENT_DIRS) {
6046
- const full = path24.join(cwd, dir);
6171
+ const full = path23.join(cwd, dir);
6047
6172
  if (!fs24.existsSync(full)) continue;
6048
6173
  let entries;
6049
6174
  try {
@@ -6052,19 +6177,19 @@ function discoverAdminComponents(cwd, collections) {
6052
6177
  continue;
6053
6178
  }
6054
6179
  for (const entry of entries) {
6055
- const entryPath = path24.join(full, entry.name);
6180
+ const entryPath = path23.join(full, entry.name);
6056
6181
  let name;
6057
6182
  let filePath;
6058
6183
  if (entry.isDirectory()) {
6059
6184
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
6060
- (f) => fs24.existsSync(path24.join(entryPath, f))
6185
+ (f) => fs24.existsSync(path23.join(entryPath, f))
6061
6186
  );
6062
6187
  if (!indexFile) continue;
6063
6188
  name = entry.name;
6064
- filePath = path24.relative(cwd, path24.join(entryPath, indexFile));
6189
+ filePath = path23.relative(cwd, path23.join(entryPath, indexFile));
6065
6190
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
6066
6191
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
6067
- filePath = path24.relative(cwd, entryPath);
6192
+ filePath = path23.relative(cwd, entryPath);
6068
6193
  } else {
6069
6194
  continue;
6070
6195
  }
@@ -6072,7 +6197,7 @@ function discoverAdminComponents(cwd, collections) {
6072
6197
  if (collections) {
6073
6198
  for (const col of collections) {
6074
6199
  try {
6075
- const colContent = fs24.readFileSync(path24.join(cwd, col.filePath), "utf-8");
6200
+ const colContent = fs24.readFileSync(path23.join(cwd, col.filePath), "utf-8");
6076
6201
  if (colContent.includes(name)) {
6077
6202
  usedInCollection = col.slug;
6078
6203
  break;
@@ -6091,7 +6216,7 @@ function scanApiRoutes(cwd) {
6091
6216
  const out = [];
6092
6217
  const appDirs = ["src/app", "app"];
6093
6218
  for (const appDir of appDirs) {
6094
- const apiDir = path24.join(cwd, appDir, "api");
6219
+ const apiDir = path23.join(cwd, appDir, "api");
6095
6220
  if (!fs24.existsSync(apiDir)) continue;
6096
6221
  walkApiRoutes(apiDir, "/api", cwd, out);
6097
6222
  break;
@@ -6108,7 +6233,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6108
6233
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
6109
6234
  if (routeFile) {
6110
6235
  try {
6111
- const content = fs24.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
6236
+ const content = fs24.readFileSync(path23.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
6112
6237
  const methods = HTTP_METHODS.filter(
6113
6238
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
6114
6239
  );
@@ -6116,7 +6241,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6116
6241
  out.push({
6117
6242
  path: prefix,
6118
6243
  methods,
6119
- filePath: path24.relative(cwd, path24.join(dir, routeFile.name))
6244
+ filePath: path23.relative(cwd, path23.join(dir, routeFile.name))
6120
6245
  });
6121
6246
  }
6122
6247
  } catch {
@@ -6127,7 +6252,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6127
6252
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6128
6253
  let segment = entry.name;
6129
6254
  if (segment.startsWith("(") && segment.endsWith(")")) {
6130
- walkApiRoutes(path24.join(dir, entry.name), prefix, cwd, out);
6255
+ walkApiRoutes(path23.join(dir, entry.name), prefix, cwd, out);
6131
6256
  continue;
6132
6257
  }
6133
6258
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -6135,7 +6260,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6135
6260
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
6136
6261
  segment = `:${segment.slice(1, -1)}`;
6137
6262
  }
6138
- walkApiRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
6263
+ walkApiRoutes(path23.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
6139
6264
  }
6140
6265
  }
6141
6266
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -6155,7 +6280,7 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
6155
6280
  function scanEnvVars(cwd) {
6156
6281
  const candidates = [".env.example", ".env.local.example", ".env.template"];
6157
6282
  for (const envFile of candidates) {
6158
- const envPath = path24.join(cwd, envFile);
6283
+ const envPath = path23.join(cwd, envFile);
6159
6284
  if (!fs24.existsSync(envPath)) continue;
6160
6285
  try {
6161
6286
  const content = fs24.readFileSync(envPath, "utf-8");
@@ -6206,9 +6331,9 @@ function runQaDiscovery(cwd) {
6206
6331
  }
6207
6332
  function detectDevServer(cwd, out) {
6208
6333
  try {
6209
- const pkg = JSON.parse(fs25.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
6334
+ const pkg = JSON.parse(fs25.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
6210
6335
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
6211
- const pm = fs25.existsSync(path25.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs25.existsSync(path25.join(cwd, "yarn.lock")) ? "yarn" : fs25.existsSync(path25.join(cwd, "bun.lockb")) ? "bun" : "npm";
6336
+ const pm = fs25.existsSync(path24.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs25.existsSync(path24.join(cwd, "yarn.lock")) ? "yarn" : fs25.existsSync(path24.join(cwd, "bun.lockb")) ? "bun" : "npm";
6212
6337
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
6213
6338
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
6214
6339
  else if (allDeps.vite) out.devPort = 5173;
@@ -6218,7 +6343,7 @@ function detectDevServer(cwd, out) {
6218
6343
  function scanFrontendRoutes(cwd, out) {
6219
6344
  const appDirs = ["src/app", "app"];
6220
6345
  for (const appDir of appDirs) {
6221
- const full = path25.join(cwd, appDir);
6346
+ const full = path24.join(cwd, appDir);
6222
6347
  if (!fs25.existsSync(full)) continue;
6223
6348
  walkFrontendRoutes(full, "", out);
6224
6349
  break;
@@ -6244,7 +6369,7 @@ function walkFrontendRoutes(dir, prefix, out) {
6244
6369
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6245
6370
  let segment = entry.name;
6246
6371
  if (segment.startsWith("(") && segment.endsWith(")")) {
6247
- walkFrontendRoutes(path25.join(dir, entry.name), prefix, out);
6372
+ walkFrontendRoutes(path24.join(dir, entry.name), prefix, out);
6248
6373
  continue;
6249
6374
  }
6250
6375
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -6252,7 +6377,7 @@ function walkFrontendRoutes(dir, prefix, out) {
6252
6377
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
6253
6378
  segment = `:${segment.slice(1, -1)}`;
6254
6379
  }
6255
- walkFrontendRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, out);
6380
+ walkFrontendRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, out);
6256
6381
  }
6257
6382
  }
6258
6383
  function detectAuthFiles(cwd, out) {
@@ -6269,13 +6394,13 @@ function detectAuthFiles(cwd, out) {
6269
6394
  "src/app/api/oauth"
6270
6395
  ];
6271
6396
  for (const c of candidates) {
6272
- if (fs25.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
6397
+ if (fs25.existsSync(path24.join(cwd, c))) out.authFiles.push(c);
6273
6398
  }
6274
6399
  }
6275
6400
  function detectRoles(cwd, out) {
6276
6401
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
6277
6402
  for (const rp of rolePaths) {
6278
- const dir = path25.join(cwd, rp);
6403
+ const dir = path24.join(cwd, rp);
6279
6404
  if (!fs25.existsSync(dir)) continue;
6280
6405
  let files;
6281
6406
  try {
@@ -6285,7 +6410,7 @@ function detectRoles(cwd, out) {
6285
6410
  }
6286
6411
  for (const f of files) {
6287
6412
  try {
6288
- const content = fs25.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
6413
+ const content = fs25.readFileSync(path24.join(dir, f), "utf-8").slice(0, 5e3);
6289
6414
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
6290
6415
  if (roleMatches) {
6291
6416
  for (const m of roleMatches) {
@@ -6369,7 +6494,7 @@ var discoverQaContext = async (ctx) => {
6369
6494
  };
6370
6495
 
6371
6496
  // src/scripts/dispatch.ts
6372
- import { execFileSync as execFileSync13 } from "child_process";
6497
+ import { execFileSync as execFileSync11 } from "child_process";
6373
6498
  var API_TIMEOUT_MS4 = 3e4;
6374
6499
  var dispatch = async (ctx, _profile, _agentResult, args) => {
6375
6500
  const next = args?.next;
@@ -6405,7 +6530,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
6405
6530
  const sub = usePr ? "pr" : "issue";
6406
6531
  const body = `@kody ${next}`;
6407
6532
  try {
6408
- execFileSync13("gh", [sub, "comment", String(targetNumber), "--body", body], {
6533
+ execFileSync11("gh", [sub, "comment", String(targetNumber), "--body", body], {
6409
6534
  timeout: API_TIMEOUT_MS4,
6410
6535
  cwd: ctx.cwd,
6411
6536
  stdio: ["ignore", "pipe", "pipe"]
@@ -6425,7 +6550,7 @@ function parsePr(url) {
6425
6550
  }
6426
6551
 
6427
6552
  // src/scripts/dispatchClassified.ts
6428
- import { execFileSync as execFileSync14 } from "child_process";
6553
+ import { execFileSync as execFileSync12 } from "child_process";
6429
6554
  var API_TIMEOUT_MS5 = 3e4;
6430
6555
  var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
6431
6556
  var dispatchClassified = async (ctx) => {
@@ -6449,7 +6574,7 @@ ${auditLine}
6449
6574
 
6450
6575
  ${stateBody}`;
6451
6576
  try {
6452
- execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
6577
+ execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
6453
6578
  cwd: ctx.cwd,
6454
6579
  timeout: API_TIMEOUT_MS5,
6455
6580
  stdio: ["ignore", "pipe", "pipe"]
@@ -6470,7 +6595,7 @@ function failedAction3(reason) {
6470
6595
 
6471
6596
  // src/scripts/dispatchJobFileTicks.ts
6472
6597
  import * as fs27 from "fs";
6473
- import * as path27 from "path";
6598
+ import * as path26 from "path";
6474
6599
 
6475
6600
  // src/scripts/jobFrontmatter.ts
6476
6601
  var SCHEDULE_EVERY_VALUES = [
@@ -6563,44 +6688,6 @@ function stripQuotes(value) {
6563
6688
  // src/scripts/jobState/contentsApiBackend.ts
6564
6689
  init_issue();
6565
6690
 
6566
- // src/stateBranch.ts
6567
- init_issue();
6568
- var STATE_BRANCH = "kody-state";
6569
- function is404(err) {
6570
- const msg = err instanceof Error ? err.message : String(err);
6571
- return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
6572
- }
6573
- function ensureStateBranch(owner, repo, cwd) {
6574
- try {
6575
- gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
6576
- return;
6577
- } catch (err) {
6578
- if (!is404(err)) throw err;
6579
- }
6580
- const repoInfo = JSON.parse(gh(["api", `/repos/${owner}/${repo}`], { cwd }));
6581
- const defaultBranch2 = repoInfo.default_branch;
6582
- if (!defaultBranch2) {
6583
- throw new Error(`ensureStateBranch: could not resolve default branch for ${owner}/${repo}`);
6584
- }
6585
- const headRef = JSON.parse(
6586
- gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${defaultBranch2}`], { cwd })
6587
- );
6588
- const sha = headRef.object?.sha;
6589
- if (!sha) {
6590
- throw new Error(`ensureStateBranch: could not resolve head sha for ${owner}/${repo}@${defaultBranch2}`);
6591
- }
6592
- try {
6593
- gh(["api", "--method", "POST", `/repos/${owner}/${repo}/git/refs`, "--input", "-"], {
6594
- cwd,
6595
- input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
6596
- });
6597
- } catch (err) {
6598
- const msg = err instanceof Error ? err.message : String(err);
6599
- if (/already exists/i.test(msg) || /HTTP 422/i.test(msg)) return;
6600
- throw err;
6601
- }
6602
- }
6603
-
6604
6691
  // src/scripts/issueStateComment.ts
6605
6692
  init_issue();
6606
6693
  function isStateEnvelope(x) {
@@ -6778,7 +6865,7 @@ var ContentsApiBackend = class {
6778
6865
 
6779
6866
  // src/scripts/jobState/localFileBackend.ts
6780
6867
  import * as fs26 from "fs";
6781
- import * as path26 from "path";
6868
+ import * as path25 from "path";
6782
6869
  var LocalFileBackend = class {
6783
6870
  name = "local-file";
6784
6871
  cwd;
@@ -6793,7 +6880,7 @@ var LocalFileBackend = class {
6793
6880
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
6794
6881
  this.cwd = opts.cwd;
6795
6882
  this.jobsDir = opts.jobsDir;
6796
- this.absDir = path26.join(opts.cwd, opts.jobsDir);
6883
+ this.absDir = path25.join(opts.cwd, opts.jobsDir);
6797
6884
  this.owner = opts.owner;
6798
6885
  this.repo = opts.repo;
6799
6886
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -6853,7 +6940,7 @@ var LocalFileBackend = class {
6853
6940
  }
6854
6941
  load(slug) {
6855
6942
  const relPath = stateFilePath(this.jobsDir, slug);
6856
- const absPath = path26.join(this.cwd, relPath);
6943
+ const absPath = path25.join(this.cwd, relPath);
6857
6944
  if (!fs26.existsSync(absPath)) {
6858
6945
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
6859
6946
  }
@@ -6874,8 +6961,8 @@ var LocalFileBackend = class {
6874
6961
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
6875
6962
  return false;
6876
6963
  }
6877
- const absPath = path26.join(this.cwd, loaded.path);
6878
- fs26.mkdirSync(path26.dirname(absPath), { recursive: true });
6964
+ const absPath = path25.join(this.cwd, loaded.path);
6965
+ fs26.mkdirSync(path25.dirname(absPath), { recursive: true });
6879
6966
  const body = JSON.stringify(next, null, 2) + "\n";
6880
6967
  fs26.writeFileSync(absPath, body, "utf-8");
6881
6968
  return true;
@@ -6955,7 +7042,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
6955
7042
  await backend.hydrate();
6956
7043
  }
6957
7044
  try {
6958
- const slugs = listJobSlugs(path27.join(ctx.cwd, jobsDir));
7045
+ const slugs = listJobSlugs(path26.join(ctx.cwd, jobsDir));
6959
7046
  ctx.data.jobSlugCount = slugs.length;
6960
7047
  if (slugs.length === 0) {
6961
7048
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -7068,7 +7155,7 @@ function formatAgo(ms) {
7068
7155
  }
7069
7156
  function readJobFrontmatter(cwd, jobsDir, slug) {
7070
7157
  try {
7071
- const raw = fs27.readFileSync(path27.join(cwd, jobsDir, `${slug}.md`), "utf-8");
7158
+ const raw = fs27.readFileSync(path26.join(cwd, jobsDir, `${slug}.md`), "utf-8");
7072
7159
  return splitFrontmatter2(raw).frontmatter;
7073
7160
  } catch {
7074
7161
  return {};
@@ -7605,7 +7692,7 @@ var finalizeTerminal = async (ctx) => {
7605
7692
 
7606
7693
  // src/scripts/finishFlow.ts
7607
7694
  init_issue();
7608
- import { execFileSync as execFileSync15 } from "child_process";
7695
+ import { execFileSync as execFileSync13 } from "child_process";
7609
7696
  var TERMINAL_PHASE = {
7610
7697
  "review-passed": { phase: "shipped", status: "succeeded" },
7611
7698
  "fix-applied": { phase: "shipped", status: "succeeded" },
@@ -7645,7 +7732,7 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
7645
7732
  **PR:** ${state.core.prUrl}` : "";
7646
7733
  const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
7647
7734
  try {
7648
- execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7735
+ execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7649
7736
  timeout: API_TIMEOUT_MS6,
7650
7737
  cwd: ctx.cwd,
7651
7738
  stdio: ["ignore", "pipe", "pipe"]
@@ -7675,9 +7762,9 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
7675
7762
  };
7676
7763
 
7677
7764
  // src/branch.ts
7678
- import { execFileSync as execFileSync16 } from "child_process";
7765
+ import { execFileSync as execFileSync14 } from "child_process";
7679
7766
  function git2(args, cwd) {
7680
- return execFileSync16("git", args, {
7767
+ return execFileSync14("git", args, {
7681
7768
  encoding: "utf-8",
7682
7769
  timeout: 3e4,
7683
7770
  cwd,
@@ -7694,11 +7781,11 @@ function getCurrentBranch(cwd) {
7694
7781
  }
7695
7782
  function resetWorkingTree(cwd) {
7696
7783
  try {
7697
- execFileSync16("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7784
+ execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7698
7785
  } catch {
7699
7786
  }
7700
7787
  try {
7701
- execFileSync16("git", ["clean", "-fd"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7788
+ execFileSync14("git", ["clean", "-fd"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7702
7789
  } catch {
7703
7790
  }
7704
7791
  }
@@ -7710,14 +7797,14 @@ function checkoutPrBranch(prNumber, cwd) {
7710
7797
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
7711
7798
  };
7712
7799
  try {
7713
- execFileSync16("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7800
+ execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7714
7801
  } catch {
7715
7802
  }
7716
7803
  try {
7717
- execFileSync16("git", ["clean", "-fd"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7804
+ execFileSync14("git", ["clean", "-fd"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7718
7805
  } catch {
7719
7806
  }
7720
- execFileSync16("gh", ["pr", "checkout", String(prNumber)], {
7807
+ execFileSync14("gh", ["pr", "checkout", String(prNumber)], {
7721
7808
  cwd,
7722
7809
  env,
7723
7810
  stdio: ["ignore", "pipe", "pipe"],
@@ -7843,7 +7930,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
7843
7930
  }
7844
7931
 
7845
7932
  // src/gha.ts
7846
- import { execFileSync as execFileSync17 } from "child_process";
7933
+ import { execFileSync as execFileSync15 } from "child_process";
7847
7934
  import * as fs28 from "fs";
7848
7935
  function getRunUrl() {
7849
7936
  const server = process.env.GITHUB_SERVER_URL;
@@ -7886,7 +7973,7 @@ function reactToTriggerComment(cwd) {
7886
7973
  for (let attempt = 0; attempt < 3; attempt++) {
7887
7974
  if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
7888
7975
  try {
7889
- execFileSync17("gh", args, opts);
7976
+ execFileSync15("gh", args, opts);
7890
7977
  return;
7891
7978
  } catch (err) {
7892
7979
  lastErr = err;
@@ -7899,7 +7986,7 @@ function reactToTriggerComment(cwd) {
7899
7986
  }
7900
7987
  function sleepMs(ms) {
7901
7988
  try {
7902
- execFileSync17("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
7989
+ execFileSync15("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
7903
7990
  } catch {
7904
7991
  }
7905
7992
  }
@@ -7908,7 +7995,7 @@ function sleepMs(ms) {
7908
7995
  init_issue();
7909
7996
 
7910
7997
  // src/workflow.ts
7911
- import { execFileSync as execFileSync18 } from "child_process";
7998
+ import { execFileSync as execFileSync16 } from "child_process";
7912
7999
  var GH_TIMEOUT_MS = 3e4;
7913
8000
  function ghToken3() {
7914
8001
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
@@ -7916,7 +8003,7 @@ function ghToken3() {
7916
8003
  function gh3(args, cwd) {
7917
8004
  const token = ghToken3();
7918
8005
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
7919
- return execFileSync18("gh", args, {
8006
+ return execFileSync16("gh", args, {
7920
8007
  encoding: "utf-8",
7921
8008
  timeout: GH_TIMEOUT_MS,
7922
8009
  cwd,
@@ -8151,13 +8238,13 @@ var handleAbandonedGoal = async (ctx) => {
8151
8238
  };
8152
8239
 
8153
8240
  // src/scripts/initFlow.ts
8154
- import { execFileSync as execFileSync19 } from "child_process";
8241
+ import { execFileSync as execFileSync17 } from "child_process";
8155
8242
  import * as fs29 from "fs";
8156
- import * as path28 from "path";
8243
+ import * as path27 from "path";
8157
8244
  function detectPackageManager(cwd) {
8158
- if (fs29.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
8159
- if (fs29.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
8160
- if (fs29.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
8245
+ if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
8246
+ if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
8247
+ if (fs29.existsSync(path27.join(cwd, "bun.lockb"))) return "bun";
8161
8248
  return "npm";
8162
8249
  }
8163
8250
  function qualityCommandsFor(pm) {
@@ -8177,7 +8264,7 @@ function schemaUrlFromPkg() {
8177
8264
  function detectOwnerRepo(cwd) {
8178
8265
  let url;
8179
8266
  try {
8180
- url = execFileSync19("git", ["remote", "get-url", "origin"], {
8267
+ url = execFileSync17("git", ["remote", "get-url", "origin"], {
8181
8268
  cwd,
8182
8269
  encoding: "utf-8",
8183
8270
  stdio: ["ignore", "pipe", "pipe"]
@@ -8262,7 +8349,7 @@ jobs:
8262
8349
  `;
8263
8350
  function defaultBranchFromGit(cwd) {
8264
8351
  try {
8265
- const ref = execFileSync19("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
8352
+ const ref = execFileSync17("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
8266
8353
  cwd,
8267
8354
  encoding: "utf-8",
8268
8355
  stdio: ["ignore", "pipe", "pipe"]
@@ -8270,7 +8357,7 @@ function defaultBranchFromGit(cwd) {
8270
8357
  return ref.replace("refs/remotes/origin/", "");
8271
8358
  } catch {
8272
8359
  try {
8273
- return execFileSync19("git", ["branch", "--show-current"], {
8360
+ return execFileSync17("git", ["branch", "--show-current"], {
8274
8361
  cwd,
8275
8362
  encoding: "utf-8",
8276
8363
  stdio: ["ignore", "pipe", "pipe"]
@@ -8286,7 +8373,7 @@ function performInit(cwd, force) {
8286
8373
  const pm = detectPackageManager(cwd);
8287
8374
  const ownerRepo = detectOwnerRepo(cwd);
8288
8375
  const defaultBranch2 = defaultBranchFromGit(cwd);
8289
- const configPath = path28.join(cwd, "kody.config.json");
8376
+ const configPath = path27.join(cwd, "kody.config.json");
8290
8377
  if (fs29.existsSync(configPath) && !force) {
8291
8378
  skipped.push("kody.config.json");
8292
8379
  } else {
@@ -8295,8 +8382,8 @@ function performInit(cwd, force) {
8295
8382
  `);
8296
8383
  wrote.push("kody.config.json");
8297
8384
  }
8298
- const workflowDir = path28.join(cwd, ".github", "workflows");
8299
- const workflowPath = path28.join(workflowDir, "kody.yml");
8385
+ const workflowDir = path27.join(cwd, ".github", "workflows");
8386
+ const workflowPath = path27.join(workflowDir, "kody.yml");
8300
8387
  if (fs29.existsSync(workflowPath) && !force) {
8301
8388
  skipped.push(".github/workflows/kody.yml");
8302
8389
  } else {
@@ -8306,11 +8393,11 @@ function performInit(cwd, force) {
8306
8393
  }
8307
8394
  const builtinJobs = listBuiltinJobs();
8308
8395
  if (builtinJobs.length > 0) {
8309
- const jobsDir = path28.join(cwd, ".kody", "duties");
8396
+ const jobsDir = path27.join(cwd, ".kody", "duties");
8310
8397
  fs29.mkdirSync(jobsDir, { recursive: true });
8311
8398
  for (const job of builtinJobs) {
8312
- const rel = path28.join(".kody", "duties", `${job.slug}.md`);
8313
- const target = path28.join(cwd, rel);
8399
+ const rel = path27.join(".kody", "duties", `${job.slug}.md`);
8400
+ const target = path27.join(cwd, rel);
8314
8401
  if (fs29.existsSync(target) && !force) {
8315
8402
  skipped.push(rel);
8316
8403
  continue;
@@ -8327,7 +8414,7 @@ function performInit(cwd, force) {
8327
8414
  continue;
8328
8415
  }
8329
8416
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
8330
- const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
8417
+ const target = path27.join(workflowDir, `kody-${exe.name}.yml`);
8331
8418
  if (fs29.existsSync(target) && !force) {
8332
8419
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
8333
8420
  continue;
@@ -8409,86 +8496,6 @@ Nothing to do. All files already present. (Use --force to overwrite.)
8409
8496
  init_loadConventions();
8410
8497
  init_loadCoverageRules();
8411
8498
 
8412
- // src/goal/state.ts
8413
- import * as fs30 from "fs";
8414
- import * as path29 from "path";
8415
- var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
8416
- var GoalStateError = class extends Error {
8417
- constructor(path40, message) {
8418
- super(`Invalid goal state at ${path40}:
8419
- ${message}`);
8420
- this.path = path40;
8421
- this.name = "GoalStateError";
8422
- }
8423
- path;
8424
- };
8425
- function parseGoalState(filePath, raw) {
8426
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8427
- throw new GoalStateError(filePath, "must be a JSON object");
8428
- }
8429
- const r = raw;
8430
- const stateValue = r.state;
8431
- if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
8432
- throw new GoalStateError(
8433
- filePath,
8434
- `"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
8435
- );
8436
- }
8437
- const parsed = {
8438
- state: stateValue,
8439
- extra: {}
8440
- };
8441
- if (typeof r.mergeApproved === "boolean") {
8442
- parsed.mergeApproved = r.mergeApproved;
8443
- }
8444
- if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
8445
- parsed.lastDispatchedIssue = r.lastDispatchedIssue;
8446
- }
8447
- for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
8448
- const v = r[ts];
8449
- if (typeof v === "string" && v.length > 0) parsed[ts] = v;
8450
- }
8451
- const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
8452
- for (const [k, v] of Object.entries(r)) {
8453
- if (!known.has(k)) parsed.extra[k] = v;
8454
- }
8455
- return parsed;
8456
- }
8457
- function serializeGoalState(s) {
8458
- const obj = { ...s.extra, state: s.state };
8459
- if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
8460
- if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
8461
- if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
8462
- if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
8463
- if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
8464
- return `${JSON.stringify(obj, null, 2)}
8465
- `;
8466
- }
8467
- function goalStatePath(cwd, goalId) {
8468
- return path29.join(cwd, ".kody", "goals", goalId, "state.json");
8469
- }
8470
- function readGoalState(cwd, goalId) {
8471
- const file = goalStatePath(cwd, goalId);
8472
- if (!fs30.existsSync(file)) {
8473
- throw new GoalStateError(file, "file not found");
8474
- }
8475
- let raw;
8476
- try {
8477
- raw = JSON.parse(fs30.readFileSync(file, "utf-8"));
8478
- } catch (err) {
8479
- throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
8480
- }
8481
- return parseGoalState(file, raw);
8482
- }
8483
- function writeGoalState(cwd, goalId, state) {
8484
- const file = goalStatePath(cwd, goalId);
8485
- fs30.mkdirSync(path29.dirname(file), { recursive: true });
8486
- fs30.writeFileSync(file, serializeGoalState(state), "utf-8");
8487
- }
8488
- function nowIso() {
8489
- return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
8490
- }
8491
-
8492
8499
  // src/scripts/loadGoalState.ts
8493
8500
  var loadGoalState = async (ctx) => {
8494
8501
  const goalId = ctx.args.goal;
@@ -8504,8 +8511,24 @@ var loadGoalState = async (ctx) => {
8504
8511
  ctx.output.reason = "invalid goal id (no slashes or '..' allowed)";
8505
8512
  return;
8506
8513
  }
8514
+ const owner = ctx.config.github?.owner;
8515
+ const repo = ctx.config.github?.repo;
8516
+ if (!owner || !repo) {
8517
+ ctx.skipAgent = true;
8518
+ ctx.output.exitCode = 1;
8519
+ ctx.output.reason = "missing github owner/repo in config";
8520
+ return;
8521
+ }
8507
8522
  try {
8508
- const state = readGoalState(ctx.cwd, goalId);
8523
+ const state = fetchGoalState(owner, repo, goalId, ctx.cwd);
8524
+ if (!state) {
8525
+ process.stdout.write(`[goal-tick] no goal state for ${goalId} on ${owner}/${repo} \u2014 nothing to tick
8526
+ `);
8527
+ ctx.skipAgent = true;
8528
+ ctx.output.exitCode = 0;
8529
+ ctx.output.reason = "no goal state to tick";
8530
+ return;
8531
+ }
8509
8532
  ctx.data.goal = {
8510
8533
  id: goalId,
8511
8534
  state: state.state,
@@ -8580,8 +8603,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
8580
8603
  };
8581
8604
 
8582
8605
  // src/scripts/loadJobFromFile.ts
8583
- import * as fs31 from "fs";
8584
- import * as path30 from "path";
8606
+ import * as fs30 from "fs";
8607
+ import * as path28 from "path";
8585
8608
  var loadJobFromFile = async (ctx, _profile, args) => {
8586
8609
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
8587
8610
  const workersDir = String(args?.workersDir ?? ".kody/staff");
@@ -8590,11 +8613,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8590
8613
  if (!slug) {
8591
8614
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
8592
8615
  }
8593
- const absPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
8594
- if (!fs31.existsSync(absPath)) {
8616
+ const absPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
8617
+ if (!fs30.existsSync(absPath)) {
8595
8618
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
8596
8619
  }
8597
- const raw = fs31.readFileSync(absPath, "utf-8");
8620
+ const raw = fs30.readFileSync(absPath, "utf-8");
8598
8621
  const { title, body } = parseJobFile(raw, slug);
8599
8622
  const frontmatter = splitFrontmatter2(raw).frontmatter;
8600
8623
  const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
@@ -8602,13 +8625,13 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8602
8625
  let workerTitle = "";
8603
8626
  let workerPersona = "";
8604
8627
  if (workerSlug) {
8605
- const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8606
- if (!fs31.existsSync(workerPath)) {
8628
+ const workerPath = path28.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8629
+ if (!fs30.existsSync(workerPath)) {
8607
8630
  throw new Error(
8608
8631
  `loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`
8609
8632
  );
8610
8633
  }
8611
- const workerRaw = fs31.readFileSync(workerPath, "utf-8");
8634
+ const workerRaw = fs30.readFileSync(workerPath, "utf-8");
8612
8635
  const parsed = parseJobFile(workerRaw, workerSlug);
8613
8636
  workerTitle = parsed.title;
8614
8637
  workerPersona = parsed.body;
@@ -8651,18 +8674,18 @@ init_loadMemoryContext();
8651
8674
  init_loadPriorArt();
8652
8675
 
8653
8676
  // src/scripts/loadQaContext.ts
8654
- import * as fs34 from "fs";
8655
- import * as path33 from "path";
8677
+ import * as fs33 from "fs";
8678
+ import * as path31 from "path";
8656
8679
 
8657
8680
  // src/scripts/kodyVariables.ts
8658
- import * as fs33 from "fs";
8659
- import * as path32 from "path";
8681
+ import * as fs32 from "fs";
8682
+ import * as path30 from "path";
8660
8683
  var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
8661
8684
  function readKodyVariables(cwd) {
8662
- const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
8685
+ const full = path30.join(cwd, KODY_VARIABLES_REL_PATH);
8663
8686
  let raw;
8664
8687
  try {
8665
- raw = fs33.readFileSync(full, "utf-8");
8688
+ raw = fs32.readFileSync(full, "utf-8");
8666
8689
  } catch {
8667
8690
  return {};
8668
8691
  }
@@ -8711,18 +8734,18 @@ function readProfileStaff(raw) {
8711
8734
  return { staff: staff ?? legacy ?? ["kody"], body };
8712
8735
  }
8713
8736
  function readProfile(cwd) {
8714
- const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
8715
- if (!fs34.existsSync(dir)) return "";
8737
+ const dir = path31.join(cwd, CONTEXT_DIR_REL_PATH);
8738
+ if (!fs33.existsSync(dir)) return "";
8716
8739
  let entries;
8717
8740
  try {
8718
- entries = fs34.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
8741
+ entries = fs33.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
8719
8742
  } catch {
8720
8743
  return "";
8721
8744
  }
8722
8745
  const blocks = [];
8723
8746
  for (const file of entries) {
8724
8747
  try {
8725
- const raw = fs34.readFileSync(path33.join(dir, file), "utf-8");
8748
+ const raw = fs33.readFileSync(path31.join(dir, file), "utf-8");
8726
8749
  const { staff, body } = readProfileStaff(raw);
8727
8750
  if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
8728
8751
  blocks.push(`## ${file}
@@ -8759,8 +8782,8 @@ var loadQaContext = async (ctx) => {
8759
8782
  init_events();
8760
8783
 
8761
8784
  // src/taskContext.ts
8762
- import * as fs35 from "fs";
8763
- import * as path34 from "path";
8785
+ import * as fs34 from "fs";
8786
+ import * as path32 from "path";
8764
8787
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
8765
8788
  function buildTaskContext(args) {
8766
8789
  return {
@@ -8776,10 +8799,10 @@ function buildTaskContext(args) {
8776
8799
  }
8777
8800
  function persistTaskContext(cwd, ctx) {
8778
8801
  try {
8779
- const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
8780
- fs35.mkdirSync(dir, { recursive: true });
8781
- const file = path34.join(dir, "task-context.json");
8782
- fs35.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8802
+ const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
8803
+ fs34.mkdirSync(dir, { recursive: true });
8804
+ const file = path32.join(dir, "task-context.json");
8805
+ fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8783
8806
  `);
8784
8807
  return file;
8785
8808
  } catch (err) {
@@ -8827,19 +8850,19 @@ var loadTaskState = async (ctx) => {
8827
8850
  };
8828
8851
 
8829
8852
  // src/scripts/loadWorkerAdhoc.ts
8830
- import * as fs36 from "fs";
8831
- import * as path35 from "path";
8853
+ import * as fs35 from "fs";
8854
+ import * as path33 from "path";
8832
8855
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
8833
8856
  const workersDir = String(args?.workersDir ?? ".kody/staff");
8834
8857
  const workerSlug = String(ctx.args.worker ?? "").trim();
8835
8858
  if (!workerSlug) {
8836
8859
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
8837
8860
  }
8838
- const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8839
- if (!fs36.existsSync(workerPath)) {
8861
+ const workerPath = path33.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8862
+ if (!fs35.existsSync(workerPath)) {
8840
8863
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
8841
8864
  }
8842
- const { title, body } = parsePersona(fs36.readFileSync(workerPath, "utf-8"), workerSlug);
8865
+ const { title, body } = parsePersona(fs35.readFileSync(workerPath, "utf-8"), workerSlug);
8843
8866
  const message = resolveMessage(ctx.args.message);
8844
8867
  if (!message) {
8845
8868
  throw new Error(
@@ -8859,9 +8882,9 @@ function resolveMessage(messageArg) {
8859
8882
  }
8860
8883
  function readCommentBody() {
8861
8884
  const eventPath = process.env.GITHUB_EVENT_PATH;
8862
- if (!eventPath || !fs36.existsSync(eventPath)) return "";
8885
+ if (!eventPath || !fs35.existsSync(eventPath)) return "";
8863
8886
  try {
8864
- const event = JSON.parse(fs36.readFileSync(eventPath, "utf-8"));
8887
+ const event = JSON.parse(fs35.readFileSync(eventPath, "utf-8"));
8865
8888
  return String(event.comment?.body ?? "");
8866
8889
  } catch {
8867
8890
  return "";
@@ -9024,7 +9047,7 @@ var mergeFlow = async (ctx) => {
9024
9047
  };
9025
9048
 
9026
9049
  // src/scripts/mergeReleasePr.ts
9027
- import { execFileSync as execFileSync20 } from "child_process";
9050
+ import { execFileSync as execFileSync18 } from "child_process";
9028
9051
  var API_TIMEOUT_MS7 = 6e4;
9029
9052
  var mergeReleasePr = async (ctx) => {
9030
9053
  const state = ctx.data.taskState;
@@ -9043,7 +9066,7 @@ var mergeReleasePr = async (ctx) => {
9043
9066
  process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
9044
9067
  `);
9045
9068
  try {
9046
- const out = execFileSync20("gh", ["pr", "merge", String(prNumber), "--merge"], {
9069
+ const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
9047
9070
  timeout: API_TIMEOUT_MS7,
9048
9071
  cwd: ctx.cwd,
9049
9072
  stdio: ["ignore", "pipe", "pipe"]
@@ -9382,6 +9405,17 @@ var parseJobStateFromAgentResult = async (ctx, _profile, agentResult, args) => {
9382
9405
  }
9383
9406
  const result = extractNextStateFromText(agentResult.finalText, fenceLabel, prevRev);
9384
9407
  if (result.error) {
9408
+ const cleanFinishNoBlock = result.error.startsWith("missing `") && agentResult.outcome === "completed" && loaded != null;
9409
+ if (cleanFinishNoBlock) {
9410
+ ctx.data.nextJobState = {
9411
+ version: 1,
9412
+ rev: prevRev + 1,
9413
+ cursor: loaded.state.cursor,
9414
+ data: loaded.state.data,
9415
+ done: loaded.state.done
9416
+ };
9417
+ return;
9418
+ }
9385
9419
  ctx.data.nextStateParseError = result.error.startsWith("missing `") ? `agent did not emit a \`${fenceLabel}\` fenced block` : result.error;
9386
9420
  return;
9387
9421
  }
@@ -9502,7 +9536,7 @@ var persistFlowState = async (ctx) => {
9502
9536
  };
9503
9537
 
9504
9538
  // src/scripts/poolServe.ts
9505
- import { spawn as spawn3 } from "child_process";
9539
+ import { spawn as spawn4 } from "child_process";
9506
9540
  import { createServer as createServer2 } from "http";
9507
9541
 
9508
9542
  // src/pool/fly.ts
@@ -9520,8 +9554,8 @@ var FlyClient = class {
9520
9554
  get fetch() {
9521
9555
  return this.opts.fetchImpl ?? fetch;
9522
9556
  }
9523
- async call(path40, init = {}) {
9524
- const res = await this.fetch(`${FLY_API_BASE}${path40}`, {
9557
+ async call(path38, init = {}) {
9558
+ const res = await this.fetch(`${FLY_API_BASE}${path38}`, {
9525
9559
  method: init.method ?? "GET",
9526
9560
  headers: {
9527
9561
  Authorization: `Bearer ${this.opts.token}`,
@@ -9532,7 +9566,7 @@ var FlyClient = class {
9532
9566
  if (res.status === 404 && init.allow404) return null;
9533
9567
  if (!res.ok) {
9534
9568
  const text = await res.text().catch(() => "");
9535
- throw new Error(`Fly API ${res.status} on ${path40}: ${text.slice(0, 200) || res.statusText}`);
9569
+ throw new Error(`Fly API ${res.status} on ${path38}: ${text.slice(0, 200) || res.statusText}`);
9536
9570
  }
9537
9571
  if (res.status === 204) return null;
9538
9572
  const raw = await res.text();
@@ -10072,14 +10106,14 @@ function sendJson2(res, status, body) {
10072
10106
  res.end(JSON.stringify(body));
10073
10107
  }
10074
10108
  function readJsonBody2(req) {
10075
- return new Promise((resolve4, reject) => {
10109
+ return new Promise((resolve5, reject) => {
10076
10110
  const chunks = [];
10077
10111
  req.on("data", (c) => chunks.push(c));
10078
10112
  req.on("end", () => {
10079
10113
  const raw = Buffer.concat(chunks).toString("utf-8");
10080
- if (!raw.trim()) return resolve4({});
10114
+ if (!raw.trim()) return resolve5({});
10081
10115
  try {
10082
- resolve4(JSON.parse(raw));
10116
+ resolve5(JSON.parse(raw));
10083
10117
  } catch (err) {
10084
10118
  reject(err instanceof Error ? err : new Error(String(err)));
10085
10119
  }
@@ -10121,7 +10155,7 @@ function superviseLitellm() {
10121
10155
  let restarts = 0;
10122
10156
  const start = () => {
10123
10157
  log(`starting litellm child (port ${port}, host ${host})`);
10124
- const child = spawn3("litellm", ["--config", config, "--port", port, "--host", host], {
10158
+ const child = spawn4("litellm", ["--config", config, "--port", port, "--host", host], {
10125
10159
  stdio: "inherit"
10126
10160
  });
10127
10161
  child.on("exit", (code) => {
@@ -10226,10 +10260,10 @@ var poolServe = async (ctx) => {
10226
10260
  }
10227
10261
  });
10228
10262
  const apiHost = process.env.POOL_API_HOST ?? "::";
10229
- await new Promise((resolve4) => {
10263
+ await new Promise((resolve5) => {
10230
10264
  server.listen(apiPort, apiHost, () => {
10231
10265
  log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
10232
- resolve4();
10266
+ resolve5();
10233
10267
  });
10234
10268
  });
10235
10269
  const shutdown = (signal) => {
@@ -10431,7 +10465,7 @@ ${body}`;
10431
10465
  }
10432
10466
 
10433
10467
  // src/scripts/recordClassification.ts
10434
- import { execFileSync as execFileSync21 } from "child_process";
10468
+ import { execFileSync as execFileSync19 } from "child_process";
10435
10469
  var API_TIMEOUT_MS8 = 3e4;
10436
10470
  var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
10437
10471
  var recordClassification = async (ctx) => {
@@ -10479,7 +10513,7 @@ function parseClassification(prSummary) {
10479
10513
  }
10480
10514
  function tryAuditComment(issueNumber, body, cwd) {
10481
10515
  try {
10482
- execFileSync21("gh", ["issue", "comment", String(issueNumber), "--body", body], {
10516
+ execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
10483
10517
  cwd,
10484
10518
  timeout: API_TIMEOUT_MS8,
10485
10519
  stdio: ["ignore", "pipe", "pipe"]
@@ -10593,7 +10627,7 @@ var resolveArtifacts = async (ctx, profile) => {
10593
10627
  };
10594
10628
 
10595
10629
  // src/scripts/resolveFlow.ts
10596
- import { execFileSync as execFileSync22 } from "child_process";
10630
+ import { execFileSync as execFileSync20 } from "child_process";
10597
10631
  init_issue();
10598
10632
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
10599
10633
  var resolveFlow = async (ctx) => {
@@ -10687,7 +10721,7 @@ function buildPreferBlock(prefer, baseBranch) {
10687
10721
  }
10688
10722
  function getConflictedFiles(cwd) {
10689
10723
  try {
10690
- const out = execFileSync22("git", ["diff", "--name-only", "--diff-filter=U"], {
10724
+ const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
10691
10725
  encoding: "utf-8",
10692
10726
  cwd,
10693
10727
  env: { ...process.env, HUSKY: "0" }
@@ -10702,7 +10736,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
10702
10736
  let total = 0;
10703
10737
  for (const f of files) {
10704
10738
  try {
10705
- const content = execFileSync22("cat", [f], { encoding: "utf-8", cwd }).toString();
10739
+ const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
10706
10740
  const snippet = `### ${f}
10707
10741
 
10708
10742
  \`\`\`
@@ -10726,12 +10760,12 @@ function tryPostPr3(prNumber, body, cwd) {
10726
10760
  function pushEmptyCommit(branch, cwd) {
10727
10761
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
10728
10762
  try {
10729
- execFileSync22(
10763
+ execFileSync20(
10730
10764
  "git",
10731
10765
  ["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
10732
10766
  { cwd, env, stdio: ["ignore", "pipe", "pipe"] }
10733
10767
  );
10734
- execFileSync22("git", ["push", "-u", "origin", branch], {
10768
+ execFileSync20("git", ["push", "-u", "origin", branch], {
10735
10769
  cwd,
10736
10770
  env,
10737
10771
  stdio: ["ignore", "pipe", "pipe"]
@@ -10742,6 +10776,46 @@ function pushEmptyCommit(branch, cwd) {
10742
10776
  }
10743
10777
  }
10744
10778
 
10779
+ // src/scripts/promoteQaGoal.ts
10780
+ init_issue();
10781
+ var REPORT_JSON_OPEN2 = "<!-- KODY_QA_REPORT_JSON";
10782
+ var promoteQaGoal = async (ctx) => {
10783
+ ctx.skipAgent = true;
10784
+ const issueNum = ctx.args.issue;
10785
+ if (typeof issueNum !== "number" || issueNum <= 0) {
10786
+ ctx.output.exitCode = 2;
10787
+ ctx.output.reason = "qa-goal requires --issue <n>";
10788
+ process.stderr.write("[qa-goal] missing --issue\n");
10789
+ return;
10790
+ }
10791
+ let report;
10792
+ try {
10793
+ const issue = getIssue(issueNum, ctx.cwd);
10794
+ const reportComment = [...issue.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
10795
+ if (!reportComment) {
10796
+ ctx.output.exitCode = 3;
10797
+ ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
10798
+ process.stderr.write(`[qa-goal] ${ctx.output.reason}
10799
+ `);
10800
+ return;
10801
+ }
10802
+ report = reportComment.body;
10803
+ } catch (err) {
10804
+ const msg = err instanceof Error ? err.message : String(err);
10805
+ ctx.output.exitCode = 3;
10806
+ ctx.output.reason = `failed to read issue #${issueNum}: ${msg}`;
10807
+ process.stderr.write(`[qa-goal] ${ctx.output.reason}
10808
+ `);
10809
+ return;
10810
+ }
10811
+ await promoteReportToGoal(
10812
+ ctx,
10813
+ report,
10814
+ ctx.args.scope,
10815
+ ctx.args.goal
10816
+ );
10817
+ };
10818
+
10745
10819
  // src/deployments.ts
10746
10820
  init_issue();
10747
10821
  function findPreviewDeploymentUrl(prNumber, cwd) {
@@ -10822,10 +10896,10 @@ var resolvePreviewUrl = async (ctx) => {
10822
10896
  };
10823
10897
 
10824
10898
  // src/scripts/resolveQaUrl.ts
10825
- import { execFileSync as execFileSync23 } from "child_process";
10899
+ import { execFileSync as execFileSync21 } from "child_process";
10826
10900
  function ghQuery(args, cwd) {
10827
10901
  try {
10828
- const out = execFileSync23("gh", args, {
10902
+ const out = execFileSync21("gh", args, {
10829
10903
  cwd,
10830
10904
  stdio: ["ignore", "pipe", "pipe"],
10831
10905
  encoding: "utf-8",
@@ -10895,7 +10969,7 @@ var resolveQaUrl = async (ctx) => {
10895
10969
  };
10896
10970
 
10897
10971
  // src/scripts/revertFlow.ts
10898
- import { execFileSync as execFileSync24 } from "child_process";
10972
+ import { execFileSync as execFileSync22 } from "child_process";
10899
10973
  init_issue();
10900
10974
  var SHA_RE = /^[0-9a-f]{4,40}$/i;
10901
10975
  var revertFlow = async (ctx) => {
@@ -10978,7 +11052,7 @@ function buildPrSummary(resolved) {
10978
11052
  return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
10979
11053
  }
10980
11054
  function git3(args, cwd) {
10981
- return execFileSync24("git", args, {
11055
+ return execFileSync22("git", args, {
10982
11056
  encoding: "utf-8",
10983
11057
  timeout: 3e4,
10984
11058
  cwd,
@@ -10988,7 +11062,7 @@ function git3(args, cwd) {
10988
11062
  }
10989
11063
  function isAncestorOfHead(sha, cwd) {
10990
11064
  try {
10991
- execFileSync24("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
11065
+ execFileSync22("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
10992
11066
  cwd,
10993
11067
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
10994
11068
  stdio: ["ignore", "ignore", "ignore"]
@@ -11083,9 +11157,9 @@ function resolveBaseOverride(value) {
11083
11157
  }
11084
11158
 
11085
11159
  // src/scripts/runnerServe.ts
11086
- import { spawn as spawn4 } from "child_process";
11160
+ import { spawn as spawn5 } from "child_process";
11087
11161
  import { createServer as createServer3 } from "http";
11088
- import * as fs37 from "fs";
11162
+ import * as fs36 from "fs";
11089
11163
  var DEFAULT_PORT2 = 8080;
11090
11164
  var DEFAULT_WORKDIR = "/workspace/repo";
11091
11165
  function getApiKey2() {
@@ -11107,17 +11181,17 @@ function authOk2(req, expected) {
11107
11181
  return false;
11108
11182
  }
11109
11183
  function readJsonBody3(req) {
11110
- return new Promise((resolve4, reject) => {
11184
+ return new Promise((resolve5, reject) => {
11111
11185
  const chunks = [];
11112
11186
  req.on("data", (c) => chunks.push(c));
11113
11187
  req.on("end", () => {
11114
11188
  const raw = Buffer.concat(chunks).toString("utf-8");
11115
11189
  if (!raw.trim()) {
11116
- resolve4({});
11190
+ resolve5({});
11117
11191
  return;
11118
11192
  }
11119
11193
  try {
11120
- resolve4(JSON.parse(raw));
11194
+ resolve5(JSON.parse(raw));
11121
11195
  } catch (err) {
11122
11196
  reject(err instanceof Error ? err : new Error(String(err)));
11123
11197
  }
@@ -11166,8 +11240,8 @@ async function defaultRunJob(job) {
11166
11240
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
11167
11241
  const branch = job.ref ?? "main";
11168
11242
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
11169
- fs37.rmSync(workdir, { recursive: true, force: true });
11170
- fs37.mkdirSync(workdir, { recursive: true });
11243
+ fs36.rmSync(workdir, { recursive: true, force: true });
11244
+ fs36.mkdirSync(workdir, { recursive: true });
11171
11245
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
11172
11246
  const interactive = job.mode === "interactive";
11173
11247
  const childEnv = {
@@ -11192,13 +11266,13 @@ async function defaultRunJob(job) {
11192
11266
  ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
11193
11267
  ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
11194
11268
  };
11195
- const run = (cmd, args, cwd) => new Promise((resolve4) => {
11196
- const child = spawn4(cmd, args, { stdio: "inherit", env: childEnv, cwd });
11197
- child.on("exit", (code) => resolve4(code ?? 0));
11269
+ const run = (cmd, args, cwd) => new Promise((resolve5) => {
11270
+ const child = spawn5(cmd, args, { stdio: "inherit", env: childEnv, cwd });
11271
+ child.on("exit", (code) => resolve5(code ?? 0));
11198
11272
  child.on("error", (err) => {
11199
11273
  process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
11200
11274
  `);
11201
- resolve4(1);
11275
+ resolve5(1);
11202
11276
  });
11203
11277
  });
11204
11278
  process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
@@ -11285,11 +11359,11 @@ var runnerServe = async (ctx) => {
11285
11359
  const port = Number(process.env.PORT ?? DEFAULT_PORT2);
11286
11360
  const server = buildServer2({ apiKey });
11287
11361
  const host = process.env.RUNNER_HOST ?? "::";
11288
- await new Promise((resolve4) => {
11362
+ await new Promise((resolve5) => {
11289
11363
  server.listen(port, host, () => {
11290
11364
  process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
11291
11365
  `);
11292
- resolve4();
11366
+ resolve5();
11293
11367
  });
11294
11368
  });
11295
11369
  const shutdown = (signal) => {
@@ -11304,9 +11378,9 @@ var runnerServe = async (ctx) => {
11304
11378
  };
11305
11379
 
11306
11380
  // src/scripts/runTickScript.ts
11307
- import { spawnSync } from "child_process";
11308
- import * as fs38 from "fs";
11309
- import * as path36 from "path";
11381
+ import { spawnSync as spawnSync2 } from "child_process";
11382
+ import * as fs37 from "fs";
11383
+ import * as path34 from "path";
11310
11384
  var runTickScript = async (ctx, _profile, args) => {
11311
11385
  ctx.skipAgent = true;
11312
11386
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -11318,13 +11392,13 @@ var runTickScript = async (ctx, _profile, args) => {
11318
11392
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
11319
11393
  return;
11320
11394
  }
11321
- const jobPath = path36.join(ctx.cwd, jobsDir, `${slug}.md`);
11322
- if (!fs38.existsSync(jobPath)) {
11395
+ const jobPath = path34.join(ctx.cwd, jobsDir, `${slug}.md`);
11396
+ if (!fs37.existsSync(jobPath)) {
11323
11397
  ctx.output.exitCode = 99;
11324
11398
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
11325
11399
  return;
11326
11400
  }
11327
- const raw = fs38.readFileSync(jobPath, "utf-8");
11401
+ const raw = fs37.readFileSync(jobPath, "utf-8");
11328
11402
  const { frontmatter } = splitFrontmatter2(raw);
11329
11403
  const tickScript = frontmatter.tickScript;
11330
11404
  if (!tickScript) {
@@ -11332,8 +11406,8 @@ var runTickScript = async (ctx, _profile, args) => {
11332
11406
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
11333
11407
  return;
11334
11408
  }
11335
- const scriptPath = path36.isAbsolute(tickScript) ? tickScript : path36.join(ctx.cwd, tickScript);
11336
- if (!fs38.existsSync(scriptPath)) {
11409
+ const scriptPath = path34.isAbsolute(tickScript) ? tickScript : path34.join(ctx.cwd, tickScript);
11410
+ if (!fs37.existsSync(scriptPath)) {
11337
11411
  ctx.output.exitCode = 99;
11338
11412
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
11339
11413
  return;
@@ -11350,7 +11424,7 @@ var runTickScript = async (ctx, _profile, args) => {
11350
11424
  ctx.data.jobSlug = slug;
11351
11425
  ctx.data.jobState = loaded;
11352
11426
  const childEnv = buildChildEnv(process.env, Boolean(ctx.args.force));
11353
- const result = spawnSync("bash", [scriptPath], {
11427
+ const result = spawnSync2("bash", [scriptPath], {
11354
11428
  cwd: ctx.cwd,
11355
11429
  env: childEnv,
11356
11430
  stdio: ["ignore", "pipe", "pipe"],
@@ -11449,7 +11523,8 @@ var saveGoalState = async (ctx) => {
11449
11523
  lastDispatchedIssue: goal.lastDispatchedIssue,
11450
11524
  updatedAt: changed ? nowIso() : prev?.updatedAt
11451
11525
  };
11452
- writeGoalState(ctx.cwd, goal.id, updated);
11526
+ ctx.data.goalPersistState = updated;
11527
+ ctx.data.goalPersistChanged = changed;
11453
11528
  ctx.skipAgent = true;
11454
11529
  };
11455
11530
 
@@ -11483,7 +11558,7 @@ function synthesizeAction(ctx) {
11483
11558
  }
11484
11559
 
11485
11560
  // src/scripts/serveFlow.ts
11486
- import { spawn as spawn5 } from "child_process";
11561
+ import { spawn as spawn6 } from "child_process";
11487
11562
  function parseTarget(positional) {
11488
11563
  if (!Array.isArray(positional) || positional.length === 0) return "none";
11489
11564
  const first = String(positional[0]).toLowerCase();
@@ -11532,15 +11607,15 @@ var serveFlow = async (ctx) => {
11532
11607
  if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
11533
11608
  `);
11534
11609
  const args = ["--dangerously-skip-permissions", "--model", model.model];
11535
- const child = spawn5("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
11536
- const exitCode = await new Promise((resolve4) => {
11537
- child.on("exit", (code) => resolve4(code ?? 0));
11610
+ const child = spawn6("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
11611
+ const exitCode = await new Promise((resolve5) => {
11612
+ child.on("exit", (code) => resolve5(code ?? 0));
11538
11613
  child.on("error", (err) => {
11539
11614
  process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
11540
11615
  `);
11541
11616
  process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
11542
11617
  `);
11543
- resolve4(1);
11618
+ resolve5(1);
11544
11619
  });
11545
11620
  });
11546
11621
  killProxy();
@@ -11553,7 +11628,7 @@ var serveFlow = async (ctx) => {
11553
11628
  if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
11554
11629
  `);
11555
11630
  try {
11556
- const code = spawn5("code", [ctx.cwd], { stdio: "inherit", env: editorEnv, detached: true });
11631
+ const code = spawn6("code", [ctx.cwd], { stdio: "inherit", env: editorEnv, detached: true });
11557
11632
  code.on("error", (err) => {
11558
11633
  process.stderr.write(`[kody serve] failed to launch VS Code: ${err.message}
11559
11634
  `);
@@ -11630,11 +11705,11 @@ var skipAgent = async (ctx) => {
11630
11705
  };
11631
11706
 
11632
11707
  // src/scripts/stageMergeConflicts.ts
11633
- import { execFileSync as execFileSync25 } from "child_process";
11708
+ import { execFileSync as execFileSync23 } from "child_process";
11634
11709
  var stageMergeConflicts = async (ctx) => {
11635
11710
  if (ctx.data.agentDone === false) return;
11636
11711
  try {
11637
- execFileSync25("git", ["add", "-A"], {
11712
+ execFileSync23("git", ["add", "-A"], {
11638
11713
  cwd: ctx.cwd,
11639
11714
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
11640
11715
  stdio: "pipe"
@@ -11645,7 +11720,7 @@ var stageMergeConflicts = async (ctx) => {
11645
11720
 
11646
11721
  // src/scripts/startFlow.ts
11647
11722
  init_issue();
11648
- import { execFileSync as execFileSync26 } from "child_process";
11723
+ import { execFileSync as execFileSync24 } from "child_process";
11649
11724
  var API_TIMEOUT_MS9 = 3e4;
11650
11725
  var startFlow = async (ctx, profile, _agentResult, args) => {
11651
11726
  const entry = args?.entry;
@@ -11679,7 +11754,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
11679
11754
  const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
11680
11755
  const body = `@kody ${next}`;
11681
11756
  try {
11682
- execFileSync26("gh", [sub, "comment", String(targetNumber), "--body", body], {
11757
+ execFileSync24("gh", [sub, "comment", String(targetNumber), "--body", body], {
11683
11758
  timeout: API_TIMEOUT_MS9,
11684
11759
  cwd,
11685
11760
  stdio: ["ignore", "pipe", "pipe"]
@@ -11693,7 +11768,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
11693
11768
  }
11694
11769
 
11695
11770
  // src/scripts/syncFlow.ts
11696
- import { execFileSync as execFileSync27 } from "child_process";
11771
+ import { execFileSync as execFileSync25 } from "child_process";
11697
11772
  init_issue();
11698
11773
  var syncFlow = async (ctx, _profile, args) => {
11699
11774
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
@@ -11758,7 +11833,7 @@ function bail2(ctx, prNumber, reason) {
11758
11833
  }
11759
11834
  function revParseHead(cwd) {
11760
11835
  try {
11761
- return execFileSync27("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
11836
+ return execFileSync25("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
11762
11837
  } catch {
11763
11838
  return "";
11764
11839
  }
@@ -11808,7 +11883,7 @@ var verify = async (ctx) => {
11808
11883
  };
11809
11884
 
11810
11885
  // src/scripts/verifyReproFails.ts
11811
- import { spawn as spawn6 } from "child_process";
11886
+ import { spawn as spawn7 } from "child_process";
11812
11887
  var TEST_TIMEOUT_MS = 10 * 60 * 1e3;
11813
11888
  var TAIL_CHARS2 = 8e3;
11814
11889
  var ANSI_RE2 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
@@ -11876,8 +11951,8 @@ function stripAnsi2(s) {
11876
11951
  return s.replace(ANSI_RE2, "");
11877
11952
  }
11878
11953
  function runCommand2(command, cwd) {
11879
- return new Promise((resolve4) => {
11880
- const child = spawn6(command, {
11954
+ return new Promise((resolve5) => {
11955
+ const child = spawn7(command, {
11881
11956
  cwd,
11882
11957
  shell: true,
11883
11958
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
@@ -11903,11 +11978,11 @@ function runCommand2(command, cwd) {
11903
11978
  }, TEST_TIMEOUT_MS);
11904
11979
  child.on("exit", (code) => {
11905
11980
  clearTimeout(timer);
11906
- resolve4({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
11981
+ resolve5({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
11907
11982
  });
11908
11983
  child.on("error", (err) => {
11909
11984
  clearTimeout(timer);
11910
- resolve4({ exitCode: -1, output: err.message });
11985
+ resolve5({ exitCode: -1, output: err.message });
11911
11986
  });
11912
11987
  });
11913
11988
  }
@@ -12015,7 +12090,7 @@ var verifyWithRetry = async (ctx) => {
12015
12090
 
12016
12091
  // src/scripts/waitForCi.ts
12017
12092
  init_issue();
12018
- import { execFileSync as execFileSync28 } from "child_process";
12093
+ import { execFileSync as execFileSync26 } from "child_process";
12019
12094
  var API_TIMEOUT_MS10 = 3e4;
12020
12095
  var waitForCi = async (ctx, _profile, _agentResult, args) => {
12021
12096
  const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
@@ -12093,7 +12168,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
12093
12168
  };
12094
12169
  function fetchChecks(prNumber, cwd) {
12095
12170
  try {
12096
- const raw = execFileSync28("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
12171
+ const raw = execFileSync26("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
12097
12172
  encoding: "utf-8",
12098
12173
  timeout: API_TIMEOUT_MS10,
12099
12174
  cwd,
@@ -12206,7 +12281,7 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
12206
12281
  };
12207
12282
 
12208
12283
  // src/scripts/warmupMcp.ts
12209
- import { spawn as spawn7 } from "child_process";
12284
+ import { spawn as spawn8 } from "child_process";
12210
12285
  var PER_SERVER_TIMEOUT_MS = 6e4;
12211
12286
  var PER_REQUEST_TIMEOUT_MS = 2e4;
12212
12287
  var warmupMcp = async (_ctx, profile) => {
@@ -12228,7 +12303,7 @@ var warmupMcp = async (_ctx, profile) => {
12228
12303
  }
12229
12304
  };
12230
12305
  async function warmupOne(command, args, env) {
12231
- const child = spawn7(command, args, {
12306
+ const child = spawn8(command, args, {
12232
12307
  stdio: ["pipe", "pipe", "pipe"],
12233
12308
  env: env ? { ...process.env, ...env } : process.env
12234
12309
  });
@@ -12329,20 +12404,20 @@ function lineStream(stream) {
12329
12404
  tryDeliver();
12330
12405
  });
12331
12406
  return {
12332
- next: (timeoutMs) => new Promise((resolve4) => {
12407
+ next: (timeoutMs) => new Promise((resolve5) => {
12333
12408
  if (queue.length > 0) {
12334
- resolve4(queue.shift());
12409
+ resolve5(queue.shift());
12335
12410
  return;
12336
12411
  }
12337
12412
  if (ended) {
12338
- resolve4(null);
12413
+ resolve5(null);
12339
12414
  return;
12340
12415
  }
12341
- waiter = resolve4;
12416
+ waiter = resolve5;
12342
12417
  const t = setTimeout(() => {
12343
- if (waiter === resolve4) {
12418
+ if (waiter === resolve5) {
12344
12419
  waiter = null;
12345
- resolve4(null);
12420
+ resolve5(null);
12346
12421
  }
12347
12422
  }, Math.max(0, timeoutMs));
12348
12423
  t.unref?.();
@@ -12440,7 +12515,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
12440
12515
  };
12441
12516
 
12442
12517
  // src/scripts/writeRunSummary.ts
12443
- import * as fs39 from "fs";
12518
+ import * as fs38 from "fs";
12444
12519
  var writeRunSummary = async (ctx, profile) => {
12445
12520
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
12446
12521
  if (!summaryPath) return;
@@ -12462,7 +12537,7 @@ var writeRunSummary = async (ctx, profile) => {
12462
12537
  if (reason) lines.push(`- **Reason:** ${reason}`);
12463
12538
  lines.push("");
12464
12539
  try {
12465
- fs39.appendFileSync(summaryPath, `${lines.join("\n")}
12540
+ fs38.appendFileSync(summaryPath, `${lines.join("\n")}
12466
12541
  `);
12467
12542
  } catch {
12468
12543
  }
@@ -12495,6 +12570,7 @@ var preflightScripts = {
12495
12570
  discoverQaContext,
12496
12571
  resolvePreviewUrl,
12497
12572
  resolveQaUrl,
12573
+ promoteQaGoal,
12498
12574
  composePrompt,
12499
12575
  setCommentTarget,
12500
12576
  setLifecycleLabel,
@@ -12565,7 +12641,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
12565
12641
  ]);
12566
12642
 
12567
12643
  // src/tools.ts
12568
- import { execFileSync as execFileSync29 } from "child_process";
12644
+ import { execFileSync as execFileSync27 } from "child_process";
12569
12645
  function verifyCliTools(tools, cwd) {
12570
12646
  const out = [];
12571
12647
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -12598,7 +12674,7 @@ function verifyOne(tool3, cwd) {
12598
12674
  }
12599
12675
  function runShell(cmd, cwd, timeoutMs = 3e4) {
12600
12676
  try {
12601
- execFileSync29("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
12677
+ execFileSync27("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
12602
12678
  return true;
12603
12679
  } catch {
12604
12680
  return false;
@@ -12706,9 +12782,9 @@ async function runExecutable(profileName, input) {
12706
12782
  })
12707
12783
  };
12708
12784
  })() : null;
12709
- const ndjsonDir = path37.join(input.cwd, ".kody");
12785
+ const ndjsonDir = path35.join(input.cwd, ".kody");
12710
12786
  const invokeAgent = async (prompt) => {
12711
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path37.isAbsolute(p) ? p : path37.resolve(profile.dir, p)).filter((p) => p.length > 0);
12787
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path35.isAbsolute(p) ? p : path35.resolve(profile.dir, p)).filter((p) => p.length > 0);
12712
12788
  const syntheticPath = ctx.data.syntheticPluginPath;
12713
12789
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
12714
12790
  const agents = loadSubagents(profile);
@@ -12920,7 +12996,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
12920
12996
  function getProfileInputsForChild(profileName, _cwd) {
12921
12997
  try {
12922
12998
  const profilePath = resolveProfilePath(profileName);
12923
- if (!fs40.existsSync(profilePath)) return null;
12999
+ if (!fs39.existsSync(profilePath)) return null;
12924
13000
  return loadProfile(profilePath).inputs;
12925
13001
  } catch {
12926
13002
  return null;
@@ -12929,17 +13005,17 @@ function getProfileInputsForChild(profileName, _cwd) {
12929
13005
  function resolveProfilePath(profileName) {
12930
13006
  const found = resolveExecutable(profileName);
12931
13007
  if (found) return found;
12932
- const here = path37.dirname(new URL(import.meta.url).pathname);
13008
+ const here = path35.dirname(new URL(import.meta.url).pathname);
12933
13009
  const candidates = [
12934
- path37.join(here, "executables", profileName, "profile.json"),
13010
+ path35.join(here, "executables", profileName, "profile.json"),
12935
13011
  // same-dir sibling (dev)
12936
- path37.join(here, "..", "executables", profileName, "profile.json"),
13012
+ path35.join(here, "..", "executables", profileName, "profile.json"),
12937
13013
  // up one (prod: dist/bin → dist/executables)
12938
- path37.join(here, "..", "src", "executables", profileName, "profile.json")
13014
+ path35.join(here, "..", "src", "executables", profileName, "profile.json")
12939
13015
  // fallback
12940
13016
  ];
12941
13017
  for (const c of candidates) {
12942
- if (fs40.existsSync(c)) return c;
13018
+ if (fs39.existsSync(c)) return c;
12943
13019
  }
12944
13020
  return candidates[0];
12945
13021
  }
@@ -13039,8 +13115,8 @@ function resolveShellTimeoutMs(entry) {
13039
13115
  var SIGKILL_GRACE_MS = 5e3;
13040
13116
  async function runShellEntry(entry, ctx, profile) {
13041
13117
  const shellName = entry.shell;
13042
- const shellPath = path37.join(profile.dir, shellName);
13043
- if (!fs40.existsSync(shellPath)) {
13118
+ const shellPath = path35.join(profile.dir, shellName);
13119
+ if (!fs39.existsSync(shellPath)) {
13044
13120
  ctx.skipAgent = true;
13045
13121
  ctx.output.exitCode = 99;
13046
13122
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -13056,7 +13132,7 @@ async function runShellEntry(entry, ctx, profile) {
13056
13132
  env[`KODY_CFG_${k}`] = v;
13057
13133
  }
13058
13134
  const timeoutMs = resolveShellTimeoutMs(entry);
13059
- const child = spawn8("bash", [shellPath, ...positional], {
13135
+ const child = spawn9("bash", [shellPath, ...positional], {
13060
13136
  cwd: ctx.cwd,
13061
13137
  env,
13062
13138
  stdio: ["pipe", "pipe", "pipe"],
@@ -13078,14 +13154,14 @@ async function runShellEntry(entry, ctx, profile) {
13078
13154
  let killTimer;
13079
13155
  let escalateTimer;
13080
13156
  const result = await new Promise(
13081
- (resolve4) => {
13157
+ (resolve5) => {
13082
13158
  let settled = false;
13083
13159
  const settle = (code, signal, spawnErr) => {
13084
13160
  if (settled) return;
13085
13161
  settled = true;
13086
13162
  if (killTimer) clearTimeout(killTimer);
13087
13163
  if (escalateTimer) clearTimeout(escalateTimer);
13088
- resolve4({ code, signal, spawnErr });
13164
+ resolve5({ code, signal, spawnErr });
13089
13165
  };
13090
13166
  child.on("error", (err) => settle(null, null, err));
13091
13167
  child.on("close", (code, signal) => settle(code, signal));
@@ -13367,7 +13443,7 @@ async function runContainerLoop(profile, ctx, input) {
13367
13443
  }
13368
13444
  function resetWorkingTree2(cwd) {
13369
13445
  try {
13370
- execFileSync30("git", ["reset", "--hard", "HEAD"], {
13446
+ execFileSync28("git", ["reset", "--hard", "HEAD"], {
13371
13447
  cwd,
13372
13448
  stdio: ["ignore", "pipe", "pipe"],
13373
13449
  timeout: 3e4
@@ -13519,14 +13595,14 @@ function resolveAuthToken(env = process.env) {
13519
13595
  return token;
13520
13596
  }
13521
13597
  function detectPackageManager2(cwd) {
13522
- if (fs41.existsSync(path38.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
13523
- if (fs41.existsSync(path38.join(cwd, "yarn.lock"))) return "yarn";
13524
- if (fs41.existsSync(path38.join(cwd, "bun.lockb"))) return "bun";
13598
+ if (fs40.existsSync(path36.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
13599
+ if (fs40.existsSync(path36.join(cwd, "yarn.lock"))) return "yarn";
13600
+ if (fs40.existsSync(path36.join(cwd, "bun.lockb"))) return "bun";
13525
13601
  return "npm";
13526
13602
  }
13527
13603
  function shellOut(cmd, args, cwd, stream = true) {
13528
13604
  try {
13529
- execFileSync31(cmd, args, {
13605
+ execFileSync29(cmd, args, {
13530
13606
  cwd,
13531
13607
  stdio: stream ? "inherit" : "pipe",
13532
13608
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -13539,7 +13615,7 @@ function shellOut(cmd, args, cwd, stream = true) {
13539
13615
  }
13540
13616
  function isOnPath(bin) {
13541
13617
  try {
13542
- execFileSync31("which", [bin], { stdio: "pipe" });
13618
+ execFileSync29("which", [bin], { stdio: "pipe" });
13543
13619
  return true;
13544
13620
  } catch {
13545
13621
  return false;
@@ -13580,7 +13656,7 @@ function installLitellmIfNeeded(cwd) {
13580
13656
  } catch {
13581
13657
  }
13582
13658
  try {
13583
- execFileSync31("python3", ["-c", "import litellm"], { stdio: "pipe" });
13659
+ execFileSync29("python3", ["-c", "import litellm"], { stdio: "pipe" });
13584
13660
  process.stdout.write("\u2192 kody: litellm already installed\n");
13585
13661
  return 0;
13586
13662
  } catch {
@@ -13590,16 +13666,16 @@ function installLitellmIfNeeded(cwd) {
13590
13666
  }
13591
13667
  function configureGitIdentity(cwd) {
13592
13668
  try {
13593
- const name = execFileSync31("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
13669
+ const name = execFileSync29("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
13594
13670
  if (name) return;
13595
13671
  } catch {
13596
13672
  }
13597
13673
  try {
13598
- execFileSync31("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
13674
+ execFileSync29("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
13599
13675
  } catch {
13600
13676
  }
13601
13677
  try {
13602
- execFileSync31("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
13678
+ execFileSync29("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
13603
13679
  cwd,
13604
13680
  stdio: "pipe"
13605
13681
  });
@@ -13608,11 +13684,11 @@ function configureGitIdentity(cwd) {
13608
13684
  }
13609
13685
  function postFailureTail(issueNumber, cwd, reason) {
13610
13686
  if (!issueNumber) return;
13611
- const logPath = path38.join(cwd, ".kody", "last-run.jsonl");
13687
+ const logPath = path36.join(cwd, ".kody", "last-run.jsonl");
13612
13688
  let tail = "";
13613
13689
  try {
13614
- if (fs41.existsSync(logPath)) {
13615
- const content = fs41.readFileSync(logPath, "utf-8");
13690
+ if (fs40.existsSync(logPath)) {
13691
+ const content = fs40.readFileSync(logPath, "utf-8");
13616
13692
  tail = content.slice(-3e3);
13617
13693
  }
13618
13694
  } catch {
@@ -13637,7 +13713,7 @@ async function runCi(argv) {
13637
13713
  return 0;
13638
13714
  }
13639
13715
  const args = parseCiArgs(argv);
13640
- const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
13716
+ const cwd = args.cwd ? path36.resolve(args.cwd) : process.cwd();
13641
13717
  let earlyConfig;
13642
13718
  try {
13643
13719
  earlyConfig = loadConfig(cwd);
@@ -13647,9 +13723,9 @@ async function runCi(argv) {
13647
13723
  const eventName = process.env.GITHUB_EVENT_NAME;
13648
13724
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
13649
13725
  let manualWorkflowDispatch = false;
13650
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs41.existsSync(dispatchEventPath)) {
13726
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs40.existsSync(dispatchEventPath)) {
13651
13727
  try {
13652
- const evt = JSON.parse(fs41.readFileSync(dispatchEventPath, "utf-8"));
13728
+ const evt = JSON.parse(fs40.readFileSync(dispatchEventPath, "utf-8"));
13653
13729
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
13654
13730
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
13655
13731
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -13908,17 +13984,17 @@ function parseChatArgs(argv, env = process.env) {
13908
13984
  return result;
13909
13985
  }
13910
13986
  function commitChatFiles(cwd, sessionId, verbose) {
13911
- const sessionFile = path39.relative(cwd, sessionFilePath(cwd, sessionId));
13912
- const eventsFile = path39.relative(cwd, eventsFilePath(cwd, sessionId));
13987
+ const sessionFile = path37.relative(cwd, sessionFilePath(cwd, sessionId));
13988
+ const eventsFile = path37.relative(cwd, eventsFilePath(cwd, sessionId));
13913
13989
  const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
13914
- const tasksDir = path39.join(".kody", "tasks", safeSession);
13990
+ const tasksDir = path37.join(".kody", "tasks", safeSession);
13915
13991
  const candidatePaths = [sessionFile, eventsFile, tasksDir];
13916
- const paths = candidatePaths.filter((p) => fs42.existsSync(path39.join(cwd, p)));
13992
+ const paths = candidatePaths.filter((p) => fs41.existsSync(path37.join(cwd, p)));
13917
13993
  if (paths.length === 0) return;
13918
13994
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
13919
13995
  try {
13920
- execFileSync32("git", ["add", "-f", ...paths], opts);
13921
- execFileSync32("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
13996
+ execFileSync30("git", ["add", "-f", ...paths], opts);
13997
+ execFileSync30("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
13922
13998
  } catch (err) {
13923
13999
  const msg = err instanceof Error ? err.message : String(err);
13924
14000
  process.stderr.write(`[kody:chat] commit skipped: ${msg}
@@ -13957,7 +14033,7 @@ async function runChat(argv) {
13957
14033
  ${CHAT_HELP}`);
13958
14034
  return 64;
13959
14035
  }
13960
- const cwd = args.cwd ? path39.resolve(args.cwd) : process.cwd();
14036
+ const cwd = args.cwd ? path37.resolve(args.cwd) : process.cwd();
13961
14037
  const sessionId = args.sessionId;
13962
14038
  const unpackedSecrets = unpackAllSecrets();
13963
14039
  if (unpackedSecrets > 0) {
@@ -14009,7 +14085,7 @@ ${CHAT_HELP}`);
14009
14085
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
14010
14086
  const meta = readMeta(sessionFile);
14011
14087
  process.stdout.write(
14012
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs42.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
14088
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs41.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
14013
14089
  `
14014
14090
  );
14015
14091
  try {