@kody-ade/kody-engine 0.2.5 → 0.2.6

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/kody2.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.2.5",
6
+ version: "0.2.6",
7
7
  description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -81,7 +81,7 @@ function loadConfig(projectDir = process.cwd()) {
81
81
  throw new Error(`kody.config.json is invalid JSON: ${msg}`);
82
82
  }
83
83
  const quality = raw.quality ?? {};
84
- const git3 = raw.git ?? {};
84
+ const git4 = raw.git ?? {};
85
85
  const github = raw.github ?? {};
86
86
  const agent = raw.agent ?? {};
87
87
  if (!agent.model || typeof agent.model !== "string") {
@@ -97,7 +97,7 @@ function loadConfig(projectDir = process.cwd()) {
97
97
  testUnit: typeof quality.testUnit === "string" ? quality.testUnit : ""
98
98
  },
99
99
  git: {
100
- defaultBranch: typeof git3.defaultBranch === "string" ? git3.defaultBranch : "main"
100
+ defaultBranch: typeof git4.defaultBranch === "string" ? git4.defaultBranch : "main"
101
101
  },
102
102
  github: {
103
103
  owner: String(github.owner),
@@ -107,9 +107,23 @@ function loadConfig(projectDir = process.cwd()) {
107
107
  model: String(agent.model)
108
108
  },
109
109
  issueContext: parseIssueContext(raw.issueContext),
110
- testRequirements: parseTestRequirements(raw.testRequirements)
110
+ testRequirements: parseTestRequirements(raw.testRequirements),
111
+ release: parseReleaseConfig(raw.release)
111
112
  };
112
113
  }
