@kody-ade/kody-engine 0.2.6 → 0.2.8
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.8",
|
|
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",
|
|
@@ -151,8 +151,8 @@ function getAnthropicApiKeyOrDummy() {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
// src/executor.ts
|
|
154
|
-
import * as
|
|
155
|
-
import * as
|
|
154
|
+
import * as fs13 from "fs";
|
|
155
|
+
import * as path11 from "path";
|
|
156
156
|
|
|
157
157
|
// src/agent.ts
|
|
158
158
|
import * as fs2 from "fs";
|
|
@@ -463,9 +463,15 @@ function loadProfile(profilePath) {
|
|
|
463
463
|
throw new ProfileError(profilePath, "profile must be a JSON object");
|
|
464
464
|
}
|
|
465
465
|
const r = raw;
|
|
466
|
+
const kind = r.kind === "scheduled" ? "scheduled" : "oneshot";
|
|
467
|
+
if (kind === "scheduled" && typeof r.schedule !== "string") {
|
|
468
|
+
throw new ProfileError(profilePath, `kind: "scheduled" requires a "schedule" cron string`);
|
|
469
|
+
}
|
|
466
470
|
const profile = {
|
|
467
471
|
name: requireString(profilePath, r, "name"),
|
|
468
472
|
describe: typeof r.describe === "string" ? r.describe : "",
|
|
473
|
+
kind,
|
|
474
|
+
schedule: typeof r.schedule === "string" ? r.schedule : void 0,
|
|
469
475
|
inputs: parseInputs(profilePath, r.inputs),
|
|
470
476
|
claudeCode: parseClaudeCode(profilePath, r.claudeCode),
|
|
471
477
|
cliTools: parseCliTools(profilePath, r.cliTools),
|
|
@@ -1664,12 +1670,75 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
1664
1670
|
|
|
1665
1671
|
// src/scripts/initFlow.ts
|
|
1666
1672
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
1673
|
+
import * as fs10 from "fs";
|
|
1674
|
+
import * as path9 from "path";
|
|
1675
|
+
|
|
1676
|
+
// src/registry.ts
|
|
1667
1677
|
import * as fs9 from "fs";
|
|
1668
1678
|
import * as path8 from "path";
|
|
1679
|
+
function getExecutablesRoot() {
|
|
1680
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
1681
|
+
const candidates = [
|
|
1682
|
+
path8.join(here, "executables"),
|
|
1683
|
+
// dev: src/
|
|
1684
|
+
path8.join(here, "..", "executables"),
|
|
1685
|
+
// built: dist/bin → dist/executables
|
|
1686
|
+
path8.join(here, "..", "src", "executables")
|
|
1687
|
+
// fallback
|
|
1688
|
+
];
|
|
1689
|
+
for (const c of candidates) {
|
|
1690
|
+
if (fs9.existsSync(c) && fs9.statSync(c).isDirectory()) return c;
|
|
1691
|
+
}
|
|
1692
|
+
return candidates[0];
|
|
1693
|
+
}
|
|
1694
|
+
function listExecutables(root = getExecutablesRoot()) {
|
|
1695
|
+
if (!fs9.existsSync(root)) return [];
|
|
1696
|
+
const entries = fs9.readdirSync(root, { withFileTypes: true });
|
|
1697
|
+
const out = [];
|
|
1698
|
+
for (const ent of entries) {
|
|
1699
|
+
if (!ent.isDirectory()) continue;
|
|
1700
|
+
const profilePath = path8.join(root, ent.name, "profile.json");
|
|
1701
|
+
if (fs9.existsSync(profilePath) && fs9.statSync(profilePath).isFile()) {
|
|
1702
|
+
out.push({ name: ent.name, profilePath });
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
1706
|
+
}
|
|
1707
|
+
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
1708
|
+
if (!isSafeName(name)) return false;
|
|
1709
|
+
const profilePath = path8.join(root, name, "profile.json");
|
|
1710
|
+
return fs9.existsSync(profilePath) && fs9.statSync(profilePath).isFile();
|
|
1711
|
+
}
|
|
1712
|
+
function isSafeName(name) {
|
|
1713
|
+
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
1714
|
+
}
|
|
1715
|
+
function parseGenericFlags(argv) {
|
|
1716
|
+
const args = {};
|
|
1717
|
+
const positional = [];
|
|
1718
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1719
|
+
const arg = argv[i];
|
|
1720
|
+
if (!arg.startsWith("--")) {
|
|
1721
|
+
positional.push(arg);
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
const key = arg.slice(2);
|
|
1725
|
+
const next = argv[i + 1];
|
|
1726
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
1727
|
+
args[key] = next;
|
|
1728
|
+
i++;
|
|
1729
|
+
} else {
|
|
1730
|
+
args[key] = true;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
if (positional.length > 0) args._ = positional;
|
|
1734
|
+
return args;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// src/scripts/initFlow.ts
|
|
1669
1738
|
function detectPackageManager(cwd) {
|
|
1670
|
-
if (
|
|
1671
|
-
if (
|
|
1672
|
-
if (
|
|
1739
|
+
if (fs10.existsSync(path9.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1740
|
+
if (fs10.existsSync(path9.join(cwd, "yarn.lock"))) return "yarn";
|
|
1741
|
+
if (fs10.existsSync(path9.join(cwd, "bun.lockb"))) return "bun";
|
|
1673
1742
|
return "npm";
|
|
1674
1743
|
}
|
|
1675
1744
|
function qualityCommandsFor(pm) {
|
|
@@ -1790,26 +1859,74 @@ function performInit(cwd, force) {
|
|
|
1790
1859
|
const pm = detectPackageManager(cwd);
|
|
1791
1860
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
1792
1861
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
1793
|
-
const configPath =
|
|
1794
|
-
if (
|
|
1862
|
+
const configPath = path9.join(cwd, "kody.config.json");
|
|
1863
|
+
if (fs10.existsSync(configPath) && !force) {
|
|
1795
1864
|
skipped.push("kody.config.json");
|
|
1796
1865
|
} else {
|
|
1797
1866
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
1798
|
-
|
|
1867
|
+
fs10.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
1799
1868
|
`);
|
|
1800
1869
|
wrote.push("kody.config.json");
|
|
1801
1870
|
}
|
|
1802
|
-
const workflowDir =
|
|
1803
|
-
const workflowPath =
|
|
1804
|
-
if (
|
|
1871
|
+
const workflowDir = path9.join(cwd, ".github", "workflows");
|
|
1872
|
+
const workflowPath = path9.join(workflowDir, "kody2.yml");
|
|
1873
|
+
if (fs10.existsSync(workflowPath) && !force) {
|
|
1805
1874
|
skipped.push(".github/workflows/kody2.yml");
|
|
1806
1875
|
} else {
|
|
1807
|
-
|
|
1808
|
-
|
|
1876
|
+
fs10.mkdirSync(workflowDir, { recursive: true });
|
|
1877
|
+
fs10.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
1809
1878
|
wrote.push(".github/workflows/kody2.yml");
|
|
1810
1879
|
}
|
|
1880
|
+
for (const exe of listExecutables()) {
|
|
1881
|
+
let profile;
|
|
1882
|
+
try {
|
|
1883
|
+
profile = loadProfile(exe.profilePath);
|
|
1884
|
+
} catch {
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
1888
|
+
const target = path9.join(workflowDir, `kody2-${exe.name}.yml`);
|
|
1889
|
+
if (fs10.existsSync(target) && !force) {
|
|
1890
|
+
skipped.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
fs10.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
1894
|
+
wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
1895
|
+
}
|
|
1811
1896
|
return { wrote, skipped };
|
|
1812
1897
|
}
|
|
1898
|
+
function renderScheduledWorkflow(name, cron) {
|
|
1899
|
+
return `# Scheduled kody2 executable: ${name}
|
|
1900
|
+
# Generated by \`kody2 init\`. Regenerate with \`kody2 init --force\`.
|
|
1901
|
+
# Edit the cron below or the executable's profile.json#schedule.
|
|
1902
|
+
|
|
1903
|
+
name: kody2 ${name}
|
|
1904
|
+
|
|
1905
|
+
on:
|
|
1906
|
+
schedule:
|
|
1907
|
+
- cron: "${cron}"
|
|
1908
|
+
workflow_dispatch:
|
|
1909
|
+
|
|
1910
|
+
jobs:
|
|
1911
|
+
run:
|
|
1912
|
+
runs-on: ubuntu-latest
|
|
1913
|
+
timeout-minutes: 30
|
|
1914
|
+
permissions:
|
|
1915
|
+
issues: write
|
|
1916
|
+
pull-requests: read
|
|
1917
|
+
contents: read
|
|
1918
|
+
steps:
|
|
1919
|
+
- uses: actions/checkout@v4
|
|
1920
|
+
with:
|
|
1921
|
+
token: \${{ secrets.KODY_TOKEN || github.token }}
|
|
1922
|
+
- uses: actions/setup-node@v4
|
|
1923
|
+
with:
|
|
1924
|
+
node-version: 22
|
|
1925
|
+
- env:
|
|
1926
|
+
GH_TOKEN: \${{ secrets.KODY_TOKEN || github.token }}
|
|
1927
|
+
run: npx -y -p @kody-ade/kody-engine@latest kody2 ${name}
|
|
1928
|
+
`;
|
|
1929
|
+
}
|
|
1813
1930
|
var initFlow = async (ctx) => {
|
|
1814
1931
|
const force = ctx.args.force === true;
|
|
1815
1932
|
const cwd = ctx.cwd;
|
|
@@ -1845,10 +1962,201 @@ var loadCoverageRules = async (ctx) => {
|
|
|
1845
1962
|
ctx.data.coverageRules = ctx.config.testRequirements ?? [];
|
|
1846
1963
|
};
|
|
1847
1964
|
|
|
1965
|
+
// src/state.ts
|
|
1966
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
1967
|
+
var STATE_BEGIN = "<!-- kody2:state:v1:begin -->";
|
|
1968
|
+
var STATE_END = "<!-- kody2:state:v1:end -->";
|
|
1969
|
+
var HISTORY_MAX_ENTRIES = 20;
|
|
1970
|
+
var API_TIMEOUT_MS2 = 3e4;
|
|
1971
|
+
function emptyState() {
|
|
1972
|
+
return {
|
|
1973
|
+
schemaVersion: 1,
|
|
1974
|
+
core: {
|
|
1975
|
+
phase: "idle",
|
|
1976
|
+
status: "pending",
|
|
1977
|
+
currentExecutable: null,
|
|
1978
|
+
lastOutcome: null,
|
|
1979
|
+
attempts: {}
|
|
1980
|
+
},
|
|
1981
|
+
executables: {},
|
|
1982
|
+
history: []
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
function ghToken3() {
|
|
1986
|
+
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
1987
|
+
}
|
|
1988
|
+
function gh3(args, input, cwd) {
|
|
1989
|
+
const token = ghToken3();
|
|
1990
|
+
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
1991
|
+
return execFileSync10("gh", args, {
|
|
1992
|
+
encoding: "utf-8",
|
|
1993
|
+
timeout: API_TIMEOUT_MS2,
|
|
1994
|
+
cwd,
|
|
1995
|
+
env,
|
|
1996
|
+
input,
|
|
1997
|
+
stdio: input ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"]
|
|
1998
|
+
}).trim();
|
|
1999
|
+
}
|
|
2000
|
+
function findStateComment(target, number, cwd) {
|
|
2001
|
+
const apiPath = target === "issue" ? `repos/{owner}/{repo}/issues/${number}/comments` : `repos/{owner}/{repo}/issues/${number}/comments`;
|
|
2002
|
+
try {
|
|
2003
|
+
const raw = gh3(["api", "--paginate", apiPath], void 0, cwd);
|
|
2004
|
+
const list = JSON.parse(raw);
|
|
2005
|
+
for (const c of list) {
|
|
2006
|
+
if (c.body?.includes(STATE_BEGIN)) {
|
|
2007
|
+
return { id: String(c.id), body: c.body };
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
} catch {
|
|
2011
|
+
}
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
function parseStateComment(body) {
|
|
2015
|
+
const beginIdx = body.indexOf(STATE_BEGIN);
|
|
2016
|
+
const endIdx = body.indexOf(STATE_END, beginIdx + 1);
|
|
2017
|
+
if (beginIdx < 0 || endIdx < 0) return emptyState();
|
|
2018
|
+
const between = body.slice(beginIdx + STATE_BEGIN.length, endIdx);
|
|
2019
|
+
const fenceMatch = between.match(/```json\s*([\s\S]*?)\s*```/);
|
|
2020
|
+
if (!fenceMatch) return emptyState();
|
|
2021
|
+
try {
|
|
2022
|
+
const parsed = JSON.parse(fenceMatch[1]);
|
|
2023
|
+
if (parsed?.schemaVersion !== 1) return emptyState();
|
|
2024
|
+
return {
|
|
2025
|
+
schemaVersion: 1,
|
|
2026
|
+
core: { ...emptyState().core, ...parsed.core },
|
|
2027
|
+
executables: parsed.executables ?? {},
|
|
2028
|
+
history: Array.isArray(parsed.history) ? parsed.history : []
|
|
2029
|
+
};
|
|
2030
|
+
} catch {
|
|
2031
|
+
return emptyState();
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
function reduce(state, executable, action) {
|
|
2035
|
+
if (!action) return state;
|
|
2036
|
+
const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
|
|
2037
|
+
const newExecutables = {
|
|
2038
|
+
...state.executables,
|
|
2039
|
+
[executable]: { ...state.executables[executable] ?? { lastAction: null }, lastAction: action }
|
|
2040
|
+
};
|
|
2041
|
+
const newHistory = [
|
|
2042
|
+
...state.history,
|
|
2043
|
+
{ timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action) }
|
|
2044
|
+
].slice(-HISTORY_MAX_ENTRIES);
|
|
2045
|
+
return {
|
|
2046
|
+
schemaVersion: 1,
|
|
2047
|
+
core: {
|
|
2048
|
+
...state.core,
|
|
2049
|
+
attempts: newAttempts,
|
|
2050
|
+
lastOutcome: action,
|
|
2051
|
+
currentExecutable: executable,
|
|
2052
|
+
status: statusFromAction(action),
|
|
2053
|
+
phase: phaseFromAction(executable, action)
|
|
2054
|
+
},
|
|
2055
|
+
executables: newExecutables,
|
|
2056
|
+
history: newHistory
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
function statusFromAction(action) {
|
|
2060
|
+
if (/FAILED$|ERROR$|MISSING$|REJECTED$/i.test(action.type)) return "failed";
|
|
2061
|
+
if (/COMPLETED$|SHIPPED$|MERGED$|SUCCESS$/i.test(action.type)) return "succeeded";
|
|
2062
|
+
return "running";
|
|
2063
|
+
}
|
|
2064
|
+
function phaseFromAction(executable, action) {
|
|
2065
|
+
if (/FAILED$|ERROR$|REJECTED$/i.test(action.type)) return "failed";
|
|
2066
|
+
if (executable === "build") return statusFromAction(action) === "succeeded" ? "implementing" : "implementing";
|
|
2067
|
+
if (executable === "review") return "reviewing";
|
|
2068
|
+
if (executable === "release") return "shipped";
|
|
2069
|
+
return "idle";
|
|
2070
|
+
}
|
|
2071
|
+
function noteFromAction(action) {
|
|
2072
|
+
const p = action.payload;
|
|
2073
|
+
if (typeof p?.prUrl === "string") return p.prUrl;
|
|
2074
|
+
if (typeof p?.reason === "string") return p.reason.slice(0, 120);
|
|
2075
|
+
if (typeof p?.commitMessage === "string") return p.commitMessage.slice(0, 120);
|
|
2076
|
+
return void 0;
|
|
2077
|
+
}
|
|
2078
|
+
function renderStateComment(state) {
|
|
2079
|
+
const lines = [];
|
|
2080
|
+
lines.push(STATE_BEGIN);
|
|
2081
|
+
lines.push("");
|
|
2082
|
+
lines.push("```json");
|
|
2083
|
+
lines.push(JSON.stringify(
|
|
2084
|
+
{ schemaVersion: state.schemaVersion, core: state.core, executables: state.executables, history: state.history },
|
|
2085
|
+
null,
|
|
2086
|
+
2
|
|
2087
|
+
));
|
|
2088
|
+
lines.push("```");
|
|
2089
|
+
lines.push("");
|
|
2090
|
+
lines.push(STATE_END);
|
|
2091
|
+
lines.push("");
|
|
2092
|
+
lines.push("## kody2 task state");
|
|
2093
|
+
lines.push("");
|
|
2094
|
+
lines.push(`- **Phase:** \`${state.core.phase}\` **Status:** \`${state.core.status}\``);
|
|
2095
|
+
if (state.core.currentExecutable) {
|
|
2096
|
+
lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
|
|
2097
|
+
}
|
|
2098
|
+
if (state.core.lastOutcome) {
|
|
2099
|
+
lines.push(`- **Last action:** \`${state.core.lastOutcome.type}\``);
|
|
2100
|
+
}
|
|
2101
|
+
const attempts = Object.entries(state.core.attempts).map(([k, v]) => `${k}:${v}`).join(", ");
|
|
2102
|
+
if (attempts) lines.push(`- **Attempts:** ${attempts}`);
|
|
2103
|
+
if (state.core.prUrl) lines.push(`- **PR:** ${state.core.prUrl}`);
|
|
2104
|
+
if (state.core.runUrl) lines.push(`- **Run:** ${state.core.runUrl}`);
|
|
2105
|
+
lines.push("");
|
|
2106
|
+
if (state.history.length > 0) {
|
|
2107
|
+
lines.push("### Recent history");
|
|
2108
|
+
lines.push("");
|
|
2109
|
+
const recent = state.history.slice(-10).reverse();
|
|
2110
|
+
for (const h of recent) {
|
|
2111
|
+
const note = h.note ? ` \u2014 ${h.note}` : "";
|
|
2112
|
+
lines.push(`- \`${h.timestamp}\` **${h.executable}** \u2192 \`${h.action}\`${note}`);
|
|
2113
|
+
}
|
|
2114
|
+
lines.push("");
|
|
2115
|
+
}
|
|
2116
|
+
return lines.join("\n");
|
|
2117
|
+
}
|
|
2118
|
+
function readTaskState(target, number, cwd) {
|
|
2119
|
+
const existing = findStateComment(target, number, cwd);
|
|
2120
|
+
return existing ? parseStateComment(existing.body) : emptyState();
|
|
2121
|
+
}
|
|
2122
|
+
function writeTaskState(target, number, state, cwd) {
|
|
2123
|
+
const body = renderStateComment(state);
|
|
2124
|
+
const existing = findStateComment(target, number, cwd);
|
|
2125
|
+
try {
|
|
2126
|
+
if (existing) {
|
|
2127
|
+
gh3(
|
|
2128
|
+
["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"],
|
|
2129
|
+
body,
|
|
2130
|
+
cwd
|
|
2131
|
+
);
|
|
2132
|
+
} else {
|
|
2133
|
+
const sub = target === "issue" ? "issue" : "pr";
|
|
2134
|
+
gh3([sub, "comment", String(number), "--body-file", "-"], body, cwd);
|
|
2135
|
+
}
|
|
2136
|
+
} catch (err) {
|
|
2137
|
+
process.stderr.write(
|
|
2138
|
+
`[kody2 state] failed to write state on ${target} #${number}: ${err instanceof Error ? err.message : String(err)}
|
|
2139
|
+
`
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/scripts/loadTaskState.ts
|
|
2145
|
+
var loadTaskState = async (ctx) => {
|
|
2146
|
+
const target = ctx.data.commentTargetType;
|
|
2147
|
+
const number = ctx.data.commentTargetNumber;
|
|
2148
|
+
if (!target || !number) {
|
|
2149
|
+
ctx.data.taskState = emptyState();
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
ctx.data.taskState = readTaskState(target, number, ctx.cwd);
|
|
2153
|
+
};
|
|
2154
|
+
|
|
1848
2155
|
// src/scripts/parseAgentResult.ts
|
|
1849
|
-
var parseAgentResult2 = async (ctx,
|
|
2156
|
+
var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
1850
2157
|
if (!agentResult) {
|
|
1851
2158
|
ctx.data.agentDone = false;
|
|
2159
|
+
ctx.data.action = makeAction("AGENT_NOT_RUN", { reason: "no agent result" });
|
|
1852
2160
|
return;
|
|
1853
2161
|
}
|
|
1854
2162
|
const parsed = parseAgentResult(agentResult.finalText);
|
|
@@ -1858,7 +2166,21 @@ var parseAgentResult2 = async (ctx, _profile, agentResult) => {
|
|
|
1858
2166
|
ctx.data.agentFailureReason = parsed.failureReason;
|
|
1859
2167
|
ctx.data.agentOutcome = agentResult.outcome;
|
|
1860
2168
|
ctx.data.agentError = agentResult.error;
|
|
2169
|
+
const modeSeg = (ctx.args.mode ?? profile.name).replace(/-/g, "_").toUpperCase();
|
|
2170
|
+
if (parsed.done) {
|
|
2171
|
+
ctx.data.action = makeAction(`${modeSeg}_COMPLETED`, {
|
|
2172
|
+
commitMessage: parsed.commitMessage,
|
|
2173
|
+
prSummary: parsed.prSummary
|
|
2174
|
+
});
|
|
2175
|
+
} else {
|
|
2176
|
+
ctx.data.action = makeAction(`${modeSeg}_FAILED`, {
|
|
2177
|
+
reason: parsed.failureReason || agentResult.error || "unknown failure"
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
1861
2180
|
};
|
|
2181
|
+
function makeAction(type, payload) {
|
|
2182
|
+
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2183
|
+
}
|
|
1862
2184
|
|
|
1863
2185
|
// src/scripts/postIssueComment.ts
|
|
1864
2186
|
var postIssueComment2 = async (ctx) => {
|
|
@@ -1962,9 +2284,9 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
1962
2284
|
};
|
|
1963
2285
|
|
|
1964
2286
|
// src/scripts/releaseFlow.ts
|
|
1965
|
-
import { execFileSync as
|
|
1966
|
-
import * as
|
|
1967
|
-
import * as
|
|
2287
|
+
import { execFileSync as execFileSync11, spawnSync } from "child_process";
|
|
2288
|
+
import * as fs11 from "fs";
|
|
2289
|
+
import * as path10 from "path";
|
|
1968
2290
|
function bumpVersion(current, bump) {
|
|
1969
2291
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
1970
2292
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -1980,19 +2302,19 @@ function bumpVersion(current, bump) {
|
|
|
1980
2302
|
return `${major}.${minor}.${patch}`;
|
|
1981
2303
|
}
|
|
1982
2304
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
1983
|
-
const abs =
|
|
1984
|
-
if (!
|
|
1985
|
-
const content =
|
|
2305
|
+
const abs = path10.join(cwd, file);
|
|
2306
|
+
if (!fs11.existsSync(abs)) return false;
|
|
2307
|
+
const content = fs11.readFileSync(abs, "utf-8");
|
|
1986
2308
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
1987
2309
|
if (updated === content) return false;
|
|
1988
|
-
|
|
2310
|
+
fs11.writeFileSync(abs, updated);
|
|
1989
2311
|
return true;
|
|
1990
2312
|
}
|
|
1991
2313
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
1992
2314
|
const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
|
|
1993
2315
|
let log = "";
|
|
1994
2316
|
try {
|
|
1995
|
-
log =
|
|
2317
|
+
log = execFileSync11("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
|
|
1996
2318
|
cwd,
|
|
1997
2319
|
encoding: "utf-8",
|
|
1998
2320
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2033,23 +2355,23 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
2033
2355
|
return parts.join("\n");
|
|
2034
2356
|
}
|
|
2035
2357
|
function prependChangelog(cwd, entry) {
|
|
2036
|
-
const p =
|
|
2358
|
+
const p = path10.join(cwd, "CHANGELOG.md");
|
|
2037
2359
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
2038
|
-
if (
|
|
2039
|
-
const prior =
|
|
2360
|
+
if (fs11.existsSync(p)) {
|
|
2361
|
+
const prior = fs11.readFileSync(p, "utf-8");
|
|
2040
2362
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
2041
2363
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
2042
|
-
|
|
2364
|
+
fs11.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
2043
2365
|
${entry}${prior.slice(idx + 1)}`);
|
|
2044
2366
|
} else {
|
|
2045
|
-
|
|
2367
|
+
fs11.writeFileSync(p, `${header}${entry}${prior}`);
|
|
2046
2368
|
}
|
|
2047
2369
|
} else {
|
|
2048
|
-
|
|
2370
|
+
fs11.writeFileSync(p, `${header}${entry}`);
|
|
2049
2371
|
}
|
|
2050
2372
|
}
|
|
2051
2373
|
function git3(args, cwd, timeout = 6e4) {
|
|
2052
|
-
return
|
|
2374
|
+
return execFileSync11("git", args, {
|
|
2053
2375
|
encoding: "utf-8",
|
|
2054
2376
|
timeout,
|
|
2055
2377
|
cwd,
|
|
@@ -2096,13 +2418,13 @@ var releaseFlow = async (ctx) => {
|
|
|
2096
2418
|
};
|
|
2097
2419
|
async function runPrepare(args) {
|
|
2098
2420
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
2099
|
-
const pkgPath =
|
|
2100
|
-
if (!
|
|
2421
|
+
const pkgPath = path10.join(cwd, "package.json");
|
|
2422
|
+
if (!fs11.existsSync(pkgPath)) {
|
|
2101
2423
|
ctx.output.exitCode = 99;
|
|
2102
2424
|
ctx.output.reason = "release prepare: package.json not found";
|
|
2103
2425
|
return;
|
|
2104
2426
|
}
|
|
2105
|
-
const pkg = JSON.parse(
|
|
2427
|
+
const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
2106
2428
|
if (typeof pkg.version !== "string") {
|
|
2107
2429
|
ctx.output.exitCode = 99;
|
|
2108
2430
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -2173,8 +2495,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
2173
2495
|
}
|
|
2174
2496
|
async function runFinalize(args) {
|
|
2175
2497
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
2176
|
-
const pkgPath =
|
|
2177
|
-
const pkg = JSON.parse(
|
|
2498
|
+
const pkgPath = path10.join(cwd, "package.json");
|
|
2499
|
+
const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
2178
2500
|
if (typeof pkg.version !== "string") {
|
|
2179
2501
|
ctx.output.exitCode = 99;
|
|
2180
2502
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -2264,7 +2586,7 @@ ${truncate2(r.stderr, 2e3)}
|
|
|
2264
2586
|
}
|
|
2265
2587
|
|
|
2266
2588
|
// src/scripts/resolveFlow.ts
|
|
2267
|
-
import { execFileSync as
|
|
2589
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
2268
2590
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
2269
2591
|
var resolveFlow = async (ctx) => {
|
|
2270
2592
|
const prNumber = ctx.args.pr;
|
|
@@ -2316,7 +2638,7 @@ var resolveFlow = async (ctx) => {
|
|
|
2316
2638
|
};
|
|
2317
2639
|
function getConflictedFiles(cwd) {
|
|
2318
2640
|
try {
|
|
2319
|
-
const out =
|
|
2641
|
+
const out = execFileSync12("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
2320
2642
|
encoding: "utf-8",
|
|
2321
2643
|
cwd,
|
|
2322
2644
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -2331,7 +2653,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
2331
2653
|
let total = 0;
|
|
2332
2654
|
for (const f of files) {
|
|
2333
2655
|
try {
|
|
2334
|
-
const content =
|
|
2656
|
+
const content = execFileSync12("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
2335
2657
|
const snippet = `### ${f}
|
|
2336
2658
|
|
|
2337
2659
|
\`\`\`
|
|
@@ -2411,6 +2733,35 @@ function tryPost(issueNumber, body, cwd) {
|
|
|
2411
2733
|
}
|
|
2412
2734
|
}
|
|
2413
2735
|
|
|
2736
|
+
// src/scripts/saveTaskState.ts
|
|
2737
|
+
var saveTaskState = async (ctx, profile) => {
|
|
2738
|
+
const target = ctx.data.commentTargetType;
|
|
2739
|
+
const number = ctx.data.commentTargetNumber;
|
|
2740
|
+
const state = ctx.data.taskState;
|
|
2741
|
+
if (!target || !number || !state) return;
|
|
2742
|
+
const executable = profile.name;
|
|
2743
|
+
const action = ctx.data.action ?? synthesizeAction(ctx);
|
|
2744
|
+
if (ctx.output.prUrl && !state.core.prUrl) state.core.prUrl = ctx.output.prUrl;
|
|
2745
|
+
if (typeof ctx.data.runUrl === "string") state.core.runUrl = ctx.data.runUrl;
|
|
2746
|
+
const next = reduce(state, executable, action);
|
|
2747
|
+
if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
|
|
2748
|
+
if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
|
|
2749
|
+
writeTaskState(target, number, next, ctx.cwd);
|
|
2750
|
+
ctx.data.taskStateRendered = renderStateComment(next);
|
|
2751
|
+
};
|
|
2752
|
+
function synthesizeAction(ctx) {
|
|
2753
|
+
const ok = ctx.output.exitCode === 0;
|
|
2754
|
+
return {
|
|
2755
|
+
type: ok ? "RUN_COMPLETED" : "RUN_FAILED",
|
|
2756
|
+
payload: {
|
|
2757
|
+
exitCode: ctx.output.exitCode,
|
|
2758
|
+
reason: ctx.output.reason,
|
|
2759
|
+
prUrl: ctx.output.prUrl
|
|
2760
|
+
},
|
|
2761
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2414
2765
|
// src/verify.ts
|
|
2415
2766
|
import { spawn as spawn2 } from "child_process";
|
|
2416
2767
|
var TAIL_CHARS = 4e3;
|
|
@@ -2495,8 +2846,87 @@ var verify = async (ctx) => {
|
|
|
2495
2846
|
}
|
|
2496
2847
|
};
|
|
2497
2848
|
|
|
2849
|
+
// src/scripts/watchStalePrsFlow.ts
|
|
2850
|
+
function readWatchConfig(ctx) {
|
|
2851
|
+
const cfg = ctx.config.watch;
|
|
2852
|
+
if (!cfg || typeof cfg !== "object") return {};
|
|
2853
|
+
const r = cfg;
|
|
2854
|
+
return {
|
|
2855
|
+
staleDays: typeof r.staleDays === "number" && r.staleDays > 0 ? Math.floor(r.staleDays) : void 0,
|
|
2856
|
+
reportIssueNumber: typeof r.reportIssueNumber === "number" && r.reportIssueNumber > 0 ? Math.floor(r.reportIssueNumber) : void 0
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
function findStalePrs(cwd, staleDays, now = /* @__PURE__ */ new Date()) {
|
|
2860
|
+
let raw = "";
|
|
2861
|
+
try {
|
|
2862
|
+
raw = gh(
|
|
2863
|
+
[
|
|
2864
|
+
"pr",
|
|
2865
|
+
"list",
|
|
2866
|
+
"--state",
|
|
2867
|
+
"open",
|
|
2868
|
+
"--limit",
|
|
2869
|
+
"100",
|
|
2870
|
+
"--json",
|
|
2871
|
+
"number,title,url,updatedAt"
|
|
2872
|
+
],
|
|
2873
|
+
{ cwd }
|
|
2874
|
+
);
|
|
2875
|
+
} catch {
|
|
2876
|
+
return [];
|
|
2877
|
+
}
|
|
2878
|
+
let list;
|
|
2879
|
+
try {
|
|
2880
|
+
list = JSON.parse(raw);
|
|
2881
|
+
} catch {
|
|
2882
|
+
return [];
|
|
2883
|
+
}
|
|
2884
|
+
if (!Array.isArray(list)) return [];
|
|
2885
|
+
const cutoffMs = now.getTime() - staleDays * 24 * 60 * 60 * 1e3;
|
|
2886
|
+
const stale = [];
|
|
2887
|
+
for (const pr of list) {
|
|
2888
|
+
const ts = Date.parse(pr.updatedAt);
|
|
2889
|
+
if (!Number.isFinite(ts) || ts > cutoffMs) continue;
|
|
2890
|
+
const daysStale = Math.floor((now.getTime() - ts) / (24 * 60 * 60 * 1e3));
|
|
2891
|
+
stale.push({ number: pr.number, title: pr.title, url: pr.url, updatedAt: pr.updatedAt, daysStale });
|
|
2892
|
+
}
|
|
2893
|
+
return stale.sort((a, b) => b.daysStale - a.daysStale);
|
|
2894
|
+
}
|
|
2895
|
+
function formatStaleReport(stale, staleDays) {
|
|
2896
|
+
if (stale.length === 0) {
|
|
2897
|
+
return `\u{1F7E2} **kody2 watch-stale-prs** \u2014 no open PRs untouched for more than ${staleDays} days. \u2728`;
|
|
2898
|
+
}
|
|
2899
|
+
const lines = [
|
|
2900
|
+
`\u{1F7E1} **kody2 watch-stale-prs** \u2014 ${stale.length} PR(s) untouched for > ${staleDays} days:`,
|
|
2901
|
+
""
|
|
2902
|
+
];
|
|
2903
|
+
for (const pr of stale.slice(0, 50)) {
|
|
2904
|
+
lines.push(`- [#${pr.number}](${pr.url}) \u2014 *${truncate2(pr.title, 80)}* (${pr.daysStale} days stale)`);
|
|
2905
|
+
}
|
|
2906
|
+
if (stale.length > 50) lines.push(`- \u2026 and ${stale.length - 50} more`);
|
|
2907
|
+
return lines.join("\n");
|
|
2908
|
+
}
|
|
2909
|
+
var watchStalePrsFlow = async (ctx) => {
|
|
2910
|
+
ctx.skipAgent = true;
|
|
2911
|
+
const { staleDays = 7, reportIssueNumber } = readWatchConfig(ctx);
|
|
2912
|
+
const stale = findStalePrs(ctx.cwd, staleDays);
|
|
2913
|
+
const report = formatStaleReport(stale, staleDays);
|
|
2914
|
+
process.stdout.write(`${report}
|
|
2915
|
+
`);
|
|
2916
|
+
if (reportIssueNumber) {
|
|
2917
|
+
try {
|
|
2918
|
+
postIssueComment(reportIssueNumber, report, ctx.cwd);
|
|
2919
|
+
} catch (err) {
|
|
2920
|
+
process.stderr.write(`[kody2 watch] failed to post to issue #${reportIssueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2921
|
+
`);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
ctx.output.exitCode = 0;
|
|
2925
|
+
ctx.data.staleCount = stale.length;
|
|
2926
|
+
};
|
|
2927
|
+
|
|
2498
2928
|
// src/scripts/writeRunSummary.ts
|
|
2499
|
-
import * as
|
|
2929
|
+
import * as fs12 from "fs";
|
|
2500
2930
|
var writeRunSummary = async (ctx) => {
|
|
2501
2931
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
2502
2932
|
if (!summaryPath) return;
|
|
@@ -2518,7 +2948,7 @@ var writeRunSummary = async (ctx) => {
|
|
|
2518
2948
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
2519
2949
|
lines.push("");
|
|
2520
2950
|
try {
|
|
2521
|
-
|
|
2951
|
+
fs12.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
2522
2952
|
`);
|
|
2523
2953
|
} catch {
|
|
2524
2954
|
}
|
|
@@ -2533,6 +2963,8 @@ var preflightScripts = {
|
|
|
2533
2963
|
reviewFlow,
|
|
2534
2964
|
initFlow,
|
|
2535
2965
|
releaseFlow,
|
|
2966
|
+
watchStalePrsFlow,
|
|
2967
|
+
loadTaskState,
|
|
2536
2968
|
loadConventions,
|
|
2537
2969
|
loadCoverageRules,
|
|
2538
2970
|
composePrompt
|
|
@@ -2545,7 +2977,8 @@ var postflightScripts = {
|
|
|
2545
2977
|
ensurePr: ensurePr2,
|
|
2546
2978
|
postIssueComment: postIssueComment2,
|
|
2547
2979
|
postReviewResult,
|
|
2548
|
-
writeRunSummary
|
|
2980
|
+
writeRunSummary,
|
|
2981
|
+
saveTaskState
|
|
2549
2982
|
};
|
|
2550
2983
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
2551
2984
|
...Object.keys(preflightScripts),
|
|
@@ -2553,7 +2986,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
2553
2986
|
]);
|
|
2554
2987
|
|
|
2555
2988
|
// src/tools.ts
|
|
2556
|
-
import { execFileSync as
|
|
2989
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
2557
2990
|
function verifyCliTools(tools, cwd) {
|
|
2558
2991
|
const out = [];
|
|
2559
2992
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -2586,7 +3019,7 @@ function verifyOne(tool, cwd) {
|
|
|
2586
3019
|
}
|
|
2587
3020
|
function runShell2(cmd, cwd, timeoutMs = 3e4) {
|
|
2588
3021
|
try {
|
|
2589
|
-
|
|
3022
|
+
execFileSync13("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
2590
3023
|
return true;
|
|
2591
3024
|
} catch {
|
|
2592
3025
|
return false;
|
|
@@ -2637,7 +3070,7 @@ async function runExecutable(profileName, input) {
|
|
|
2637
3070
|
data: {},
|
|
2638
3071
|
output: { exitCode: 0 }
|
|
2639
3072
|
};
|
|
2640
|
-
const ndjsonDir =
|
|
3073
|
+
const ndjsonDir = path11.join(input.cwd, ".kody2");
|
|
2641
3074
|
const invokeAgent = async (prompt) => runAgent({
|
|
2642
3075
|
prompt,
|
|
2643
3076
|
model,
|
|
@@ -2696,17 +3129,17 @@ async function runExecutable(profileName, input) {
|
|
|
2696
3129
|
}
|
|
2697
3130
|
}
|
|
2698
3131
|
function resolveProfilePath(profileName) {
|
|
2699
|
-
const here =
|
|
3132
|
+
const here = path11.dirname(new URL(import.meta.url).pathname);
|
|
2700
3133
|
const candidates = [
|
|
2701
|
-
|
|
3134
|
+
path11.join(here, "executables", profileName, "profile.json"),
|
|
2702
3135
|
// same-dir sibling (dev)
|
|
2703
|
-
|
|
3136
|
+
path11.join(here, "..", "executables", profileName, "profile.json"),
|
|
2704
3137
|
// up one (prod: dist/bin → dist/executables)
|
|
2705
|
-
|
|
3138
|
+
path11.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
2706
3139
|
// fallback
|
|
2707
3140
|
];
|
|
2708
3141
|
for (const c of candidates) {
|
|
2709
|
-
if (
|
|
3142
|
+
if (fs13.existsSync(c)) return c;
|
|
2710
3143
|
}
|
|
2711
3144
|
return candidates[0];
|
|
2712
3145
|
}
|
|
@@ -2784,12 +3217,12 @@ function finish(out) {
|
|
|
2784
3217
|
}
|
|
2785
3218
|
|
|
2786
3219
|
// src/kody2-cli.ts
|
|
2787
|
-
import { execFileSync as
|
|
2788
|
-
import * as
|
|
2789
|
-
import * as
|
|
3220
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
3221
|
+
import * as fs15 from "fs";
|
|
3222
|
+
import * as path12 from "path";
|
|
2790
3223
|
|
|
2791
3224
|
// src/dispatch.ts
|
|
2792
|
-
import * as
|
|
3225
|
+
import * as fs14 from "fs";
|
|
2793
3226
|
function autoDispatch(explicit) {
|
|
2794
3227
|
if (explicit?.mode && explicit.target) {
|
|
2795
3228
|
return {
|
|
@@ -2799,10 +3232,10 @@ function autoDispatch(explicit) {
|
|
|
2799
3232
|
}
|
|
2800
3233
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
2801
3234
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2802
|
-
if (!eventName || !eventPath || !
|
|
3235
|
+
if (!eventName || !eventPath || !fs14.existsSync(eventPath)) return null;
|
|
2803
3236
|
let event = {};
|
|
2804
3237
|
try {
|
|
2805
|
-
event = JSON.parse(
|
|
3238
|
+
event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
|
|
2806
3239
|
} catch {
|
|
2807
3240
|
return null;
|
|
2808
3241
|
}
|
|
@@ -2922,14 +3355,14 @@ function resolveAuthToken(env = process.env) {
|
|
|
2922
3355
|
return token;
|
|
2923
3356
|
}
|
|
2924
3357
|
function detectPackageManager2(cwd) {
|
|
2925
|
-
if (
|
|
2926
|
-
if (
|
|
2927
|
-
if (
|
|
3358
|
+
if (fs15.existsSync(path12.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
3359
|
+
if (fs15.existsSync(path12.join(cwd, "yarn.lock"))) return "yarn";
|
|
3360
|
+
if (fs15.existsSync(path12.join(cwd, "bun.lockb"))) return "bun";
|
|
2928
3361
|
return "npm";
|
|
2929
3362
|
}
|
|
2930
3363
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
2931
3364
|
try {
|
|
2932
|
-
|
|
3365
|
+
execFileSync14(cmd, args, {
|
|
2933
3366
|
cwd,
|
|
2934
3367
|
stdio: stream ? "inherit" : "pipe",
|
|
2935
3368
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -2942,7 +3375,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
2942
3375
|
}
|
|
2943
3376
|
function isOnPath(bin) {
|
|
2944
3377
|
try {
|
|
2945
|
-
|
|
3378
|
+
execFileSync14("which", [bin], { stdio: "pipe" });
|
|
2946
3379
|
return true;
|
|
2947
3380
|
} catch {
|
|
2948
3381
|
return false;
|
|
@@ -2976,7 +3409,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
2976
3409
|
} catch {
|
|
2977
3410
|
}
|
|
2978
3411
|
try {
|
|
2979
|
-
|
|
3412
|
+
execFileSync14("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
2980
3413
|
process.stdout.write("\u2192 kody2: litellm already installed\n");
|
|
2981
3414
|
return 0;
|
|
2982
3415
|
} catch {
|
|
@@ -2986,26 +3419,26 @@ function installLitellmIfNeeded(cwd) {
|
|
|
2986
3419
|
}
|
|
2987
3420
|
function configureGitIdentity(cwd) {
|
|
2988
3421
|
try {
|
|
2989
|
-
const name =
|
|
3422
|
+
const name = execFileSync14("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
2990
3423
|
if (name) return;
|
|
2991
3424
|
} catch {
|
|
2992
3425
|
}
|
|
2993
3426
|
try {
|
|
2994
|
-
|
|
3427
|
+
execFileSync14("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
|
|
2995
3428
|
} catch {
|
|
2996
3429
|
}
|
|
2997
3430
|
try {
|
|
2998
|
-
|
|
3431
|
+
execFileSync14("git", ["config", "user.email", "kody2-bot@users.noreply.github.com"], { cwd, stdio: "pipe" });
|
|
2999
3432
|
} catch {
|
|
3000
3433
|
}
|
|
3001
3434
|
}
|
|
3002
3435
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
3003
3436
|
if (!issueNumber) return;
|
|
3004
|
-
const logPath =
|
|
3437
|
+
const logPath = path12.join(cwd, ".kody2", "last-run.jsonl");
|
|
3005
3438
|
let tail = "";
|
|
3006
3439
|
try {
|
|
3007
|
-
if (
|
|
3008
|
-
const content =
|
|
3440
|
+
if (fs15.existsSync(logPath)) {
|
|
3441
|
+
const content = fs15.readFileSync(logPath, "utf-8");
|
|
3009
3442
|
tail = content.slice(-3e3);
|
|
3010
3443
|
}
|
|
3011
3444
|
} catch {
|
|
@@ -3042,7 +3475,7 @@ async function runCi(argv) {
|
|
|
3042
3475
|
${CI_HELP}`);
|
|
3043
3476
|
return 64;
|
|
3044
3477
|
}
|
|
3045
|
-
const cwd = args.cwd ?
|
|
3478
|
+
const cwd = args.cwd ? path12.resolve(args.cwd) : process.cwd();
|
|
3046
3479
|
const dispatch = autoFallback ?? {
|
|
3047
3480
|
mode: "run",
|
|
3048
3481
|
target: args.issueNumber,
|
|
@@ -3117,67 +3550,6 @@ ${CI_HELP}`);
|
|
|
3117
3550
|
}
|
|
3118
3551
|
}
|
|
3119
3552
|
|
|
3120
|
-
// src/registry.ts
|
|
3121
|
-
import * as fs15 from "fs";
|
|
3122
|
-
import * as path12 from "path";
|
|
3123
|
-
function getExecutablesRoot() {
|
|
3124
|
-
const here = path12.dirname(new URL(import.meta.url).pathname);
|
|
3125
|
-
const candidates = [
|
|
3126
|
-
path12.join(here, "executables"),
|
|
3127
|
-
// dev: src/
|
|
3128
|
-
path12.join(here, "..", "executables"),
|
|
3129
|
-
// built: dist/bin → dist/executables
|
|
3130
|
-
path12.join(here, "..", "src", "executables")
|
|
3131
|
-
// fallback
|
|
3132
|
-
];
|
|
3133
|
-
for (const c of candidates) {
|
|
3134
|
-
if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
|
|
3135
|
-
}
|
|
3136
|
-
return candidates[0];
|
|
3137
|
-
}
|
|
3138
|
-
function listExecutables(root = getExecutablesRoot()) {
|
|
3139
|
-
if (!fs15.existsSync(root)) return [];
|
|
3140
|
-
const entries = fs15.readdirSync(root, { withFileTypes: true });
|
|
3141
|
-
const out = [];
|
|
3142
|
-
for (const ent of entries) {
|
|
3143
|
-
if (!ent.isDirectory()) continue;
|
|
3144
|
-
const profilePath = path12.join(root, ent.name, "profile.json");
|
|
3145
|
-
if (fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile()) {
|
|
3146
|
-
out.push({ name: ent.name, profilePath });
|
|
3147
|
-
}
|
|
3148
|
-
}
|
|
3149
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
3150
|
-
}
|
|
3151
|
-
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
3152
|
-
if (!isSafeName(name)) return false;
|
|
3153
|
-
const profilePath = path12.join(root, name, "profile.json");
|
|
3154
|
-
return fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile();
|
|
3155
|
-
}
|
|
3156
|
-
function isSafeName(name) {
|
|
3157
|
-
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
3158
|
-
}
|
|
3159
|
-
function parseGenericFlags(argv) {
|
|
3160
|
-
const args = {};
|
|
3161
|
-
const positional = [];
|
|
3162
|
-
for (let i = 0; i < argv.length; i++) {
|
|
3163
|
-
const arg = argv[i];
|
|
3164
|
-
if (!arg.startsWith("--")) {
|
|
3165
|
-
positional.push(arg);
|
|
3166
|
-
continue;
|
|
3167
|
-
}
|
|
3168
|
-
const key = arg.slice(2);
|
|
3169
|
-
const next = argv[i + 1];
|
|
3170
|
-
if (next !== void 0 && !next.startsWith("--")) {
|
|
3171
|
-
args[key] = next;
|
|
3172
|
-
i++;
|
|
3173
|
-
} else {
|
|
3174
|
-
args[key] = true;
|
|
3175
|
-
}
|
|
3176
|
-
}
|
|
3177
|
-
if (positional.length > 0) args._ = positional;
|
|
3178
|
-
return args;
|
|
3179
|
-
}
|
|
3180
|
-
|
|
3181
3553
|
// src/entry.ts
|
|
3182
3554
|
var HELP_TEXT = `kody2 \u2014 single-session autonomous engineer
|
|
3183
3555
|
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
{ "script": "fixFlow", "runWhen": { "args.mode": "fix" } },
|
|
68
68
|
{ "script": "fixCiFlow", "runWhen": { "args.mode": "fix-ci" } },
|
|
69
69
|
{ "script": "resolveFlow", "runWhen": { "args.mode": "resolve" } },
|
|
70
|
+
{ "script": "loadTaskState" },
|
|
70
71
|
{ "script": "loadConventions" },
|
|
71
72
|
{ "script": "loadCoverageRules" },
|
|
72
73
|
{ "script": "composePrompt" }
|
|
@@ -78,7 +79,21 @@
|
|
|
78
79
|
{ "script": "commitAndPush" },
|
|
79
80
|
{ "script": "ensurePr" },
|
|
80
81
|
{ "script": "postIssueComment" },
|
|
81
|
-
{ "script": "writeRunSummary" }
|
|
82
|
+
{ "script": "writeRunSummary" },
|
|
83
|
+
{ "script": "saveTaskState" }
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"output": {
|
|
87
|
+
"actionTypes": [
|
|
88
|
+
"RUN_COMPLETED",
|
|
89
|
+
"RUN_FAILED",
|
|
90
|
+
"FIX_COMPLETED",
|
|
91
|
+
"FIX_FAILED",
|
|
92
|
+
"FIX_CI_COMPLETED",
|
|
93
|
+
"FIX_CI_FAILED",
|
|
94
|
+
"RESOLVE_COMPLETED",
|
|
95
|
+
"RESOLVE_FAILED",
|
|
96
|
+
"AGENT_NOT_RUN"
|
|
82
97
|
]
|
|
83
98
|
}
|
|
84
99
|
}
|
|
@@ -17,6 +17,14 @@ import type { Kody2Config } from "../config.js"
|
|
|
17
17
|
export interface Profile {
|
|
18
18
|
name: string
|
|
19
19
|
describe: string
|
|
20
|
+
/**
|
|
21
|
+
* Execution model. `oneshot` (default): single invocation on demand.
|
|
22
|
+
* `scheduled`: fires periodically via an external cron (typically GHA
|
|
23
|
+
* `schedule:`). Scheduled profiles must declare a `schedule` cron string.
|
|
24
|
+
*/
|
|
25
|
+
kind: "oneshot" | "scheduled"
|
|
26
|
+
/** Cron expression for scheduled profiles (e.g. "0 8 * * MON"). */
|
|
27
|
+
schedule?: string
|
|
20
28
|
inputs: InputSpec[]
|
|
21
29
|
claudeCode: ClaudeCodeSpec
|
|
22
30
|
cliTools: CliToolSpec[]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "watch-stale-prs",
|
|
3
|
+
"describe": "Scheduled: list open PRs untouched for N days and report. No agent invocation.",
|
|
4
|
+
|
|
5
|
+
"kind": "scheduled",
|
|
6
|
+
"schedule": "0 8 * * MON",
|
|
7
|
+
|
|
8
|
+
"inputs": [],
|
|
9
|
+
|
|
10
|
+
"claudeCode": {
|
|
11
|
+
"model": "inherit",
|
|
12
|
+
"permissionMode": "default",
|
|
13
|
+
"maxTurns": null,
|
|
14
|
+
"systemPromptAppend": null,
|
|
15
|
+
"tools": [],
|
|
16
|
+
"hooks": { "PreToolUse": [], "PostToolUse": [], "Stop": [] },
|
|
17
|
+
"skills": [],
|
|
18
|
+
"commands": [],
|
|
19
|
+
"subagents": [],
|
|
20
|
+
"plugins": [],
|
|
21
|
+
"mcpServers": []
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
"cliTools": [],
|
|
25
|
+
|
|
26
|
+
"scripts": {
|
|
27
|
+
"preflight": [
|
|
28
|
+
{ "script": "watchStalePrsFlow" }
|
|
29
|
+
],
|
|
30
|
+
"postflight": []
|
|
31
|
+
}
|
|
32
|
+
}
|
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.8",
|
|
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",
|