@kody-ade/kody-engine 0.4.71 → 0.4.72

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.72",
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",
@@ -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"], {
@@ -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);
@@ -6613,8 +6667,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
6613
6667
  return { branch: branchName, created: false };
6614
6668
  } catch {
6615
6669
  }
6616
- let forkPoint = defaultBranch;
6617
- if (baseBranch && baseBranch !== defaultBranch) {
6670
+ let forkPoint = defaultBranch2;
6671
+ if (baseBranch && baseBranch !== defaultBranch2) {
6618
6672
  try {
6619
6673
  git2(["rev-parse", "--verify", `origin/${baseBranch}`], cwd);
6620
6674
  forkPoint = baseBranch;
@@ -6986,11 +7040,11 @@ function detectOwnerRepo(cwd) {
6986
7040
  if (!m) return null;
6987
7041
  return { owner: m[1], repo: m[2] };
6988
7042
  }
6989
- function makeConfig(pm, ownerRepo, defaultBranch) {
7043
+ function makeConfig(pm, ownerRepo, defaultBranch2) {
6990
7044
  return {
6991
7045
  $schema: "https://raw.githubusercontent.com/aharonyaircohen/kody-engine/main/kody.config.schema.json",
6992
7046
  quality: qualityCommandsFor(pm),
6993
- git: { defaultBranch },
7047
+ git: { defaultBranch: defaultBranch2 },
6994
7048
  github: {
6995
7049
  owner: ownerRepo?.owner ?? "OWNER",
6996
7050
  repo: ownerRepo?.repo ?? "REPO"
@@ -7082,12 +7136,12 @@ function performInit(cwd, force) {
7082
7136
  const skipped = [];
7083
7137
  const pm = detectPackageManager(cwd);
7084
7138
  const ownerRepo = detectOwnerRepo(cwd);
7085
- const defaultBranch = defaultBranchFromGit(cwd);
7139
+ const defaultBranch2 = defaultBranchFromGit(cwd);
7086
7140
  const configPath = path23.join(cwd, "kody.config.json");
7087
7141
  if (fs24.existsSync(configPath) && !force) {
7088
7142
  skipped.push("kody.config.json");
7089
7143
  } else {
7090
- const cfg = makeConfig(pm, ownerRepo, defaultBranch);
7144
+ const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
7091
7145
  fs24.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7092
7146
  `);
7093
7147
  wrote.push("kody.config.json");
@@ -8797,19 +8851,8 @@ var runFlow = async (ctx) => {
8797
8851
  process.stderr.write(`[kody runFlow] resolved base branch: ${base} (from --base)
8798
8852
  `);
8799
8853
  }
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
- }
8854
+ const branchInfo = ensureFeatureBranch(issueNumber, issue.title, ctx.config.git.defaultBranch, ctx.cwd, base ?? void 0);
8855
+ ctx.data.branch = branchInfo.branch;
8813
8856
  const runUrl = getRunUrl();
8814
8857
  const startMsg = runUrl ? `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\`, run ${runUrl}` : `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\``;
8815
8858
  tryPost(issueNumber, startMsg, ctx.cwd);
@@ -10521,7 +10564,7 @@ async function runContainerLoop(profile, ctx, input) {
10521
10564
  process.stderr.write(`[kody container] step ${iteration}: invoking ${child.exec}
10522
10565
  `);
10523
10566
  if (profile.resetBetweenChildren !== false) {
10524
- resetWorkingTree(input.cwd);
10567
+ resetWorkingTree2(input.cwd);
10525
10568
  } else {
10526
10569
  process.stderr.write(`[kody container] resetBetweenChildren=false; preserving tracked tree
10527
10570
  `);
@@ -10684,7 +10727,7 @@ async function runContainerLoop(profile, ctx, input) {
10684
10727
  currentIdx = nextIdx;
10685
10728
  }
10686
10729
  }
10687
- function resetWorkingTree(cwd) {
10730
+ function resetWorkingTree2(cwd) {
10688
10731
  try {
10689
10732
  execFileSync29("git", ["reset", "--hard", "HEAD"], {
10690
10733
  cwd,
@@ -10780,7 +10823,6 @@ Exit codes (inherited from kody run):
10780
10823
  2 verify failed (no PR opened \u2014 branch pushed for inspection)
10781
10824
  3 no commits to ship
10782
10825
  4 PR creation failed
10783
- 5 uncommitted changes on target branch
10784
10826
  99 wrapper crashed
10785
10827
  `;
10786
10828
  function parseCiArgs(argv) {
@@ -11584,7 +11626,6 @@ Exit codes:
11584
11626
  2 verify failed (no PR opened \u2014 branch pushed for inspection) \u2014 skipped in resolve mode
11585
11627
  3 no commits to ship (also the resolve clean-merge short-circuit)
11586
11628
  4 PR creation failed
11587
- 5 uncommitted changes on target branch
11588
11629
  64 invalid CLI args
11589
11630
  99 wrapper crashed
11590
11631
  `;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
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.72",
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
+ }