114
+ function parseReleaseConfig(raw) {
115
+ if (!raw || typeof raw !== "object") return void 0;
116
+ const r = raw;
117
+ const out = {};
118
+ if (Array.isArray(r.versionFiles)) out.versionFiles = r.versionFiles.filter((f) => typeof f === "string");
119
+ if (typeof r.publishCommand === "string") out.publishCommand = r.publishCommand;
120
+ if (typeof r.notifyCommand === "string") out.notifyCommand = r.notifyCommand;
121
+ if (typeof r.e2eCommand === "string") out.e2eCommand = r.e2eCommand;
122
+ if (typeof r.draftRelease === "boolean") out.draftRelease = r.draftRelease;
123
+ if (typeof r.releaseBranch === "string") out.releaseBranch = r.releaseBranch;
124
+ if (typeof r.timeoutMs === "number" && r.timeoutMs > 0) out.timeoutMs = Math.floor(r.timeoutMs);
125
+ return Object.keys(out).length > 0 ? out : void 0;
126
+ }
113
127
  function parseIssueContext(raw) {
114
128
  if (!raw || typeof raw !== "object") return void 0;
115
129
  const r = raw;
@@ -137,8 +151,8 @@ function getAnthropicApiKeyOrDummy() {
137
151
  }
138
152
 
139
153
  // src/executor.ts
140
- import * as fs11 from "fs";
141
- import * as path9 from "path";
154
+ import * as fs12 from "fs";
155
+ import * as path10 from "path";
142
156
 
143
157
  // src/agent.ts
144
158
  import * as fs2 from "fs";
@@ -1947,8 +1961,310 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
1947
1961
  `);
1948
1962
  };
1949
1963
 
1964
+ // src/scripts/releaseFlow.ts
1965
+ import { execFileSync as execFileSync10, spawnSync } from "child_process";
1966
+ import * as fs10 from "fs";
1967
+ import * as path9 from "path";
1968
+ function bumpVersion(current, bump) {
1969
+ const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
1970
+ if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
1971
+ let [major, minor, patch] = [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
1972
+ if (bump === "major") {
1973
+ major++;
1974
+ minor = 0;
1975
+ patch = 0;
1976
+ } else if (bump === "minor") {
1977
+ minor++;
1978
+ patch = 0;
1979
+ } else patch++;
1980
+ return `${major}.${minor}.${patch}`;
1981
+ }
1982
+ function updateVersionInFile(file, newVersion, cwd) {
1983
+ const abs = path9.join(cwd, file);
1984
+ if (!fs10.existsSync(abs)) return false;
1985
+ const content = fs10.readFileSync(abs, "utf-8");
1986
+ const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
1987
+ if (updated === content) return false;
1988
+ fs10.writeFileSync(abs, updated);
1989
+ return true;
1990
+ }
1991
+ function generateChangelog(cwd, newVersion, lastTag) {
1992
+ const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
1993
+ let log = "";
1994
+ try {
1995
+ log = execFileSync10("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
1996
+ cwd,
1997
+ encoding: "utf-8",
1998
+ stdio: ["ignore", "pipe", "pipe"]
1999
+ }).trim();
2000
+ } catch {
2001
+ }
2002
+ const commits = log.split("\n").filter((l) => l.length > 0).map((line) => {
2003
+ const [subject, sha] = line.split("||");
2004
+ return { subject: subject ?? "", sha: sha ?? "" };
2005
+ }).filter((c) => !/^chore:\s*release\s+v\d/i.test(c.subject));
2006
+ const groups = { feat: [], fix: [], perf: [], refactor: [], docs: [], chore: [], other: [] };
2007
+ for (const c of commits) {
2008
+ const m = c.subject.match(/^(\w+)(?:\(.*?\))?\s*:\s*(.+)$/);
2009
+ const type = m?.[1]?.toLowerCase() ?? "other";
2010
+ const msg = m?.[2] ?? c.subject;
2011
+ const bucket = groups[type] ?? groups.other;
2012
+ bucket.push(`- ${msg} (${c.sha})`);
2013
+ }
2014
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2015
+ const parts = [`## v${newVersion} \u2014 ${date}`, ""];
2016
+ const labels = [
2017
+ ["feat", "Features"],
2018
+ ["fix", "Fixes"],
2019
+ ["perf", "Performance"],
2020
+ ["refactor", "Refactoring"],
2021
+ ["docs", "Docs"],
2022
+ ["chore", "Chores"],
2023
+ ["other", "Other"]
2024
+ ];
2025
+ for (const [key, label] of labels) {
2026
+ const items = groups[key];
2027
+ if (!items || items.length === 0) continue;
2028
+ parts.push(`### ${label}`);
2029
+ parts.push(...items);
2030
+ parts.push("");
2031
+ }
2032
+ if (parts.length === 2) parts.push("_No notable commits since the last release._", "");
2033
+ return parts.join("\n");
2034
+ }
2035
+ function prependChangelog(cwd, entry) {
2036
+ const p = path9.join(cwd, "CHANGELOG.md");
2037
+ const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
2038
+ if (fs10.existsSync(p)) {
2039
+ const prior = fs10.readFileSync(p, "utf-8");
2040
+ if (/^#\s*Changelog\b/m.test(prior)) {
2041
+ const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
2042
+ fs10.writeFileSync(p, `${prior.slice(0, idx + 1)}
2043
+ ${entry}${prior.slice(idx + 1)}`);
2044
+ } else {
2045
+ fs10.writeFileSync(p, `${header}${entry}${prior}`);
2046
+ }
2047
+ } else {
2048
+ fs10.writeFileSync(p, `${header}${entry}`);
2049
+ }
2050
+ }
2051
+ function git3(args, cwd, timeout = 6e4) {
2052
+ return execFileSync10("git", args, {
2053
+ encoding: "utf-8",
2054
+ timeout,
2055
+ cwd,
2056
+ env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
2057
+ stdio: ["pipe", "pipe", "pipe"]
2058
+ }).trim();
2059
+ }
2060
+ function lastReleaseTag(cwd) {
2061
+ try {
2062
+ return git3(["describe", "--tags", "--abbrev=0", "--match", "v*"], cwd);
2063
+ } catch {
2064
+ return null;
2065
+ }
2066
+ }
2067
+ function runShell(cmd, cwd, timeoutMs) {
2068
+ const r = spawnSync(cmd, {
2069
+ cwd,
2070
+ shell: true,
2071
+ env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
2072
+ encoding: "utf-8",
2073
+ timeout: timeoutMs
2074
+ });
2075
+ return { exitCode: r.status ?? -1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
2076
+ }
2077
+ var releaseFlow = async (ctx) => {
2078
+ const mode = ctx.args.mode ?? "prepare";
2079
+ const bump = ctx.args.bump ?? "patch";
2080
+ const dryRun = ctx.args["dry-run"] === true || ctx.args.dryRun === true;
2081
+ const cwd = ctx.cwd;
2082
+ const releaseCfg = ctx.config.release ?? {};
2083
+ const versionFiles = releaseCfg.versionFiles && releaseCfg.versionFiles.length > 0 ? releaseCfg.versionFiles : ["package.json"];
2084
+ const timeoutMs = releaseCfg.timeoutMs ?? 6e5;
2085
+ ctx.skipAgent = true;
2086
+ if (mode === "prepare") {
2087
+ await runPrepare({ cwd, bump, dryRun, versionFiles, ctx });
2088
+ return;
2089
+ }
2090
+ if (mode === "finalize") {
2091
+ await runFinalize({ cwd, dryRun, timeoutMs, releaseCfg, ctx });
2092
+ return;
2093
+ }
2094
+ ctx.output.exitCode = 64;
2095
+ ctx.output.reason = `release: unknown mode '${mode}'`;
2096
+ };
2097
+ async function runPrepare(args) {
2098
+ const { cwd, bump, dryRun, versionFiles, ctx } = args;
2099
+ const pkgPath = path9.join(cwd, "package.json");
2100
+ if (!fs10.existsSync(pkgPath)) {
2101
+ ctx.output.exitCode = 99;
2102
+ ctx.output.reason = "release prepare: package.json not found";
2103
+ return;
2104
+ }
2105
+ const pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
2106
+ if (typeof pkg.version !== "string") {
2107
+ ctx.output.exitCode = 99;
2108
+ ctx.output.reason = "release prepare: package.json has no version";
2109
+ return;
2110
+ }
2111
+ const oldVersion = pkg.version;
2112
+ const newVersion = bumpVersion(oldVersion, bump);
2113
+ const tag = `v${newVersion}`;
2114
+ process.stdout.write(`\u2192 release prepare: ${oldVersion} \u2192 ${newVersion} (${bump})
2115
+ `);
2116
+ if (dryRun) {
2117
+ ctx.output.exitCode = 0;
2118
+ ctx.output.reason = `dry-run \u2014 would bump to ${newVersion}`;
2119
+ process.stdout.write(`RELEASE_PLAN=bump=${newVersion} tag=${tag}
2120
+ `);
2121
+ return;
2122
+ }
2123
+ const touched = [];
2124
+ for (const f of versionFiles) {
2125
+ if (updateVersionInFile(f, newVersion, cwd)) touched.push(f);
2126
+ }
2127
+ if (touched.length === 0) {
2128
+ ctx.output.exitCode = 1;
2129
+ ctx.output.reason = `release prepare: no version strings updated (files: ${versionFiles.join(", ")})`;
2130
+ return;
2131
+ }
2132
+ process.stdout.write(` wrote ${touched.join(", ")}
2133
+ `);
2134
+ const entry = generateChangelog(cwd, newVersion, lastReleaseTag(cwd));
2135
+ prependChangelog(cwd, entry);
2136
+ process.stdout.write(` wrote CHANGELOG.md
2137
+ `);
2138
+ const releaseBranch = `release/${tag}`;
2139
+ try {
2140
+ git3(["checkout", "-b", releaseBranch], cwd);
2141
+ for (const f of [...touched, "CHANGELOG.md"]) git3(["add", "--", f], cwd);
2142
+ git3(["commit", "--no-gpg-sign", "-m", `chore: release ${tag}`], cwd);
2143
+ git3(["push", "-u", "origin", releaseBranch], cwd, 12e4);
2144
+ } catch (err) {
2145
+ const msg = err instanceof Error ? err.message : String(err);
2146
+ ctx.output.exitCode = 4;
2147
+ ctx.output.reason = `release prepare: git commit/push failed: ${msg}`;
2148
+ return;
2149
+ }
2150
+ const base = ctx.config.git.defaultBranch;
2151
+ const title = `chore: release ${tag}`;
2152
+ const body = `Automated release PR opened by kody2.
2153
+
2154
+ ${entry}
2155
+
2156
+ Merge this and then run \`kody2 release --mode finalize\`.`;
2157
+ let prUrl = "";
2158
+ try {
2159
+ prUrl = gh(
2160
+ ["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"],
2161
+ { input: body, cwd }
2162
+ ).trim();
2163
+ } catch (err) {
2164
+ const msg = err instanceof Error ? err.message : String(err);
2165
+ ctx.output.exitCode = 4;
2166
+ ctx.output.reason = `release prepare: gh pr create failed: ${msg}`;
2167
+ return;
2168
+ }
2169
+ ctx.output.prUrl = prUrl;
2170
+ ctx.output.exitCode = 0;
2171
+ process.stdout.write(`RELEASE_PR=${prUrl}
2172
+ `);
2173
+ }
2174
+ async function runFinalize(args) {
2175
+ const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
2176
+ const pkgPath = path9.join(cwd, "package.json");
2177
+ const pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
2178
+ if (typeof pkg.version !== "string") {
2179
+ ctx.output.exitCode = 99;
2180
+ ctx.output.reason = "release finalize: package.json has no version";
2181
+ return;
2182
+ }
2183
+ const version = pkg.version;
2184
+ const tag = `v${version}`;
2185
+ process.stdout.write(`\u2192 release finalize: ${tag}
2186
+ `);
2187
+ try {
2188
+ git3(["rev-parse", "--verify", tag], cwd);
2189
+ ctx.output.exitCode = 1;
2190
+ ctx.output.reason = `release finalize: tag ${tag} already exists`;
2191
+ return;
2192
+ } catch {
2193
+ }
2194
+ if (dryRun) {
2195
+ ctx.output.exitCode = 0;
2196
+ ctx.output.reason = `dry-run \u2014 would tag + publish ${tag}`;
2197
+ return;
2198
+ }
2199
+ if (releaseCfg.e2eCommand && releaseCfg.e2eCommand.trim().length > 0) {
2200
+ const cmd = releaseCfg.e2eCommand.replace(/\$VERSION/g, version);
2201
+ process.stdout.write(` E2E gate: ${cmd}
2202
+ `);
2203
+ const r = runShell(cmd, cwd, timeoutMs);
2204
+ if (r.exitCode !== 0) {
2205
+ ctx.output.exitCode = 2;
2206
+ ctx.output.reason = `release finalize: E2E gate failed (exit ${r.exitCode}): ${truncate2(r.stderr, 600)}`;
2207
+ return;
2208
+ }
2209
+ }
2210
+ try {
2211
+ git3(["tag", "-a", tag, "-m", `Release ${tag}`], cwd);
2212
+ git3(["push", "origin", tag], cwd, 12e4);
2213
+ } catch (err) {
2214
+ const msg = err instanceof Error ? err.message : String(err);
2215
+ ctx.output.exitCode = 4;
2216
+ ctx.output.reason = `release finalize: tag/push failed: ${msg}`;
2217
+ return;
2218
+ }
2219
+ let publishStatus = "skipped";
2220
+ if (releaseCfg.publishCommand && releaseCfg.publishCommand.trim().length > 0) {
2221
+ const cmd = releaseCfg.publishCommand.replace(/\$VERSION/g, version);
2222
+ process.stdout.write(` publish: ${cmd}
2223
+ `);
2224
+ const r = runShell(cmd, cwd, timeoutMs);
2225
+ publishStatus = r.exitCode === 0 ? "ok" : "failed";
2226
+ if (r.exitCode !== 0) {
2227
+ process.stderr.write(`[kody2 release] publishCommand exit ${r.exitCode}
2228
+ ${truncate2(r.stderr, 2e3)}
2229
+ `);
2230
+ }
2231
+ }
2232
+ let releaseUrl = "";
2233
+ try {
2234
+ const releaseArgs = [
2235
+ "release",
2236
+ "create",
2237
+ tag,
2238
+ "--title",
2239
+ tag,
2240
+ "--notes",
2241
+ `Release ${tag} \u2014 automated by kody2.`
2242
+ ];
2243
+ if (releaseCfg.draftRelease) releaseArgs.push("--draft");
2244
+ releaseUrl = gh(releaseArgs, { cwd }).trim();
2245
+ } catch (err) {
2246
+ process.stderr.write(`[kody2 release] gh release create failed: ${err instanceof Error ? err.message : String(err)}
2247
+ `);
2248
+ }
2249
+ if (releaseCfg.notifyCommand && releaseCfg.notifyCommand.trim().length > 0) {
2250
+ const cmd = releaseCfg.notifyCommand.replace(/\$VERSION/g, version);
2251
+ runShell(cmd, cwd, timeoutMs);
2252
+ }
2253
+ if (releaseUrl) ctx.output.prUrl = releaseUrl;
2254
+ if (publishStatus === "failed") {
2255
+ ctx.output.exitCode = 1;
2256
+ ctx.output.reason = `release finalize: tag + gh release created, but publishCommand failed`;
2257
+ return;
2258
+ }
2259
+ ctx.output.exitCode = 0;
2260
+ process.stdout.write(`RELEASE_TAG=${tag}
2261
+ `);
2262
+ if (releaseUrl) process.stdout.write(`RELEASE_URL=${releaseUrl}
2263
+ `);
2264
+ }
2265
+
1950
2266
  // src/scripts/resolveFlow.ts
1951
- import { execFileSync as execFileSync10 } from "child_process";
2267
+ import { execFileSync as execFileSync11 } from "child_process";
1952
2268
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
1953
2269
  var resolveFlow = async (ctx) => {
1954
2270
  const prNumber = ctx.args.pr;
@@ -2000,7 +2316,7 @@ var resolveFlow = async (ctx) => {
2000
2316
  };
2001
2317
  function getConflictedFiles(cwd) {
2002
2318
  try {
2003
- const out = execFileSync10("git", ["diff", "--name-only", "--diff-filter=U"], {
2319
+ const out = execFileSync11("git", ["diff", "--name-only", "--diff-filter=U"], {
2004
2320
  encoding: "utf-8",
2005
2321
  cwd,
2006
2322
  env: { ...process.env, HUSKY: "0" }
@@ -2015,7 +2331,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
2015
2331
  let total = 0;
2016
2332
  for (const f of files) {
2017
2333
  try {
2018
- const content = execFileSync10("cat", [f], { encoding: "utf-8", cwd }).toString();
2334
+ const content = execFileSync11("cat", [f], { encoding: "utf-8", cwd }).toString();
2019
2335
  const snippet = `### ${f}
2020
2336
 
2021
2337
  \`\`\`
@@ -2180,7 +2496,7 @@ var verify = async (ctx) => {
2180
2496
  };
2181
2497
 
2182
2498
  // src/scripts/writeRunSummary.ts
2183
- import * as fs10 from "fs";
2499
+ import * as fs11 from "fs";
2184
2500
  var writeRunSummary = async (ctx) => {
2185
2501
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
2186
2502
  if (!summaryPath) return;
@@ -2202,7 +2518,7 @@ var writeRunSummary = async (ctx) => {
2202
2518
  if (reason) lines.push(`- **Reason:** ${reason}`);
2203
2519
  lines.push("");
2204
2520
  try {
2205
- fs10.appendFileSync(summaryPath, `${lines.join("\n")}
2521
+ fs11.appendFileSync(summaryPath, `${lines.join("\n")}
2206
2522
  `);
2207
2523
  } catch {
2208
2524
  }
@@ -2216,6 +2532,7 @@ var preflightScripts = {
2216
2532
  resolveFlow,
2217
2533
  reviewFlow,
2218
2534
  initFlow,
2535
+ releaseFlow,
2219
2536
  loadConventions,
2220
2537
  loadCoverageRules,
2221
2538
  composePrompt
@@ -2236,7 +2553,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
2236
2553
  ]);
2237
2554
 
2238
2555
  // src/tools.ts
2239
- import { execFileSync as execFileSync11 } from "child_process";
2556
+ import { execFileSync as execFileSync12 } from "child_process";
2240
2557
  function verifyCliTools(tools, cwd) {
2241
2558
  const out = [];
2242
2559
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -2252,24 +2569,24 @@ function firstRequiredFailure(results, tools) {
2252
2569
  }
2253
2570
  function verifyOne(tool, cwd) {
2254
2571
  const result = { name: tool.name, present: false, verified: false };
2255
- let present = runShell(tool.install.checkCommand, cwd);
2572
+ let present = runShell2(tool.install.checkCommand, cwd);
2256
2573
  if (!present && tool.install.installCommand) {
2257
- runShell(tool.install.installCommand, cwd, 12e4);
2258
- present = runShell(tool.install.checkCommand, cwd);
2574
+ runShell2(tool.install.installCommand, cwd, 12e4);
2575
+ present = runShell2(tool.install.checkCommand, cwd);
2259
2576
  }
2260
2577
  result.present = present;
2261
2578
  if (!present) {
2262
2579
  result.error = `tool "${tool.name}" not on PATH (check: ${tool.install.checkCommand})`;
2263
2580
  return result;
2264
2581
  }
2265
- const verified = runShell(tool.verify, cwd);
2582
+ const verified = runShell2(tool.verify, cwd);
2266
2583
  result.verified = verified;
2267
2584
  if (!verified) result.error = `tool "${tool.name}" failed verify: ${tool.verify}`;
2268
2585
  return result;
2269
2586
  }
2270
- function runShell(cmd, cwd, timeoutMs = 3e4) {
2587
+ function runShell2(cmd, cwd, timeoutMs = 3e4) {
2271
2588
  try {
2272
- execFileSync11("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
2589
+ execFileSync12("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
2273
2590
  return true;
2274
2591
  } catch {
2275
2592
  return false;
@@ -2320,7 +2637,7 @@ async function runExecutable(profileName, input) {
2320
2637
  data: {},
2321
2638
  output: { exitCode: 0 }
2322
2639
  };
2323
- const ndjsonDir = path9.join(input.cwd, ".kody2");
2640
+ const ndjsonDir = path10.join(input.cwd, ".kody2");
2324
2641
  const invokeAgent = async (prompt) => runAgent({
2325
2642
  prompt,
2326
2643
  model,
@@ -2379,17 +2696,17 @@ async function runExecutable(profileName, input) {
2379
2696
  }
2380
2697
  }
2381
2698
  function resolveProfilePath(profileName) {
2382
- const here = path9.dirname(new URL(import.meta.url).pathname);
2699
+ const here = path10.dirname(new URL(import.meta.url).pathname);
2383
2700
  const candidates = [
2384
- path9.join(here, "executables", profileName, "profile.json"),
2701
+ path10.join(here, "executables", profileName, "profile.json"),
2385
2702
  // same-dir sibling (dev)
2386
- path9.join(here, "..", "executables", profileName, "profile.json"),
2703
+ path10.join(here, "..", "executables", profileName, "profile.json"),
2387
2704
  // up one (prod: dist/bin → dist/executables)
2388
- path9.join(here, "..", "src", "executables", profileName, "profile.json")
2705
+ path10.join(here, "..", "src", "executables", profileName, "profile.json")
2389
2706
  // fallback
2390
2707
  ];
2391
2708
  for (const c of candidates) {
2392
- if (fs11.existsSync(c)) return c;
2709
+ if (fs12.existsSync(c)) return c;
2393
2710
  }
2394
2711
  return candidates[0];
2395
2712
  }
@@ -2467,12 +2784,12 @@ function finish(out) {
2467
2784
  }
2468
2785
 
2469
2786
  // src/kody2-cli.ts
2470
- import { execFileSync as execFileSync12 } from "child_process";
2471
- import * as fs13 from "fs";
2472
- import * as path10 from "path";
2787
+ import { execFileSync as execFileSync13 } from "child_process";
2788
+ import * as fs14 from "fs";
2789
+ import * as path11 from "path";
2473
2790
 
2474
2791
  // src/dispatch.ts
2475
- import * as fs12 from "fs";
2792
+ import * as fs13 from "fs";
2476
2793
  function autoDispatch(explicit) {
2477
2794
  if (explicit?.mode && explicit.target) {
2478
2795
  return {
@@ -2482,10 +2799,10 @@ function autoDispatch(explicit) {
2482
2799
  }
2483
2800
  const eventName = process.env.GITHUB_EVENT_NAME;
2484
2801
  const eventPath = process.env.GITHUB_EVENT_PATH;
2485
- if (!eventName || !eventPath || !fs12.existsSync(eventPath)) return null;
2802
+ if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
2486
2803
  let event = {};
2487
2804
  try {
2488
- event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
2805
+ event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
2489
2806
  } catch {
2490
2807
  return null;
2491
2808
  }
@@ -2605,14 +2922,14 @@ function resolveAuthToken(env = process.env) {
2605
2922
  return token;
2606
2923
  }
2607
2924
  function detectPackageManager2(cwd) {
2608
- if (fs13.existsSync(path10.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
2609
- if (fs13.existsSync(path10.join(cwd, "yarn.lock"))) return "yarn";
2610
- if (fs13.existsSync(path10.join(cwd, "bun.lockb"))) return "bun";
2925
+ if (fs14.existsSync(path11.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
2926
+ if (fs14.existsSync(path11.join(cwd, "yarn.lock"))) return "yarn";
2927
+ if (fs14.existsSync(path11.join(cwd, "bun.lockb"))) return "bun";
2611
2928
  return "npm";
2612
2929
  }
2613
2930
  function shellOut(cmd, args, cwd, stream = true) {
2614
2931
  try {
2615
- execFileSync12(cmd, args, {
2932
+ execFileSync13(cmd, args, {
2616
2933
  cwd,
2617
2934
  stdio: stream ? "inherit" : "pipe",
2618
2935
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -2625,7 +2942,7 @@ function shellOut(cmd, args, cwd, stream = true) {
2625
2942
  }
2626
2943
  function isOnPath(bin) {
2627
2944
  try {
2628
- execFileSync12("which", [bin], { stdio: "pipe" });
2945
+ execFileSync13("which", [bin], { stdio: "pipe" });
2629
2946
  return true;
2630
2947
  } catch {
2631
2948
  return false;
@@ -2659,7 +2976,7 @@ function installLitellmIfNeeded(cwd) {
2659
2976
  } catch {
2660
2977
  }
2661
2978
  try {
2662
- execFileSync12("python3", ["-c", "import litellm"], { stdio: "pipe" });
2979
+ execFileSync13("python3", ["-c", "import litellm"], { stdio: "pipe" });
2663
2980
  process.stdout.write("\u2192 kody2: litellm already installed\n");
2664
2981
  return 0;
2665
2982
  } catch {
@@ -2669,26 +2986,26 @@ function installLitellmIfNeeded(cwd) {
2669
2986
  }
2670
2987
  function configureGitIdentity(cwd) {
2671
2988
  try {
2672
- const name = execFileSync12("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
2989
+ const name = execFileSync13("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
2673
2990
  if (name) return;
2674
2991
  } catch {
2675
2992
  }
2676
2993
  try {
2677
- execFileSync12("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
2994
+ execFileSync13("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
2678
2995
  } catch {
2679
2996
  }
2680
2997
  try {
2681
- execFileSync12("git", ["config", "user.email", "kody2-bot@users.noreply.github.com"], { cwd, stdio: "pipe" });
2998
+ execFileSync13("git", ["config", "user.email", "kody2-bot@users.noreply.github.com"], { cwd, stdio: "pipe" });
2682
2999
  } catch {
2683
3000
  }
2684
3001
  }
2685
3002
  function postFailureTail(issueNumber, cwd, reason) {
2686
3003
  if (!issueNumber) return;
2687
- const logPath = path10.join(cwd, ".kody2", "last-run.jsonl");
3004
+ const logPath = path11.join(cwd, ".kody2", "last-run.jsonl");
2688
3005
  let tail = "";
2689
3006
  try {
2690
- if (fs13.existsSync(logPath)) {
2691
- const content = fs13.readFileSync(logPath, "utf-8");
3007
+ if (fs14.existsSync(logPath)) {
3008
+ const content = fs14.readFileSync(logPath, "utf-8");
2692
3009
  tail = content.slice(-3e3);
2693
3010
  }
2694
3011
  } catch {
@@ -2725,7 +3042,7 @@ async function runCi(argv) {
2725
3042
  ${CI_HELP}`);
2726
3043
  return 64;
2727
3044
  }
2728
- const cwd = args.cwd ? path10.resolve(args.cwd) : process.cwd();
3045
+ const cwd = args.cwd ? path11.resolve(args.cwd) : process.cwd();
2729
3046
  const dispatch = autoFallback ?? {
2730
3047
  mode: "run",
2731
3048
  target: args.issueNumber,
@@ -2801,31 +3118,31 @@ ${CI_HELP}`);
2801
3118
  }
2802
3119
 
2803
3120
  // src/registry.ts
2804
- import * as fs14 from "fs";
2805
- import * as path11 from "path";
3121
+ import * as fs15 from "fs";
3122
+ import * as path12 from "path";
2806
3123
  function getExecutablesRoot() {
2807
- const here = path11.dirname(new URL(import.meta.url).pathname);
3124
+ const here = path12.dirname(new URL(import.meta.url).pathname);
2808
3125
  const candidates = [
2809
- path11.join(here, "executables"),
3126
+ path12.join(here, "executables"),
2810
3127
  // dev: src/
2811
- path11.join(here, "..", "executables"),
3128
+ path12.join(here, "..", "executables"),
2812
3129
  // built: dist/bin → dist/executables
2813
- path11.join(here, "..", "src", "executables")
3130
+ path12.join(here, "..", "src", "executables")
2814
3131
  // fallback
2815
3132
  ];
2816
3133
  for (const c of candidates) {
2817
- if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
3134
+ if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
2818
3135
  }
2819
3136
  return candidates[0];
2820
3137
  }
2821
3138
  function listExecutables(root = getExecutablesRoot()) {
2822
- if (!fs14.existsSync(root)) return [];
2823
- const entries = fs14.readdirSync(root, { withFileTypes: true });
3139
+ if (!fs15.existsSync(root)) return [];
3140
+ const entries = fs15.readdirSync(root, { withFileTypes: true });
2824
3141
  const out = [];
2825
3142
  for (const ent of entries) {
2826
3143
  if (!ent.isDirectory()) continue;
2827
- const profilePath = path11.join(root, ent.name, "profile.json");
2828
- if (fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile()) {
3144
+ const profilePath = path12.join(root, ent.name, "profile.json");
3145
+ if (fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile()) {
2829
3146
  out.push({ name: ent.name, profilePath });
2830
3147
  }
2831
3148
  }
@@ -2833,8 +3150,8 @@ function listExecutables(root = getExecutablesRoot()) {
2833
3150
  }
2834
3151
  function hasExecutable(name, root = getExecutablesRoot()) {
2835
3152
  if (!isSafeName(name)) return false;
2836
- const profilePath = path11.join(root, name, "profile.json");
2837
- return fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile();
3153
+ const profilePath = path12.join(root, name, "profile.json");
3154
+ return fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile();
2838
3155
  }
2839
3156
  function isSafeName(name) {
2840
3157
  return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "release",
3
+ "describe": "Version bump + changelog + release PR (prepare), or tag + publish + GH release (finalize). No agent.",
4
+
5
+ "inputs": [
6
+ {
7
+ "name": "mode",
8
+ "flag": "--mode",
9
+ "type": "enum",
10
+ "values": ["prepare", "finalize"],
11
+ "required": false,
12
+ "describe": "`prepare` (default): bump + changelog + release PR. `finalize`: E2E gate + tag + publish + GH release."
13
+ },
14
+ {
15
+ "name": "bump",
16
+ "flag": "--bump",
17
+ "type": "enum",
18
+ "values": ["patch", "minor", "major"],
19
+ "required": false,
20
+ "describe": "Version bump when mode=prepare (ignored in finalize). Default patch."
21
+ },
22
+ {
23
+ "name": "dry-run",
24
+ "flag": "--dry-run",
25
+ "type": "bool",
26
+ "required": false,
27
+ "describe": "Print plan without writing files, creating PRs, tagging, or publishing."
28
+ }
29
+ ],
30
+
31
+ "claudeCode": {
32
+ "model": "inherit",
33
+ "permissionMode": "acceptEdits",
34
+ "maxTurns": null,
35
+ "systemPromptAppend": null,
36
+ "tools": [],
37
+ "hooks": { "PreToolUse": [], "PostToolUse": [], "Stop": [] },
38
+ "skills": [],
39
+ "commands": [],
40
+ "subagents": [],
41
+ "plugins": [],
42
+ "mcpServers": []
43
+ },
44
+
45
+ "cliTools": [],
46
+
47
+ "scripts": {
48
+ "preflight": [
49
+ { "script": "releaseFlow" }
50
+ ],
51
+ "postflight": []
52
+ }
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "kody2 — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",