@kody-ade/kody-engine 0.4.71 → 0.4.73

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
@@ -864,7 +864,7 @@ var init_loadPriorArt = __esm({
864
864
  // package.json
865
865
  var package_default = {
866
866
  name: "@kody-ade/kody-engine",
867
- version: "0.4.71",
867
+ version: "0.4.73",
868
868
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
869
869
  license: "MIT",
870
870
  type: "module",
@@ -919,8 +919,8 @@ var package_default = {
919
919
 
920
920
  // src/chat-cli.ts
921
921
  import { execFileSync as execFileSync31 } from "child_process";
922
- import * as fs33 from "fs";
923
- import * as path31 from "path";
922
+ import * as fs34 from "fs";
923
+ import * as path32 from "path";
924
924
 
925
925
  // src/chat/events.ts
926
926
  import * as fs from "fs";
@@ -1722,6 +1722,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
1722
1722
  // src/chat/modes/interactive.ts
1723
1723
  import { execFileSync as execFileSync2 } from "child_process";
1724
1724
  import * as fs6 from "fs";
1725
+ import * as os from "os";
1725
1726
  import * as path6 from "path";
1726
1727
 
1727
1728
  // src/chat/inbox.ts
@@ -1883,6 +1884,45 @@ function commitTurn(cwd, sessionId, verbose) {
1883
1884
  const eventsRel = path6.relative(cwd, eventsFilePath(cwd, sessionId));
1884
1885
  const paths = [sessionRel, eventsRel].filter((p) => fs6.existsSync(path6.join(cwd, p)));
1885
1886
  if (paths.length === 0) return;
1887
+ const startBranch = currentBranch2(cwd);
1888
+ const eventsBranch = defaultBranch(cwd) ?? "main";
1889
+ if (startBranch === eventsBranch) {
1890
+ commitPathsAndPush(cwd, paths, sessionId, verbose, "HEAD");
1891
+ return;
1892
+ }
1893
+ const stdio = verbose ? "inherit" : "pipe";
1894
+ const exec = (args) => execFileSync2("git", args, { cwd, stdio });
1895
+ const worktreeDir = fs6.mkdtempSync(path6.join(os.tmpdir(), "kody-events-"));
1896
+ let worktreeAdded = false;
1897
+ try {
1898
+ exec(["fetch", "--quiet", "origin", eventsBranch]);
1899
+ exec(["worktree", "add", "--detach", "--quiet", worktreeDir, `origin/${eventsBranch}`]);
1900
+ worktreeAdded = true;
1901
+ for (const rel of paths) {
1902
+ const src = path6.join(cwd, rel);
1903
+ const dst = path6.join(worktreeDir, rel);
1904
+ fs6.mkdirSync(path6.dirname(dst), { recursive: true });
1905
+ fs6.copyFileSync(src, dst);
1906
+ }
1907
+ commitPathsAndPush(worktreeDir, paths, sessionId, verbose, `HEAD:${eventsBranch}`);
1908
+ } catch (err) {
1909
+ const msg = err instanceof Error ? err.message : String(err);
1910
+ process.stderr.write(`[kody:chat:interactive] worktree commit failed: ${msg}
1911
+ `);
1912
+ } finally {
1913
+ if (worktreeAdded) {
1914
+ try {
1915
+ exec(["worktree", "remove", "--force", "--quiet", worktreeDir]);
1916
+ } catch {
1917
+ }
1918
+ }
1919
+ try {
1920
+ fs6.rmSync(worktreeDir, { recursive: true, force: true });
1921
+ } catch {
1922
+ }
1923
+ }
1924
+ }
1925
+ function commitPathsAndPush(cwd, paths, sessionId, verbose, pushSpec) {
1886
1926
  const stdio = verbose ? "inherit" : "pipe";
1887
1927
  const exec = (args) => execFileSync2("git", args, { cwd, stdio });
1888
1928
  try {
@@ -1896,7 +1936,7 @@ function commitTurn(cwd, sessionId, verbose) {
1896
1936
  }
1897
1937
  for (let attempt = 1; attempt <= 3; attempt++) {
1898
1938
  try {
1899
- exec(["push", "--quiet", "origin", "HEAD"]);
1939
+ exec(["push", "--quiet", "origin", pushSpec]);
1900
1940
  return;
1901
1941
  } catch (err) {
1902
1942
  const msg = err instanceof Error ? err.message : String(err);
@@ -1910,14 +1950,16 @@ function commitTurn(cwd, sessionId, verbose) {
1910
1950
  `);
1911
1951
  try {
1912
1952
  exec(["fetch", "--quiet", "origin"]);
1913
- const branch = currentBranch2(cwd);
1914
- if (branch) {
1915
- exec(["rebase", "--quiet", `origin/${branch}`]);
1916
- } else {
1917
- process.stderr.write(`[kody:chat:interactive] cannot rebase: no current branch
1953
+ const upstream = pushSpec.includes(":") ? `origin/${pushSpec.split(":")[1]}` : (() => {
1954
+ const branch = currentBranch2(cwd);
1955
+ return branch ? `origin/${branch}` : null;
1956
+ })();
1957
+ if (!upstream) {
1958
+ process.stderr.write(`[kody:chat:interactive] cannot rebase: no upstream resolved
1918
1959
  `);
1919
1960
  return;
1920
1961
  }
1962
+ exec(["rebase", "--quiet", upstream]);
1921
1963
  } catch (rebaseErr) {
1922
1964
  const rmsg = rebaseErr instanceof Error ? rebaseErr.message : String(rebaseErr);
1923
1965
  process.stderr.write(`[kody:chat:interactive] rebase failed: ${rmsg}
@@ -1927,6 +1969,20 @@ function commitTurn(cwd, sessionId, verbose) {
1927
1969
  }
1928
1970
  }
1929
1971
  }
1972
+ function defaultBranch(cwd) {
1973
+ try {
1974
+ const out = execFileSync2(
1975
+ "git",
1976
+ ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
1977
+ { cwd, stdio: ["ignore", "pipe", "ignore"] }
1978
+ );
1979
+ const symbolic = out.toString("utf-8").trim();
1980
+ if (symbolic.startsWith("origin/")) return symbolic.slice("origin/".length);
1981
+ return symbolic || null;
1982
+ } catch {
1983
+ return null;
1984
+ }
1985
+ }
1930
1986
  function currentBranch2(cwd) {
1931
1987
  try {
1932
1988
  const out = execFileSync2("git", ["symbolic-ref", "--short", "HEAD"], {
@@ -1957,8 +2013,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
1957
2013
 
1958
2014
  // src/kody-cli.ts
1959
2015
  import { execFileSync as execFileSync30 } from "child_process";
1960
- import * as fs32 from "fs";
1961
- import * as path30 from "path";
2016
+ import * as fs33 from "fs";
2017
+ import * as path31 from "path";
1962
2018
 
1963
2019
  // src/dispatch.ts
1964
2020
  import * as fs8 from "fs";
@@ -2399,8 +2455,8 @@ init_issue();
2399
2455
 
2400
2456
  // src/executor.ts
2401
2457
  import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
2402
- import * as fs31 from "fs";
2403
- import * as path29 from "path";
2458
+ import * as fs32 from "fs";
2459
+ import * as path30 from "path";
2404
2460
  init_events();
2405
2461
 
2406
2462
  // src/lifecycleLabels.ts
@@ -3022,7 +3078,7 @@ function errMsg(err) {
3022
3078
  // src/litellm.ts
3023
3079
  import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
3024
3080
  import * as fs10 from "fs";
3025
- import * as os from "os";
3081
+ import * as os2 from "os";
3026
3082
  import * as path9 from "path";
3027
3083
  async function checkLitellmHealth(url) {
3028
3084
  try {
@@ -3070,13 +3126,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3070
3126
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
3071
3127
  }
3072
3128
  }
3073
- const configPath = path9.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3129
+ const configPath = path9.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3074
3130
  fs10.writeFileSync(configPath, generateLitellmConfigYaml(model));
3075
3131
  const portMatch = url.match(/:(\d+)/);
3076
3132
  const port = portMatch ? portMatch[1] : "4000";
3077
3133
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
3078
3134
  const dotenvVars = readDotenvApiKeys(projectDir);
3079
- const logPath = path9.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
3135
+ const logPath = path9.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3080
3136
  const outFd = fs10.openSync(logPath, "w");
3081
3137
  const child = spawn2(cmd, args, {
3082
3138
  stdio: ["ignore", outFd, outFd],
@@ -3306,13 +3362,13 @@ function commitAndPush(branch, agentMessage, cwd) {
3306
3362
  }
3307
3363
  }
3308
3364
  }
3309
- function hasCommitsAhead(branch, defaultBranch, cwd) {
3365
+ function hasCommitsAhead(branch, defaultBranch2, cwd) {
3310
3366
  try {
3311
- const out = git(["rev-list", "--count", `origin/${defaultBranch}..${branch}`], cwd);
3367
+ const out = git(["rev-list", "--count", `origin/${defaultBranch2}..${branch}`], cwd);
3312
3368
  return parseInt(out, 10) > 0;
3313
3369
  } catch {
3314
3370
  try {
3315
- const out = git(["rev-list", "--count", `${defaultBranch}..${branch}`], cwd);
3371
+ const out = git(["rev-list", "--count", `${defaultBranch2}..${branch}`], cwd);
3316
3372
  return parseInt(out, 10) > 0;
3317
3373
  } catch {
3318
3374
  return false;
@@ -3578,7 +3634,7 @@ var advanceFlow = async (ctx, profile) => {
3578
3634
 
3579
3635
  // src/scripts/buildSyntheticPlugin.ts
3580
3636
  import * as fs12 from "fs";
3581
- import * as os2 from "os";
3637
+ import * as os3 from "os";
3582
3638
  import * as path11 from "path";
3583
3639
  function getPluginsCatalogRoot() {
3584
3640
  const here = path11.dirname(new URL(import.meta.url).pathname);
@@ -3601,7 +3657,7 @@ var buildSyntheticPlugin = async (ctx, profile) => {
3601
3657
  if (!needsSynthetic) return;
3602
3658
  const catalog = getPluginsCatalogRoot();
3603
3659
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3604
- const root = path11.join(os2.tmpdir(), `kody-synth-${runId}`);
3660
+ const root = path11.join(os3.tmpdir(), `kody-synth-${runId}`);
3605
3661
  fs12.mkdirSync(path11.join(root, ".claude-plugin"), { recursive: true });
3606
3662
  const resolvePart = (bucket, entry) => {
3607
3663
  const local = path11.join(profile.dir, bucket, entry);
@@ -4810,10 +4866,10 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
4810
4866
  // src/scripts/diagMcp.ts
4811
4867
  import { execFileSync as execFileSync11 } from "child_process";
4812
4868
  import * as fs17 from "fs";
4813
- import * as os3 from "os";
4869
+ import * as os4 from "os";
4814
4870
  import * as path17 from "path";
4815
4871
  var diagMcp = async (_ctx) => {
4816
- const home = os3.homedir();
4872
+ const home = os4.homedir();
4817
4873
  const cacheDir = path17.join(home, ".cache", "ms-playwright");
4818
4874
  let entries = [];
4819
4875
  try {
@@ -6474,14 +6530,6 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
6474
6530
 
6475
6531
  // src/branch.ts
6476
6532
  import { execFileSync as execFileSync15 } from "child_process";
6477
- var UncommittedChangesError = class extends Error {
6478
- constructor(branch) {
6479
- super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
6480
- this.branch = branch;
6481
- this.name = "UncommittedChangesError";
6482
- }
6483
- branch;
6484
- };
6485
6533
  function git2(args, cwd) {
6486
6534
  return execFileSync15("git", args, {
6487
6535
  encoding: "utf-8",
@@ -6498,8 +6546,15 @@ function deriveBranchName(issueNumber, title) {
6498
6546
  function getCurrentBranch(cwd) {
6499
6547
  return git2(["branch", "--show-current"], cwd);
6500
6548
  }
6501
- function hasUncommittedChanges(cwd) {
6502
- return git2(["status", "--porcelain", "--untracked-files=no"], cwd).length > 0;
6549
+ function resetWorkingTree(cwd) {
6550
+ try {
6551
+ execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
6552
+ } catch {
6553
+ }
6554
+ try {
6555
+ execFileSync15("git", ["clean", "-fd"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
6556
+ } catch {
6557
+ }
6503
6558
  }
6504
6559
  function checkoutPrBranch(prNumber, cwd) {
6505
6560
  const env = {
@@ -6546,14 +6601,13 @@ function mergeBase(baseBranch, cwd) {
6546
6601
  return "error";
6547
6602
  }
6548
6603
  }
6549
- function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch) {
6604
+ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch) {
6550
6605
  const branchName = deriveBranchName(issueNumber, title);
6606
+ resetWorkingTree(cwd);
6551
6607
  const current = getCurrentBranch(cwd);
6552
6608
  if (current === branchName) {
6553
- if (hasUncommittedChanges(cwd)) throw new UncommittedChangesError(branchName);
6554
6609
  return { branch: branchName, created: false };
6555
6610
  }
6556
- if (hasUncommittedChanges(cwd)) throw new UncommittedChangesError(current || "(detached)");
6557
6611
  try {
6558
6612
  git2(["fetch", "origin"], cwd);
6559
6613
  } catch {
@@ -6564,7 +6618,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
6564
6618
  originBranchExists = true;
6565
6619
  } catch {
6566
6620
  }
6567
- if (originBranchExists && baseBranch && baseBranch !== defaultBranch) {
6621
+ if (originBranchExists && baseBranch && baseBranch !== defaultBranch2) {
6568
6622
  let baseExists = false;
6569
6623
  try {
6570
6624
  git2(["rev-parse", "--verify", `origin/${baseBranch}`], cwd);
@@ -6605,6 +6659,19 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
6605
6659
  git2(["pull", "origin", branchName], cwd);
6606
6660
  } catch {
6607
6661
  }
6662
+ if (!baseBranch || baseBranch === defaultBranch2) {
6663
+ try {
6664
+ git2(["merge", "--no-edit", `origin/${defaultBranch2}`], cwd);
6665
+ } catch {
6666
+ try {
6667
+ git2(["merge", "--abort"], cwd);
6668
+ } catch {
6669
+ }
6670
+ throw new Error(
6671
+ `Branch '${branchName}' has merge conflicts with 'origin/${defaultBranch2}'. Resolve manually or delete the branch to start fresh.`
6672
+ );
6673
+ }
6674
+ }
6608
6675
  return { branch: branchName, created: false };
6609
6676
  }
6610
6677
  try {
@@ -6613,8 +6680,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
6613
6680
  return { branch: branchName, created: false };
6614
6681
  } catch {
6615
6682
  }
6616
- let forkPoint = defaultBranch;
6617
- if (baseBranch && baseBranch !== defaultBranch) {
6683
+ let forkPoint = defaultBranch2;
6684
+ if (baseBranch && baseBranch !== defaultBranch2) {
6618
6685
  try {
6619
6686
  git2(["rev-parse", "--verify", `origin/${baseBranch}`], cwd);
6620
6687
  forkPoint = baseBranch;
@@ -6986,11 +7053,11 @@ function detectOwnerRepo(cwd) {
6986
7053
  if (!m) return null;
6987
7054
  return { owner: m[1], repo: m[2] };
6988
7055
  }
6989
- function makeConfig(pm, ownerRepo, defaultBranch) {
7056
+ function makeConfig(pm, ownerRepo, defaultBranch2) {
6990
7057
  return {
6991
7058
  $schema: "https://raw.githubusercontent.com/aharonyaircohen/kody-engine/main/kody.config.schema.json",
6992
7059
  quality: qualityCommandsFor(pm),
6993
- git: { defaultBranch },
7060
+ git: { defaultBranch: defaultBranch2 },
6994
7061
  github: {
6995
7062
  owner: ownerRepo?.owner ?? "OWNER",
6996
7063
  repo: ownerRepo?.repo ?? "REPO"
@@ -7082,12 +7149,12 @@ function performInit(cwd, force) {
7082
7149
  const skipped = [];
7083
7150
  const pm = detectPackageManager(cwd);
7084
7151
  const ownerRepo = detectOwnerRepo(cwd);
7085
- const defaultBranch = defaultBranchFromGit(cwd);
7152
+ const defaultBranch2 = defaultBranchFromGit(cwd);
7086
7153
  const configPath = path23.join(cwd, "kody.config.json");
7087
7154
  if (fs24.existsSync(configPath) && !force) {
7088
7155
  skipped.push("kody.config.json");
7089
7156
  } else {
7090
- const cfg = makeConfig(pm, ownerRepo, defaultBranch);
7157
+ const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
7091
7158
  fs24.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7092
7159
  `);
7093
7160
  wrote.push("kody.config.json");
@@ -7223,10 +7290,10 @@ import * as fs25 from "fs";
7223
7290
  import * as path24 from "path";
7224
7291
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
7225
7292
  var GoalStateError = class extends Error {
7226
- constructor(path32, message) {
7227
- super(`Invalid goal state at ${path32}:
7293
+ constructor(path33, message) {
7294
+ super(`Invalid goal state at ${path33}:
7228
7295
  ${message}`);
7229
- this.path = path32;
7296
+ this.path = path33;
7230
7297
  this.name = "GoalStateError";
7231
7298
  }
7232
7299
  path;
@@ -8797,19 +8864,8 @@ var runFlow = async (ctx) => {
8797
8864
  process.stderr.write(`[kody runFlow] resolved base branch: ${base} (from --base)
8798
8865
  `);
8799
8866
  }
8800
- try {
8801
- const branchInfo = ensureFeatureBranch(issueNumber, issue.title, ctx.config.git.defaultBranch, ctx.cwd, base ?? void 0);
8802
- ctx.data.branch = branchInfo.branch;
8803
- } catch (err) {
8804
- if (err instanceof UncommittedChangesError) {
8805
- ctx.output.exitCode = 5;
8806
- ctx.output.reason = err.message;
8807
- ctx.skipAgent = true;
8808
- tryPost(issueNumber, `\u26A0\uFE0F kody refused to start: ${err.message}`, ctx.cwd);
8809
- return;
8810
- }
8811
- throw err;
8812
- }
8867
+ const branchInfo = ensureFeatureBranch(issueNumber, issue.title, ctx.config.git.defaultBranch, ctx.cwd, base ?? void 0);
8868
+ ctx.data.branch = branchInfo.branch;
8813
8869
  const runUrl = getRunUrl();
8814
8870
  const startMsg = runUrl ? `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\`, run ${runUrl}` : `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\``;
8815
8871
  tryPost(issueNumber, startMsg, ctx.cwd);
@@ -8959,6 +9015,240 @@ function buildChildEnv(parent, force) {
8959
9015
  return out;
8960
9016
  }
8961
9017
 
9018
+ // src/scripts/brainServe.ts
9019
+ import { createServer } from "http";
9020
+ import * as fs30 from "fs";
9021
+ import * as path29 from "path";
9022
+ var DEFAULT_PORT = 8080;
9023
+ function getApiKey() {
9024
+ const key = (process.env.BRAIN_API_KEY ?? "").trim();
9025
+ if (!key) {
9026
+ throw new Error(
9027
+ "BRAIN_API_KEY env var is required \u2014 set it on the Fly machine before boot."
9028
+ );
9029
+ }
9030
+ return key;
9031
+ }
9032
+ function authOk(req, expected) {
9033
+ const xApiKey = req.headers["x-api-key"]?.trim();
9034
+ if (xApiKey && xApiKey === expected) return true;
9035
+ const auth = req.headers["authorization"]?.trim();
9036
+ if (auth && auth.toLowerCase().startsWith("bearer ")) {
9037
+ return auth.slice(7).trim() === expected;
9038
+ }
9039
+ return false;
9040
+ }
9041
+ function readJsonBody(req) {
9042
+ return new Promise((resolve4, reject) => {
9043
+ const chunks = [];
9044
+ req.on("data", (c) => chunks.push(c));
9045
+ req.on("end", () => {
9046
+ const raw = Buffer.concat(chunks).toString("utf-8");
9047
+ if (!raw.trim()) {
9048
+ resolve4({});
9049
+ return;
9050
+ }
9051
+ try {
9052
+ resolve4(JSON.parse(raw));
9053
+ } catch (err) {
9054
+ reject(err instanceof Error ? err : new Error(String(err)));
9055
+ }
9056
+ });
9057
+ req.on("error", reject);
9058
+ });
9059
+ }
9060
+ function sendJson(res, status, body) {
9061
+ res.writeHead(status, { "content-type": "application/json" });
9062
+ res.end(JSON.stringify(body));
9063
+ }
9064
+ function writeSseHeaders(res) {
9065
+ res.writeHead(200, {
9066
+ "content-type": "text/event-stream; charset=utf-8",
9067
+ "cache-control": "no-cache, no-transform",
9068
+ connection: "keep-alive",
9069
+ "x-accel-buffering": "no"
9070
+ });
9071
+ }
9072
+ function emitSse(res, event) {
9073
+ res.write(`data: ${JSON.stringify(event)}
9074
+
9075
+ `);
9076
+ }
9077
+ var BrainSseSink = class {
9078
+ constructor(res, chatId) {
9079
+ this.res = res;
9080
+ this.chatId = chatId;
9081
+ }
9082
+ res;
9083
+ chatId;
9084
+ async emit(event) {
9085
+ switch (event.event) {
9086
+ case "chat.message": {
9087
+ const content = String(event.payload.content ?? "");
9088
+ if (content.length > 0) {
9089
+ emitSse(this.res, { type: "text", text: content, chatId: this.chatId });
9090
+ }
9091
+ return;
9092
+ }
9093
+ case "chat.tool": {
9094
+ if (event.payload.phase !== "use") return;
9095
+ emitSse(this.res, {
9096
+ type: "tool_use",
9097
+ name: typeof event.payload.name === "string" ? event.payload.name : "tool",
9098
+ input: event.payload.input ?? {},
9099
+ chatId: this.chatId
9100
+ });
9101
+ return;
9102
+ }
9103
+ case "chat.done": {
9104
+ emitSse(this.res, { type: "done", chatId: this.chatId });
9105
+ return;
9106
+ }
9107
+ case "chat.error": {
9108
+ const errMsg2 = typeof event.payload.error === "string" ? event.payload.error : "agent error";
9109
+ emitSse(this.res, { type: "error", error: errMsg2, chatId: this.chatId });
9110
+ return;
9111
+ }
9112
+ // chat.thinking / chat.ready / chat.exit — not part of the Brain protocol.
9113
+ default:
9114
+ return;
9115
+ }
9116
+ }
9117
+ };
9118
+ async function handleChatTurn(req, res, chatId, opts) {
9119
+ let body;
9120
+ try {
9121
+ body = await readJsonBody(req);
9122
+ } catch {
9123
+ sendJson(res, 400, { error: "invalid JSON body" });
9124
+ return;
9125
+ }
9126
+ const message = typeof body === "object" && body !== null && "message" in body ? body.message : void 0;
9127
+ if (typeof message !== "string" || !message.trim()) {
9128
+ sendJson(res, 400, { error: "message required" });
9129
+ return;
9130
+ }
9131
+ const sessionFile = sessionFilePath(opts.cwd, chatId);
9132
+ fs30.mkdirSync(path29.dirname(sessionFile), { recursive: true });
9133
+ appendTurn(sessionFile, {
9134
+ role: "user",
9135
+ content: message,
9136
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9137
+ });
9138
+ writeSseHeaders(res);
9139
+ emitSse(res, { type: "chat", chatId });
9140
+ const sink = new BrainSseSink(res, chatId);
9141
+ try {
9142
+ await opts.runTurn({
9143
+ sessionId: chatId,
9144
+ sessionFile,
9145
+ cwd: opts.cwd,
9146
+ model: opts.model,
9147
+ litellmUrl: opts.litellmUrl,
9148
+ sink
9149
+ });
9150
+ } catch (err) {
9151
+ const errMsg2 = err instanceof Error ? err.message : String(err);
9152
+ process.stderr.write(`[brain-serve] chat turn failed: ${errMsg2}
9153
+ `);
9154
+ try {
9155
+ emitSse(res, { type: "error", error: errMsg2, chatId });
9156
+ } catch {
9157
+ }
9158
+ } finally {
9159
+ try {
9160
+ res.end();
9161
+ } catch {
9162
+ }
9163
+ }
9164
+ }
9165
+ function buildServer(opts) {
9166
+ const runTurn = opts.runTurn ?? runChatTurn;
9167
+ return createServer(async (req, res) => {
9168
+ if (!req.method || !req.url) {
9169
+ sendJson(res, 400, { error: "bad request" });
9170
+ return;
9171
+ }
9172
+ const url = new URL(req.url, `http://localhost`);
9173
+ if (req.method === "GET" && url.pathname === "/healthz") {
9174
+ sendJson(res, 200, { ok: true });
9175
+ return;
9176
+ }
9177
+ if (!authOk(req, opts.apiKey)) {
9178
+ sendJson(res, 401, { error: "unauthorized" });
9179
+ return;
9180
+ }
9181
+ const m = url.pathname.match(/^\/chats\/([^/]+)\/messages\/?$/);
9182
+ if (req.method === "POST" && m) {
9183
+ const chatId = decodeURIComponent(m[1] ?? "");
9184
+ if (!chatId) {
9185
+ sendJson(res, 400, { error: "chatId required" });
9186
+ return;
9187
+ }
9188
+ await handleChatTurn(req, res, chatId, {
9189
+ cwd: opts.cwd,
9190
+ model: opts.model,
9191
+ litellmUrl: opts.litellmUrl,
9192
+ runTurn
9193
+ });
9194
+ return;
9195
+ }
9196
+ sendJson(res, 404, { error: "not found" });
9197
+ });
9198
+ }
9199
+ var brainServe = async (ctx) => {
9200
+ ctx.skipAgent = true;
9201
+ const apiKey = getApiKey();
9202
+ const port = Number(process.env.PORT ?? DEFAULT_PORT);
9203
+ const model = parseProviderModel(ctx.config.agent.model);
9204
+ const usesProxy = needsLitellmProxy(model);
9205
+ let handle = null;
9206
+ if (usesProxy) {
9207
+ process.stdout.write(
9208
+ `[brain-serve] starting LiteLLM proxy for ${model.provider}/${model.model}...
9209
+ `
9210
+ );
9211
+ handle = await startLitellmIfNeeded(model, ctx.cwd);
9212
+ process.stdout.write(
9213
+ `[brain-serve] LiteLLM ready at ${handle?.url ?? LITELLM_DEFAULT_URL}
9214
+ `
9215
+ );
9216
+ }
9217
+ const litellmUrl = usesProxy ? handle?.url ?? LITELLM_DEFAULT_URL : null;
9218
+ const server = buildServer({
9219
+ apiKey,
9220
+ cwd: ctx.cwd,
9221
+ model,
9222
+ litellmUrl
9223
+ });
9224
+ await new Promise((resolve4) => {
9225
+ server.listen(port, "0.0.0.0", () => {
9226
+ process.stdout.write(
9227
+ `[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
9228
+ `
9229
+ );
9230
+ resolve4();
9231
+ });
9232
+ });
9233
+ const shutdown = (signal) => {
9234
+ process.stdout.write(`[brain-serve] ${signal} \u2014 shutting down
9235
+ `);
9236
+ server.close(() => {
9237
+ if (handle) {
9238
+ try {
9239
+ handle.kill();
9240
+ } catch {
9241
+ }
9242
+ }
9243
+ process.exit(0);
9244
+ });
9245
+ };
9246
+ process.once("SIGINT", () => shutdown("SIGINT"));
9247
+ process.once("SIGTERM", () => shutdown("SIGTERM"));
9248
+ await new Promise(() => {
9249
+ });
9250
+ };
9251
+
8962
9252
  // src/scripts/serveFlow.ts
8963
9253
  import { spawn as spawn3 } from "child_process";
8964
9254
  function parseTarget(positional) {
@@ -9798,7 +10088,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
9798
10088
  };
9799
10089
 
9800
10090
  // src/scripts/writeRunSummary.ts
9801
- import * as fs30 from "fs";
10091
+ import * as fs31 from "fs";
9802
10092
  var writeRunSummary = async (ctx, profile) => {
9803
10093
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
9804
10094
  if (!summaryPath) return;
@@ -9820,7 +10110,7 @@ var writeRunSummary = async (ctx, profile) => {
9820
10110
  if (reason) lines.push(`- **Reason:** ${reason}`);
9821
10111
  lines.push("");
9822
10112
  try {
9823
- fs30.appendFileSync(summaryPath, `${lines.join("\n")}
10113
+ fs31.appendFileSync(summaryPath, `${lines.join("\n")}
9824
10114
  `);
9825
10115
  } catch {
9826
10116
  }
@@ -9862,6 +10152,7 @@ var preflightScripts = {
9862
10152
  dispatchJobFileTicks,
9863
10153
  runTickScript,
9864
10154
  serveFlow,
10155
+ brainServe,
9865
10156
  loadGoalState,
9866
10157
  handleAbandonedGoal,
9867
10158
  deriveGoalPhase,
@@ -10042,9 +10333,9 @@ async function runExecutable(profileName, input) {
10042
10333
  data: { ...input.preloadedData ?? {} },
10043
10334
  output: { exitCode: 0 }
10044
10335
  };
10045
- const ndjsonDir = path29.join(input.cwd, ".kody");
10336
+ const ndjsonDir = path30.join(input.cwd, ".kody");
10046
10337
  const invokeAgent = async (prompt) => {
10047
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path29.isAbsolute(p) ? p : path29.resolve(profile.dir, p)).filter((p) => p.length > 0);
10338
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path30.isAbsolute(p) ? p : path30.resolve(profile.dir, p)).filter((p) => p.length > 0);
10048
10339
  const syntheticPath = ctx.data.syntheticPluginPath;
10049
10340
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
10050
10341
  return runAgent({
@@ -10239,7 +10530,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
10239
10530
  function getProfileInputsForChild(profileName, _cwd) {
10240
10531
  try {
10241
10532
  const profilePath = resolveProfilePath(profileName);
10242
- if (!fs31.existsSync(profilePath)) return null;
10533
+ if (!fs32.existsSync(profilePath)) return null;
10243
10534
  return loadProfile(profilePath).inputs;
10244
10535
  } catch {
10245
10536
  return null;
@@ -10248,17 +10539,17 @@ function getProfileInputsForChild(profileName, _cwd) {
10248
10539
  function resolveProfilePath(profileName) {
10249
10540
  const found = resolveExecutable(profileName);
10250
10541
  if (found) return found;
10251
- const here = path29.dirname(new URL(import.meta.url).pathname);
10542
+ const here = path30.dirname(new URL(import.meta.url).pathname);
10252
10543
  const candidates = [
10253
- path29.join(here, "executables", profileName, "profile.json"),
10544
+ path30.join(here, "executables", profileName, "profile.json"),
10254
10545
  // same-dir sibling (dev)
10255
- path29.join(here, "..", "executables", profileName, "profile.json"),
10546
+ path30.join(here, "..", "executables", profileName, "profile.json"),
10256
10547
  // up one (prod: dist/bin → dist/executables)
10257
- path29.join(here, "..", "src", "executables", profileName, "profile.json")
10548
+ path30.join(here, "..", "src", "executables", profileName, "profile.json")
10258
10549
  // fallback
10259
10550
  ];
10260
10551
  for (const c of candidates) {
10261
- if (fs31.existsSync(c)) return c;
10552
+ if (fs32.existsSync(c)) return c;
10262
10553
  }
10263
10554
  return candidates[0];
10264
10555
  }
@@ -10358,8 +10649,8 @@ function resolveShellTimeoutMs(entry) {
10358
10649
  var SIGKILL_GRACE_MS = 5e3;
10359
10650
  async function runShellEntry(entry, ctx, profile) {
10360
10651
  const shellName = entry.shell;
10361
- const shellPath = path29.join(profile.dir, shellName);
10362
- if (!fs31.existsSync(shellPath)) {
10652
+ const shellPath = path30.join(profile.dir, shellName);
10653
+ if (!fs32.existsSync(shellPath)) {
10363
10654
  ctx.skipAgent = true;
10364
10655
  ctx.output.exitCode = 99;
10365
10656
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -10521,7 +10812,7 @@ async function runContainerLoop(profile, ctx, input) {
10521
10812
  process.stderr.write(`[kody container] step ${iteration}: invoking ${child.exec}
10522
10813
  `);
10523
10814
  if (profile.resetBetweenChildren !== false) {
10524
- resetWorkingTree(input.cwd);
10815
+ resetWorkingTree2(input.cwd);
10525
10816
  } else {
10526
10817
  process.stderr.write(`[kody container] resetBetweenChildren=false; preserving tracked tree
10527
10818
  `);
@@ -10684,7 +10975,7 @@ async function runContainerLoop(profile, ctx, input) {
10684
10975
  currentIdx = nextIdx;
10685
10976
  }
10686
10977
  }
10687
- function resetWorkingTree(cwd) {
10978
+ function resetWorkingTree2(cwd) {
10688
10979
  try {
10689
10980
  execFileSync29("git", ["reset", "--hard", "HEAD"], {
10690
10981
  cwd,
@@ -10780,7 +11071,6 @@ Exit codes (inherited from kody run):
10780
11071
  2 verify failed (no PR opened \u2014 branch pushed for inspection)
10781
11072
  3 no commits to ship
10782
11073
  4 PR creation failed
10783
- 5 uncommitted changes on target branch
10784
11074
  99 wrapper crashed
10785
11075
  `;
10786
11076
  function parseCiArgs(argv) {
@@ -10839,9 +11129,9 @@ function resolveAuthToken(env = process.env) {
10839
11129
  return token;
10840
11130
  }
10841
11131
  function detectPackageManager2(cwd) {
10842
- if (fs32.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
10843
- if (fs32.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
10844
- if (fs32.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
11132
+ if (fs33.existsSync(path31.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
11133
+ if (fs33.existsSync(path31.join(cwd, "yarn.lock"))) return "yarn";
11134
+ if (fs33.existsSync(path31.join(cwd, "bun.lockb"))) return "bun";
10845
11135
  return "npm";
10846
11136
  }
10847
11137
  function shellOut(cmd, args, cwd, stream = true) {
@@ -10928,11 +11218,11 @@ function configureGitIdentity(cwd) {
10928
11218
  }
10929
11219
  function postFailureTail(issueNumber, cwd, reason) {
10930
11220
  if (!issueNumber) return;
10931
- const logPath = path30.join(cwd, ".kody", "last-run.jsonl");
11221
+ const logPath = path31.join(cwd, ".kody", "last-run.jsonl");
10932
11222
  let tail = "";
10933
11223
  try {
10934
- if (fs32.existsSync(logPath)) {
10935
- const content = fs32.readFileSync(logPath, "utf-8");
11224
+ if (fs33.existsSync(logPath)) {
11225
+ const content = fs33.readFileSync(logPath, "utf-8");
10936
11226
  tail = content.slice(-3e3);
10937
11227
  }
10938
11228
  } catch {
@@ -10957,7 +11247,7 @@ async function runCi(argv) {
10957
11247
  return 0;
10958
11248
  }
10959
11249
  const args = parseCiArgs(argv);
10960
- const cwd = args.cwd ? path30.resolve(args.cwd) : process.cwd();
11250
+ const cwd = args.cwd ? path31.resolve(args.cwd) : process.cwd();
10961
11251
  let earlyConfig;
10962
11252
  try {
10963
11253
  earlyConfig = loadConfig(cwd);
@@ -10967,9 +11257,9 @@ async function runCi(argv) {
10967
11257
  const eventName = process.env.GITHUB_EVENT_NAME;
10968
11258
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
10969
11259
  let manualWorkflowDispatch = false;
10970
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs32.existsSync(dispatchEventPath)) {
11260
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs33.existsSync(dispatchEventPath)) {
10971
11261
  try {
10972
- const evt = JSON.parse(fs32.readFileSync(dispatchEventPath, "utf-8"));
11262
+ const evt = JSON.parse(fs33.readFileSync(dispatchEventPath, "utf-8"));
10973
11263
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
10974
11264
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
10975
11265
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -11228,9 +11518,9 @@ function parseChatArgs(argv, env = process.env) {
11228
11518
  return result;
11229
11519
  }
11230
11520
  function commitChatFiles(cwd, sessionId, verbose) {
11231
- const sessionFile = path31.relative(cwd, sessionFilePath(cwd, sessionId));
11232
- const eventsFile = path31.relative(cwd, eventsFilePath(cwd, sessionId));
11233
- const paths = [sessionFile, eventsFile].filter((p) => fs33.existsSync(path31.join(cwd, p)));
11521
+ const sessionFile = path32.relative(cwd, sessionFilePath(cwd, sessionId));
11522
+ const eventsFile = path32.relative(cwd, eventsFilePath(cwd, sessionId));
11523
+ const paths = [sessionFile, eventsFile].filter((p) => fs34.existsSync(path32.join(cwd, p)));
11234
11524
  if (paths.length === 0) return;
11235
11525
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
11236
11526
  try {
@@ -11268,7 +11558,7 @@ async function runChat(argv) {
11268
11558
  ${CHAT_HELP}`);
11269
11559
  return 64;
11270
11560
  }
11271
- const cwd = args.cwd ? path31.resolve(args.cwd) : process.cwd();
11561
+ const cwd = args.cwd ? path32.resolve(args.cwd) : process.cwd();
11272
11562
  const sessionId = args.sessionId;
11273
11563
  const unpackedSecrets = unpackAllSecrets();
11274
11564
  if (unpackedSecrets > 0) {
@@ -11320,7 +11610,7 @@ ${CHAT_HELP}`);
11320
11610
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
11321
11611
  const meta = readMeta(sessionFile);
11322
11612
  process.stdout.write(
11323
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs33.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
11613
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs34.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
11324
11614
  `
11325
11615
  );
11326
11616
  try {
@@ -11584,7 +11874,6 @@ Exit codes:
11584
11874
  2 verify failed (no PR opened \u2014 branch pushed for inspection) \u2014 skipped in resolve mode
11585
11875
  3 no commits to ship (also the resolve clean-merge short-circuit)
11586
11876
  4 PR creation failed
11587
- 5 uncommitted changes on target branch
11588
11877
  64 invalid CLI args
11589
11878
  99 wrapper crashed
11590
11879
  `;
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "brain-serve",
3
+ "role": "utility",
4
+ "describe": "Long-lived HTTP server that wraps the Kody chat loop and speaks the Brain SSE protocol. Listens on $PORT (default 8080); auth via $BRAIN_API_KEY. Pairs with the Kody-Dashboard /api/kody/chat/brain proxy. Usage: `kody brain-serve` (runs until SIGINT/SIGTERM).",
5
+ "inputs": [],
6
+ "claudeCode": {
7
+ "model": "inherit",
8
+ "permissionMode": "acceptEdits",
9
+ "maxTurns": null,
10
+ "systemPromptAppend": null,
11
+ "tools": [],
12
+ "hooks": [],
13
+ "skills": [],
14
+ "commands": [],
15
+ "subagents": [],
16
+ "plugins": [],
17
+ "mcpServers": []
18
+ },
19
+ "cliTools": [],
20
+ "scripts": {
21
+ "preflight": [
22
+ {
23
+ "script": "brainServe"
24
+ }
25
+ ],
26
+ "postflight": []
27
+ }
28
+ }
File without changes
@@ -89,7 +89,19 @@ For EACH file you will change or create, include:
89
89
  - Target state — what will be there after the change, at the same level of specificity.
90
90
  - Exact locations of edits (function name, line range if stable, or anchor like "after the `meta` group field, before the closing `fields: []`").
91
91
  - For new files: rough shape including exports, key functions with signatures, and top-level module comment. **Do not paste full function bodies** — signatures and 1–2 sentence intent per export are enough for an implementer to write the body. Single-line type/interface declarations and short config snippets are fine.
92
- - Dependencies touched (imports added/removed, new packages) — call out if anything needs installing.
92
+ - Dependencies touched (imports added/removed) — call out per file; list any new third-party packages here AND aggregate them in the `## Dependencies to install` section below.
93
+
94
+ ## Dependencies to install
95
+ REQUIRED if the plan introduces any third-party import not already present in
96
+ the repo's manifest (`package.json` / `requirements.txt` / `go.mod` / etc).
97
+ One line per dep with the exact install command the implementer should run,
98
+ picked from the repo's lockfile (`pnpm-lock.yaml` → pnpm, `package-lock.json`
99
+ → npm, `yarn.lock` → yarn, `bun.lockb` → bun):
100
+ - `pnpm add <pkg>` — runtime dep, used by `<file>`
101
+ - `pnpm add -D <pkg>` — dev/types-only dep (`@types/*`, test tooling)
102
+ If no new deps are needed, write the single line `- none`. Omitting this
103
+ section when new deps ARE needed is a planning failure — the implementer will
104
+ hit a `Cannot find module` typecheck error and have to recover blind.
93
105
 
94
106
  ## Algorithms & pseudocode
95
107
  REQUIRED for any non-trivial logic (sorting, diffing, state transitions, concurrency, batching, caching, conflict resolution).
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -41,6 +41,8 @@ If a prior-art block is present above, READ THE DIFFS — those are failed or su
41
41
  3. **Build** — Edit/Write to implement the change. Stay within the plan; if you discover the plan was wrong, briefly say so and adjust.
42
42
  4. **Test** — for every new module you added and every behavior you changed, write or update tests. If the plan above contains a "Test plan" section, treat it as authoritative: every item there must produce a corresponding test. Before writing a test, open the newest existing file in the same test directory (`tests/int/`, `tests/unit/`, `tests/e2e/`, or sibling `*.test.ts`) and copy its imports, setup hooks, and auth pattern **verbatim**. Do NOT introduce a new test infrastructure (own testcontainers, `fetch` against relative URLs, alternate auth headers) when a working pattern already exists in that directory — divergence from the established pattern is a hard failure even if the test passes locally. Cover at least one happy path and one failure path per change. Skipping tests is a hard failure. A change may only be declared untestable if you can name the specific blocker (e.g., "no fake exists for the X SDK and stubbing it would mock the entire call surface"); vague "this is just config" claims are rejected. Untestable changes go in `PLAN_DEVIATIONS:` with the named blocker.
43
43
  5. **Verify** — before declaring DONE, call the `verify` tool (mcp__kody-verify__verify). It runs typecheck/lint/tests with the project's configured commands and returns `{ ok, failures, attemptsRemaining }`. If `ok: true`, you may proceed to DONE. If `ok: false`, read the truncated `failures` list, fix the root cause, commit-equivalent edits, and call `verify` again. You have up to 4 total attempts; the tool will return `locked: true` after that and you must wrap up with FAILED. The postflight verifier runs again after this session ends and is the final ratifier — but it's also the gate that downgrades a self-reported DONE to FAILED if you skipped this step, so calling the tool is strictly cheaper than not.
44
+
45
+ **Allowed fixes between attempts** include installing missing third-party dependencies. If `failures` contains `Cannot find module 'X'` / `Cannot find package 'X'` / `error TS2307` for a NON-relative import (i.e. not `./` or `../`), the fix is to install it with the repo's package manager before the next verify call — `pnpm add X` (runtime) or `pnpm add -D X` (types-only, `@types/*`, dev tooling). Pick the package manager from the repo's lockfile: `pnpm-lock.yaml` → pnpm, `package-lock.json` → npm, `yarn.lock` → yarn, `bun.lockb` → bun. If the plan provided a `## Dependencies to install` section, prefer the exact command it specifies. Do NOT install a dep just to silence a relative-path resolution error — that's a code bug, fix the import path instead.
44
46
  6. Your FINAL message must use this exact format (or a single `FAILED: <reason>` line on failure). The `PLAN_DEVIATIONS:` block is REQUIRED whenever a plan was provided.
45
47
 
46
48
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.71",
3
+ "version": "0.4.73",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,6 +12,23 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
+ "scripts": {
16
+ "kody": "tsx bin/kody.ts",
17
+ "serve": "tsx bin/kody.ts serve",
18
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
19
+ "serve:claude": "tsx bin/kody.ts serve claude",
20
+ "build": "tsup && node scripts/copy-assets.cjs",
21
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
22
+ "pretest": "pnpm check:modularity",
23
+ "test": "vitest run tests/unit tests/int --no-coverage",
24
+ "test:e2e": "vitest run tests/e2e --no-coverage",
25
+ "test:all": "vitest run tests --no-coverage",
26
+ "typecheck": "tsc --noEmit",
27
+ "lint": "biome check",
28
+ "lint:fix": "biome check --write",
29
+ "format": "biome format --write",
30
+ "prepublishOnly": "pnpm build"
31
+ },
15
32
  "dependencies": {
16
33
  "@actions/cache": "^6.0.0",
17
34
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -33,21 +50,5 @@
33
50
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
34
51
  },
35
52
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
36
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
37
- "scripts": {
38
- "kody": "tsx bin/kody.ts",
39
- "serve": "tsx bin/kody.ts serve",
40
- "serve:vscode": "tsx bin/kody.ts serve vscode",
41
- "serve:claude": "tsx bin/kody.ts serve claude",
42
- "build": "tsup && node scripts/copy-assets.cjs",
43
- "check:modularity": "tsx scripts/check-script-modularity.ts",
44
- "pretest": "pnpm check:modularity",
45
- "test": "vitest run tests/unit tests/int --no-coverage",
46
- "test:e2e": "vitest run tests/e2e --no-coverage",
47
- "test:all": "vitest run tests --no-coverage",
48
- "typecheck": "tsc --noEmit",
49
- "lint": "biome check",
50
- "lint:fix": "biome check --write",
51
- "format": "biome format --write"
52
- }
53
- }
53
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
54
+ }