@kody-ade/kody-engine 0.2.4 → 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.
|
|
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";
|
|
@@ -1898,8 +1912,359 @@ function postWith(type, n, body, cwd) {
|
|
|
1898
1912
|
}
|
|
1899
1913
|
}
|
|
1900
1914
|
|
|
1915
|
+
// src/scripts/postReviewResult.ts
|
|
1916
|
+
function detectVerdict(body) {
|
|
1917
|
+
const m = body.match(/##\s*Verdict\s*:\s*(PASS|CONCERNS|FAIL)\b/i);
|
|
1918
|
+
if (!m) return "UNKNOWN";
|
|
1919
|
+
return m[1].toUpperCase();
|
|
1920
|
+
}
|
|
1921
|
+
var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
1922
|
+
const prNumber = ctx.data.commentTargetNumber;
|
|
1923
|
+
if (!prNumber) {
|
|
1924
|
+
ctx.output.exitCode = 99;
|
|
1925
|
+
ctx.output.reason = "review postflight: no PR number in context";
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
if (!agentResult || agentResult.outcome !== "completed") {
|
|
1929
|
+
const reason = agentResult?.error ?? "agent did not complete";
|
|
1930
|
+
try {
|
|
1931
|
+
postPrReviewComment(prNumber, `\u26A0\uFE0F kody2 review FAILED: ${truncate2(reason, 1e3)}`, ctx.cwd);
|
|
1932
|
+
} catch {
|
|
1933
|
+
}
|
|
1934
|
+
ctx.output.exitCode = 1;
|
|
1935
|
+
ctx.output.reason = reason;
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
const reviewBody = agentResult.finalText.trim();
|
|
1939
|
+
if (!reviewBody) {
|
|
1940
|
+
try {
|
|
1941
|
+
postPrReviewComment(prNumber, `\u26A0\uFE0F kody2 review FAILED: agent produced no review body`, ctx.cwd);
|
|
1942
|
+
} catch {
|
|
1943
|
+
}
|
|
1944
|
+
ctx.output.exitCode = 1;
|
|
1945
|
+
ctx.output.reason = "empty review body";
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
try {
|
|
1949
|
+
postPrReviewComment(prNumber, reviewBody, ctx.cwd);
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1952
|
+
ctx.output.exitCode = 4;
|
|
1953
|
+
ctx.output.reason = `failed to post review comment: ${msg}`;
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
const verdict = detectVerdict(reviewBody);
|
|
1957
|
+
ctx.data.reviewVerdict = verdict;
|
|
1958
|
+
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
1959
|
+
process.stdout.write(`
|
|
1960
|
+
REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/pull/${prNumber} (verdict: ${verdict})
|
|
1961
|
+
`);
|
|
1962
|
+
};
|
|
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
|
+
|
|
1901
2266
|
// src/scripts/resolveFlow.ts
|
|
1902
|
-
import { execFileSync as
|
|
2267
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
1903
2268
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
1904
2269
|
var resolveFlow = async (ctx) => {
|
|
1905
2270
|
const prNumber = ctx.args.pr;
|
|
@@ -1951,7 +2316,7 @@ var resolveFlow = async (ctx) => {
|
|
|
1951
2316
|
};
|
|
1952
2317
|
function getConflictedFiles(cwd) {
|
|
1953
2318
|
try {
|
|
1954
|
-
const out =
|
|
2319
|
+
const out = execFileSync11("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
1955
2320
|
encoding: "utf-8",
|
|
1956
2321
|
cwd,
|
|
1957
2322
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -1966,7 +2331,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
1966
2331
|
let total = 0;
|
|
1967
2332
|
for (const f of files) {
|
|
1968
2333
|
try {
|
|
1969
|
-
const content =
|
|
2334
|
+
const content = execFileSync11("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
1970
2335
|
const snippet = `### ${f}
|
|
1971
2336
|
|
|
1972
2337
|
\`\`\`
|
|
@@ -1988,6 +2353,33 @@ function tryPostPr3(prNumber, body, cwd) {
|
|
|
1988
2353
|
}
|
|
1989
2354
|
}
|
|
1990
2355
|
|
|
2356
|
+
// src/scripts/reviewFlow.ts
|
|
2357
|
+
var reviewFlow = async (ctx) => {
|
|
2358
|
+
const prNumber = ctx.args.pr;
|
|
2359
|
+
const pr = getPr(prNumber, ctx.cwd);
|
|
2360
|
+
if (pr.state !== "OPEN") {
|
|
2361
|
+
ctx.output.exitCode = 1;
|
|
2362
|
+
ctx.output.reason = `PR #${prNumber} is not OPEN (state: ${pr.state})`;
|
|
2363
|
+
ctx.skipAgent = true;
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
ctx.data.pr = pr;
|
|
2367
|
+
ctx.data.commentTargetType = "pr";
|
|
2368
|
+
ctx.data.commentTargetNumber = prNumber;
|
|
2369
|
+
checkoutPrBranch(prNumber, ctx.cwd);
|
|
2370
|
+
ctx.data.branch = getCurrentBranch(ctx.cwd);
|
|
2371
|
+
ctx.data.prDiff = getPrDiff(prNumber, ctx.cwd);
|
|
2372
|
+
const runUrl = getRunUrl();
|
|
2373
|
+
const runSuffix = runUrl ? `, run ${runUrl}` : "";
|
|
2374
|
+
tryPostPr4(prNumber, `\u{1F440} kody2 review started on PR #${prNumber}${runSuffix}`, ctx.cwd);
|
|
2375
|
+
};
|
|
2376
|
+
function tryPostPr4(prNumber, body, cwd) {
|
|
2377
|
+
try {
|
|
2378
|
+
postPrReviewComment(prNumber, body, cwd);
|
|
2379
|
+
} catch {
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
|
|
1991
2383
|
// src/scripts/runFlow.ts
|
|
1992
2384
|
var runFlow = async (ctx) => {
|
|
1993
2385
|
const issueNumber = ctx.args.issue;
|
|
@@ -2104,7 +2496,7 @@ var verify = async (ctx) => {
|
|
|
2104
2496
|
};
|
|
2105
2497
|
|
|
2106
2498
|
// src/scripts/writeRunSummary.ts
|
|
2107
|
-
import * as
|
|
2499
|
+
import * as fs11 from "fs";
|
|
2108
2500
|
var writeRunSummary = async (ctx) => {
|
|
2109
2501
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
2110
2502
|
if (!summaryPath) return;
|
|
@@ -2126,7 +2518,7 @@ var writeRunSummary = async (ctx) => {
|
|
|
2126
2518
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
2127
2519
|
lines.push("");
|
|
2128
2520
|
try {
|
|
2129
|
-
|
|
2521
|
+
fs11.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
2130
2522
|
`);
|
|
2131
2523
|
} catch {
|
|
2132
2524
|
}
|
|
@@ -2138,7 +2530,9 @@ var preflightScripts = {
|
|
|
2138
2530
|
fixFlow,
|
|
2139
2531
|
fixCiFlow,
|
|
2140
2532
|
resolveFlow,
|
|
2533
|
+
reviewFlow,
|
|
2141
2534
|
initFlow,
|
|
2535
|
+
releaseFlow,
|
|
2142
2536
|
loadConventions,
|
|
2143
2537
|
loadCoverageRules,
|
|
2144
2538
|
composePrompt
|
|
@@ -2150,6 +2544,7 @@ var postflightScripts = {
|
|
|
2150
2544
|
commitAndPush: commitAndPush2,
|
|
2151
2545
|
ensurePr: ensurePr2,
|
|
2152
2546
|
postIssueComment: postIssueComment2,
|
|
2547
|
+
postReviewResult,
|
|
2153
2548
|
writeRunSummary
|
|
2154
2549
|
};
|
|
2155
2550
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
@@ -2158,7 +2553,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
2158
2553
|
]);
|
|
2159
2554
|
|
|
2160
2555
|
// src/tools.ts
|
|
2161
|
-
import { execFileSync as
|
|
2556
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
2162
2557
|
function verifyCliTools(tools, cwd) {
|
|
2163
2558
|
const out = [];
|
|
2164
2559
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -2174,24 +2569,24 @@ function firstRequiredFailure(results, tools) {
|
|
|
2174
2569
|
}
|
|
2175
2570
|
function verifyOne(tool, cwd) {
|
|
2176
2571
|
const result = { name: tool.name, present: false, verified: false };
|
|
2177
|
-
let present =
|
|
2572
|
+
let present = runShell2(tool.install.checkCommand, cwd);
|
|
2178
2573
|
if (!present && tool.install.installCommand) {
|
|
2179
|
-
|
|
2180
|
-
present =
|
|
2574
|
+
runShell2(tool.install.installCommand, cwd, 12e4);
|
|
2575
|
+
present = runShell2(tool.install.checkCommand, cwd);
|
|
2181
2576
|
}
|
|
2182
2577
|
result.present = present;
|
|
2183
2578
|
if (!present) {
|
|
2184
2579
|
result.error = `tool "${tool.name}" not on PATH (check: ${tool.install.checkCommand})`;
|
|
2185
2580
|
return result;
|
|
2186
2581
|
}
|
|
2187
|
-
const verified =
|
|
2582
|
+
const verified = runShell2(tool.verify, cwd);
|
|
2188
2583
|
result.verified = verified;
|
|
2189
2584
|
if (!verified) result.error = `tool "${tool.name}" failed verify: ${tool.verify}`;
|
|
2190
2585
|
return result;
|
|
2191
2586
|
}
|
|
2192
|
-
function
|
|
2587
|
+
function runShell2(cmd, cwd, timeoutMs = 3e4) {
|
|
2193
2588
|
try {
|
|
2194
|
-
|
|
2589
|
+
execFileSync12("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
2195
2590
|
return true;
|
|
2196
2591
|
} catch {
|
|
2197
2592
|
return false;
|
|
@@ -2242,7 +2637,7 @@ async function runExecutable(profileName, input) {
|
|
|
2242
2637
|
data: {},
|
|
2243
2638
|
output: { exitCode: 0 }
|
|
2244
2639
|
};
|
|
2245
|
-
const ndjsonDir =
|
|
2640
|
+
const ndjsonDir = path10.join(input.cwd, ".kody2");
|
|
2246
2641
|
const invokeAgent = async (prompt) => runAgent({
|
|
2247
2642
|
prompt,
|
|
2248
2643
|
model,
|
|
@@ -2301,17 +2696,17 @@ async function runExecutable(profileName, input) {
|
|
|
2301
2696
|
}
|
|
2302
2697
|
}
|
|
2303
2698
|
function resolveProfilePath(profileName) {
|
|
2304
|
-
const here =
|
|
2699
|
+
const here = path10.dirname(new URL(import.meta.url).pathname);
|
|
2305
2700
|
const candidates = [
|
|
2306
|
-
|
|
2701
|
+
path10.join(here, "executables", profileName, "profile.json"),
|
|
2307
2702
|
// same-dir sibling (dev)
|
|
2308
|
-
|
|
2703
|
+
path10.join(here, "..", "executables", profileName, "profile.json"),
|
|
2309
2704
|
// up one (prod: dist/bin → dist/executables)
|
|
2310
|
-
|
|
2705
|
+
path10.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
2311
2706
|
// fallback
|
|
2312
2707
|
];
|
|
2313
2708
|
for (const c of candidates) {
|
|
2314
|
-
if (
|
|
2709
|
+
if (fs12.existsSync(c)) return c;
|
|
2315
2710
|
}
|
|
2316
2711
|
return candidates[0];
|
|
2317
2712
|
}
|
|
@@ -2389,12 +2784,12 @@ function finish(out) {
|
|
|
2389
2784
|
}
|
|
2390
2785
|
|
|
2391
2786
|
// src/kody2-cli.ts
|
|
2392
|
-
import { execFileSync as
|
|
2393
|
-
import * as
|
|
2394
|
-
import * as
|
|
2787
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
2788
|
+
import * as fs14 from "fs";
|
|
2789
|
+
import * as path11 from "path";
|
|
2395
2790
|
|
|
2396
2791
|
// src/dispatch.ts
|
|
2397
|
-
import * as
|
|
2792
|
+
import * as fs13 from "fs";
|
|
2398
2793
|
function autoDispatch(explicit) {
|
|
2399
2794
|
if (explicit?.mode && explicit.target) {
|
|
2400
2795
|
return {
|
|
@@ -2404,10 +2799,10 @@ function autoDispatch(explicit) {
|
|
|
2404
2799
|
}
|
|
2405
2800
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
2406
2801
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2407
|
-
if (!eventName || !eventPath || !
|
|
2802
|
+
if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
|
|
2408
2803
|
let event = {};
|
|
2409
2804
|
try {
|
|
2410
|
-
event = JSON.parse(
|
|
2805
|
+
event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
|
|
2411
2806
|
} catch {
|
|
2412
2807
|
return null;
|
|
2413
2808
|
}
|
|
@@ -2527,14 +2922,14 @@ function resolveAuthToken(env = process.env) {
|
|
|
2527
2922
|
return token;
|
|
2528
2923
|
}
|
|
2529
2924
|
function detectPackageManager2(cwd) {
|
|
2530
|
-
if (
|
|
2531
|
-
if (
|
|
2532
|
-
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";
|
|
2533
2928
|
return "npm";
|
|
2534
2929
|
}
|
|
2535
2930
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
2536
2931
|
try {
|
|
2537
|
-
|
|
2932
|
+
execFileSync13(cmd, args, {
|
|
2538
2933
|
cwd,
|
|
2539
2934
|
stdio: stream ? "inherit" : "pipe",
|
|
2540
2935
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -2547,7 +2942,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
2547
2942
|
}
|
|
2548
2943
|
function isOnPath(bin) {
|
|
2549
2944
|
try {
|
|
2550
|
-
|
|
2945
|
+
execFileSync13("which", [bin], { stdio: "pipe" });
|
|
2551
2946
|
return true;
|
|
2552
2947
|
} catch {
|
|
2553
2948
|
return false;
|
|
@@ -2581,7 +2976,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
2581
2976
|
} catch {
|
|
2582
2977
|
}
|
|
2583
2978
|
try {
|
|
2584
|
-
|
|
2979
|
+
execFileSync13("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
2585
2980
|
process.stdout.write("\u2192 kody2: litellm already installed\n");
|
|
2586
2981
|
return 0;
|
|
2587
2982
|
} catch {
|
|
@@ -2591,26 +2986,26 @@ function installLitellmIfNeeded(cwd) {
|
|
|
2591
2986
|
}
|
|
2592
2987
|
function configureGitIdentity(cwd) {
|
|
2593
2988
|
try {
|
|
2594
|
-
const name =
|
|
2989
|
+
const name = execFileSync13("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
2595
2990
|
if (name) return;
|
|
2596
2991
|
} catch {
|
|
2597
2992
|
}
|
|
2598
2993
|
try {
|
|
2599
|
-
|
|
2994
|
+
execFileSync13("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
|
|
2600
2995
|
} catch {
|
|
2601
2996
|
}
|
|
2602
2997
|
try {
|
|
2603
|
-
|
|
2998
|
+
execFileSync13("git", ["config", "user.email", "kody2-bot@users.noreply.github.com"], { cwd, stdio: "pipe" });
|
|
2604
2999
|
} catch {
|
|
2605
3000
|
}
|
|
2606
3001
|
}
|
|
2607
3002
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
2608
3003
|
if (!issueNumber) return;
|
|
2609
|
-
const logPath =
|
|
3004
|
+
const logPath = path11.join(cwd, ".kody2", "last-run.jsonl");
|
|
2610
3005
|
let tail = "";
|
|
2611
3006
|
try {
|
|
2612
|
-
if (
|
|
2613
|
-
const content =
|
|
3007
|
+
if (fs14.existsSync(logPath)) {
|
|
3008
|
+
const content = fs14.readFileSync(logPath, "utf-8");
|
|
2614
3009
|
tail = content.slice(-3e3);
|
|
2615
3010
|
}
|
|
2616
3011
|
} catch {
|
|
@@ -2647,7 +3042,7 @@ async function runCi(argv) {
|
|
|
2647
3042
|
${CI_HELP}`);
|
|
2648
3043
|
return 64;
|
|
2649
3044
|
}
|
|
2650
|
-
const cwd = args.cwd ?
|
|
3045
|
+
const cwd = args.cwd ? path11.resolve(args.cwd) : process.cwd();
|
|
2651
3046
|
const dispatch = autoFallback ?? {
|
|
2652
3047
|
mode: "run",
|
|
2653
3048
|
target: args.issueNumber,
|
|
@@ -2723,31 +3118,31 @@ ${CI_HELP}`);
|
|
|
2723
3118
|
}
|
|
2724
3119
|
|
|
2725
3120
|
// src/registry.ts
|
|
2726
|
-
import * as
|
|
2727
|
-
import * as
|
|
3121
|
+
import * as fs15 from "fs";
|
|
3122
|
+
import * as path12 from "path";
|
|
2728
3123
|
function getExecutablesRoot() {
|
|
2729
|
-
const here =
|
|
3124
|
+
const here = path12.dirname(new URL(import.meta.url).pathname);
|
|
2730
3125
|
const candidates = [
|
|
2731
|
-
|
|
3126
|
+
path12.join(here, "executables"),
|
|
2732
3127
|
// dev: src/
|
|
2733
|
-
|
|
3128
|
+
path12.join(here, "..", "executables"),
|
|
2734
3129
|
// built: dist/bin → dist/executables
|
|
2735
|
-
|
|
3130
|
+
path12.join(here, "..", "src", "executables")
|
|
2736
3131
|
// fallback
|
|
2737
3132
|
];
|
|
2738
3133
|
for (const c of candidates) {
|
|
2739
|
-
if (
|
|
3134
|
+
if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
|
|
2740
3135
|
}
|
|
2741
3136
|
return candidates[0];
|
|
2742
3137
|
}
|
|
2743
3138
|
function listExecutables(root = getExecutablesRoot()) {
|
|
2744
|
-
if (!
|
|
2745
|
-
const entries =
|
|
3139
|
+
if (!fs15.existsSync(root)) return [];
|
|
3140
|
+
const entries = fs15.readdirSync(root, { withFileTypes: true });
|
|
2746
3141
|
const out = [];
|
|
2747
3142
|
for (const ent of entries) {
|
|
2748
3143
|
if (!ent.isDirectory()) continue;
|
|
2749
|
-
const profilePath =
|
|
2750
|
-
if (
|
|
3144
|
+
const profilePath = path12.join(root, ent.name, "profile.json");
|
|
3145
|
+
if (fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile()) {
|
|
2751
3146
|
out.push({ name: ent.name, profilePath });
|
|
2752
3147
|
}
|
|
2753
3148
|
}
|
|
@@ -2755,8 +3150,8 @@ function listExecutables(root = getExecutablesRoot()) {
|
|
|
2755
3150
|
}
|
|
2756
3151
|
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
2757
3152
|
if (!isSafeName(name)) return false;
|
|
2758
|
-
const profilePath =
|
|
2759
|
-
return
|
|
3153
|
+
const profilePath = path12.join(root, name, "profile.json");
|
|
3154
|
+
return fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile();
|
|
2760
3155
|
}
|
|
2761
3156
|
function isSafeName(name) {
|
|
2762
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
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "review",
|
|
3
|
+
"describe": "Read-only structured review of an open PR. Posts one comment, never commits.",
|
|
4
|
+
|
|
5
|
+
"inputs": [
|
|
6
|
+
{
|
|
7
|
+
"name": "pr",
|
|
8
|
+
"flag": "--pr",
|
|
9
|
+
"type": "int",
|
|
10
|
+
"required": true,
|
|
11
|
+
"describe": "GitHub PR number to review."
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
|
|
15
|
+
"claudeCode": {
|
|
16
|
+
"model": "inherit",
|
|
17
|
+
"permissionMode": "default",
|
|
18
|
+
"maxTurns": null,
|
|
19
|
+
"systemPromptAppend": null,
|
|
20
|
+
"tools": ["Read", "Grep", "Glob", "Bash"],
|
|
21
|
+
"hooks": { "PreToolUse": [], "PostToolUse": [], "Stop": [] },
|
|
22
|
+
"skills": [],
|
|
23
|
+
"commands": [],
|
|
24
|
+
"subagents": [],
|
|
25
|
+
"plugins": [],
|
|
26
|
+
"mcpServers": []
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
"cliTools": [],
|
|
30
|
+
|
|
31
|
+
"scripts": {
|
|
32
|
+
"preflight": [
|
|
33
|
+
{ "script": "reviewFlow" },
|
|
34
|
+
{ "script": "loadConventions" },
|
|
35
|
+
{ "script": "composePrompt" }
|
|
36
|
+
],
|
|
37
|
+
"postflight": [
|
|
38
|
+
{ "script": "postReviewResult" }
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
You are Kody, a senior code reviewer. Review PR #{{pr.number}} carefully and post ONE structured review comment. Do NOT edit any files. Do NOT run any `git` or `gh` commands. Use Read / Grep / Glob / Bash only to inspect the diff and surrounding code.
|
|
2
|
+
|
|
3
|
+
# PR #{{pr.number}}: {{pr.title}}
|
|
4
|
+
|
|
5
|
+
Base: {{pr.baseRefName}} ← Head: {{pr.headRefName}}
|
|
6
|
+
|
|
7
|
+
{{pr.body}}
|
|
8
|
+
|
|
9
|
+
{{conventionsBlock}}
|
|
10
|
+
|
|
11
|
+
# Diff
|
|
12
|
+
|
|
13
|
+
```diff
|
|
14
|
+
{{prDiff}}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
# Required output
|
|
18
|
+
|
|
19
|
+
Your FINAL message must be a markdown-formatted review comment, **structured exactly as below** — no preamble, no DONE / COMMIT_MSG / PR_SUMMARY markers. The entire final message IS the review comment and will be posted verbatim:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
## Verdict: PASS | CONCERNS | FAIL
|
|
23
|
+
|
|
24
|
+
### Summary
|
|
25
|
+
<2-3 sentences: what this PR does, is the approach sound>
|
|
26
|
+
|
|
27
|
+
### Strengths
|
|
28
|
+
- <bullet>
|
|
29
|
+
- <bullet>
|
|
30
|
+
|
|
31
|
+
### Concerns
|
|
32
|
+
- <bullet, or "None" if none>
|
|
33
|
+
|
|
34
|
+
### Suggestions
|
|
35
|
+
- <bullet with file:line reference where possible>
|
|
36
|
+
|
|
37
|
+
### Bottom line
|
|
38
|
+
<one sentence>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
# Rules
|
|
42
|
+
|
|
43
|
+
- No file edits. No `git`/`gh` invocations. Read-only investigation.
|
|
44
|
+
- Be specific: cite file paths and line numbers. No generic advice.
|
|
45
|
+
- Verdict **FAIL** only for clear correctness / security / regression risks.
|
|
46
|
+
- Verdict **CONCERNS** for style / clarity / test-coverage gaps that shouldn't block.
|
|
47
|
+
- Verdict **PASS** when the PR meets spec with no blocking issues.
|
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",
|