@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 +373 -56
- package/dist/executables/release/profile.json +53 -0
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
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
|
|
141
|
-
import * as
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
2572
|
+
let present = runShell2(tool.install.checkCommand, cwd);
|
|
2256
2573
|
if (!present && tool.install.installCommand) {
|
|
2257
|
-
|
|
2258
|
-
present =
|
|
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 =
|
|
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
|
|
2587
|
+
function runShell2(cmd, cwd, timeoutMs = 3e4) {
|
|
2271
2588
|
try {
|
|
2272
|
-
|
|
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 =
|
|
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 =
|
|
2699
|
+
const here = path10.dirname(new URL(import.meta.url).pathname);
|
|
2383
2700
|
const candidates = [
|
|
2384
|
-
|
|
2701
|
+
path10.join(here, "executables", profileName, "profile.json"),
|
|
2385
2702
|
// same-dir sibling (dev)
|
|
2386
|
-
|
|
2703
|
+
path10.join(here, "..", "executables", profileName, "profile.json"),
|
|
2387
2704
|
// up one (prod: dist/bin → dist/executables)
|
|
2388
|
-
|
|
2705
|
+
path10.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
2389
2706
|
// fallback
|
|
2390
2707
|
];
|
|
2391
2708
|
for (const c of candidates) {
|
|
2392
|
-
if (
|
|
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
|
|
2471
|
-
import * as
|
|
2472
|
-
import * as
|
|
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
|
|
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 || !
|
|
2802
|
+
if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
|
|
2486
2803
|
let event = {};
|
|
2487
2804
|
try {
|
|
2488
|
-
event = JSON.parse(
|
|
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 (
|
|
2609
|
-
if (
|
|
2610
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2994
|
+
execFileSync13("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
|
|
2678
2995
|
} catch {
|
|
2679
2996
|
}
|
|
2680
2997
|
try {
|
|
2681
|
-
|
|
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 =
|
|
3004
|
+
const logPath = path11.join(cwd, ".kody2", "last-run.jsonl");
|
|
2688
3005
|
let tail = "";
|
|
2689
3006
|
try {
|
|
2690
|
-
if (
|
|
2691
|
-
const content =
|
|
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 ?
|
|
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
|
|
2805
|
-
import * as
|
|
3121
|
+
import * as fs15 from "fs";
|
|
3122
|
+
import * as path12 from "path";
|
|
2806
3123
|
function getExecutablesRoot() {
|
|
2807
|
-
const here =
|
|
3124
|
+
const here = path12.dirname(new URL(import.meta.url).pathname);
|
|
2808
3125
|
const candidates = [
|
|
2809
|
-
|
|
3126
|
+
path12.join(here, "executables"),
|
|
2810
3127
|
// dev: src/
|
|
2811
|
-
|
|
3128
|
+
path12.join(here, "..", "executables"),
|
|
2812
3129
|
// built: dist/bin → dist/executables
|
|
2813
|
-
|
|
3130
|
+
path12.join(here, "..", "src", "executables")
|
|
2814
3131
|
// fallback
|
|
2815
3132
|
];
|
|
2816
3133
|
for (const c of candidates) {
|
|
2817
|
-
if (
|
|
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 (!
|
|
2823
|
-
const entries =
|
|
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 =
|
|
2828
|
-
if (
|
|
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 =
|
|
2837
|
-
return
|
|
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.
|
|
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",
|