@kody-ade/kody-engine 0.2.6 → 0.2.7
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.7",
|
|
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;
|
|
@@ -1963,8 +2080,8 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
1963
2080
|
|
|
1964
2081
|
// src/scripts/releaseFlow.ts
|
|
1965
2082
|
import { execFileSync as execFileSync10, spawnSync } from "child_process";
|
|
1966
|
-
import * as
|
|
1967
|
-
import * as
|
|
2083
|
+
import * as fs11 from "fs";
|
|
2084
|
+
import * as path10 from "path";
|
|
1968
2085
|
function bumpVersion(current, bump) {
|
|
1969
2086
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
1970
2087
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -1980,12 +2097,12 @@ function bumpVersion(current, bump) {
|
|
|
1980
2097
|
return `${major}.${minor}.${patch}`;
|
|
1981
2098
|
}
|
|
1982
2099
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
1983
|
-
const abs =
|
|
1984
|
-
if (!
|
|
1985
|
-
const content =
|
|
2100
|
+
const abs = path10.join(cwd, file);
|
|
2101
|
+
if (!fs11.existsSync(abs)) return false;
|
|
2102
|
+
const content = fs11.readFileSync(abs, "utf-8");
|
|
1986
2103
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
1987
2104
|
if (updated === content) return false;
|
|
1988
|
-
|
|
2105
|
+
fs11.writeFileSync(abs, updated);
|
|
1989
2106
|
return true;
|
|
1990
2107
|
}
|
|
1991
2108
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
@@ -2033,19 +2150,19 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
2033
2150
|
return parts.join("\n");
|
|
2034
2151
|
}
|
|
2035
2152
|
function prependChangelog(cwd, entry) {
|
|
2036
|
-
const p =
|
|
2153
|
+
const p = path10.join(cwd, "CHANGELOG.md");
|
|
2037
2154
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
2038
|
-
if (
|
|
2039
|
-
const prior =
|
|
2155
|
+
if (fs11.existsSync(p)) {
|
|
2156
|
+
const prior = fs11.readFileSync(p, "utf-8");
|
|
2040
2157
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
2041
2158
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
2042
|
-
|
|
2159
|
+
fs11.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
2043
2160
|
${entry}${prior.slice(idx + 1)}`);
|
|
2044
2161
|
} else {
|
|
2045
|
-
|
|
2162
|
+
fs11.writeFileSync(p, `${header}${entry}${prior}`);
|
|
2046
2163
|
}
|
|
2047
2164
|
} else {
|
|
2048
|
-
|
|
2165
|
+
fs11.writeFileSync(p, `${header}${entry}`);
|
|
2049
2166
|
}
|
|
2050
2167
|
}
|
|
2051
2168
|
function git3(args, cwd, timeout = 6e4) {
|
|
@@ -2096,13 +2213,13 @@ var releaseFlow = async (ctx) => {
|
|
|
2096
2213
|
};
|
|
2097
2214
|
async function runPrepare(args) {
|
|
2098
2215
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
2099
|
-
const pkgPath =
|
|
2100
|
-
if (!
|
|
2216
|
+
const pkgPath = path10.join(cwd, "package.json");
|
|
2217
|
+
if (!fs11.existsSync(pkgPath)) {
|
|
2101
2218
|
ctx.output.exitCode = 99;
|
|
2102
2219
|
ctx.output.reason = "release prepare: package.json not found";
|
|
2103
2220
|
return;
|
|
2104
2221
|
}
|
|
2105
|
-
const pkg = JSON.parse(
|
|
2222
|
+
const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
2106
2223
|
if (typeof pkg.version !== "string") {
|
|
2107
2224
|
ctx.output.exitCode = 99;
|
|
2108
2225
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -2173,8 +2290,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
2173
2290
|
}
|
|
2174
2291
|
async function runFinalize(args) {
|
|
2175
2292
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
2176
|
-
const pkgPath =
|
|
2177
|
-
const pkg = JSON.parse(
|
|
2293
|
+
const pkgPath = path10.join(cwd, "package.json");
|
|
2294
|
+
const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
2178
2295
|
if (typeof pkg.version !== "string") {
|
|
2179
2296
|
ctx.output.exitCode = 99;
|
|
2180
2297
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -2495,8 +2612,87 @@ var verify = async (ctx) => {
|
|
|
2495
2612
|
}
|
|
2496
2613
|
};
|
|
2497
2614
|
|
|
2615
|
+
// src/scripts/watchStalePrsFlow.ts
|
|
2616
|
+
function readWatchConfig(ctx) {
|
|
2617
|
+
const cfg = ctx.config.watch;
|
|
2618
|
+
if (!cfg || typeof cfg !== "object") return {};
|
|
2619
|
+
const r = cfg;
|
|
2620
|
+
return {
|
|
2621
|
+
staleDays: typeof r.staleDays === "number" && r.staleDays > 0 ? Math.floor(r.staleDays) : void 0,
|
|
2622
|
+
reportIssueNumber: typeof r.reportIssueNumber === "number" && r.reportIssueNumber > 0 ? Math.floor(r.reportIssueNumber) : void 0
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
function findStalePrs(cwd, staleDays, now = /* @__PURE__ */ new Date()) {
|
|
2626
|
+
let raw = "";
|
|
2627
|
+
try {
|
|
2628
|
+
raw = gh(
|
|
2629
|
+
[
|
|
2630
|
+
"pr",
|
|
2631
|
+
"list",
|
|
2632
|
+
"--state",
|
|
2633
|
+
"open",
|
|
2634
|
+
"--limit",
|
|
2635
|
+
"100",
|
|
2636
|
+
"--json",
|
|
2637
|
+
"number,title,url,updatedAt"
|
|
2638
|
+
],
|
|
2639
|
+
{ cwd }
|
|
2640
|
+
);
|
|
2641
|
+
} catch {
|
|
2642
|
+
return [];
|
|
2643
|
+
}
|
|
2644
|
+
let list;
|
|
2645
|
+
try {
|
|
2646
|
+
list = JSON.parse(raw);
|
|
2647
|
+
} catch {
|
|
2648
|
+
return [];
|
|
2649
|
+
}
|
|
2650
|
+
if (!Array.isArray(list)) return [];
|
|
2651
|
+
const cutoffMs = now.getTime() - staleDays * 24 * 60 * 60 * 1e3;
|
|
2652
|
+
const stale = [];
|
|
2653
|
+
for (const pr of list) {
|
|
2654
|
+
const ts = Date.parse(pr.updatedAt);
|
|
2655
|
+
if (!Number.isFinite(ts) || ts > cutoffMs) continue;
|
|
2656
|
+
const daysStale = Math.floor((now.getTime() - ts) / (24 * 60 * 60 * 1e3));
|
|
2657
|
+
stale.push({ number: pr.number, title: pr.title, url: pr.url, updatedAt: pr.updatedAt, daysStale });
|
|
2658
|
+
}
|
|
2659
|
+
return stale.sort((a, b) => b.daysStale - a.daysStale);
|
|
2660
|
+
}
|
|
2661
|
+
function formatStaleReport(stale, staleDays) {
|
|
2662
|
+
if (stale.length === 0) {
|
|
2663
|
+
return `\u{1F7E2} **kody2 watch-stale-prs** \u2014 no open PRs untouched for more than ${staleDays} days. \u2728`;
|
|
2664
|
+
}
|
|
2665
|
+
const lines = [
|
|
2666
|
+
`\u{1F7E1} **kody2 watch-stale-prs** \u2014 ${stale.length} PR(s) untouched for > ${staleDays} days:`,
|
|
2667
|
+
""
|
|
2668
|
+
];
|
|
2669
|
+
for (const pr of stale.slice(0, 50)) {
|
|
2670
|
+
lines.push(`- [#${pr.number}](${pr.url}) \u2014 *${truncate2(pr.title, 80)}* (${pr.daysStale} days stale)`);
|
|
2671
|
+
}
|
|
2672
|
+
if (stale.length > 50) lines.push(`- \u2026 and ${stale.length - 50} more`);
|
|
2673
|
+
return lines.join("\n");
|
|
2674
|
+
}
|
|
2675
|
+
var watchStalePrsFlow = async (ctx) => {
|
|
2676
|
+
ctx.skipAgent = true;
|
|
2677
|
+
const { staleDays = 7, reportIssueNumber } = readWatchConfig(ctx);
|
|
2678
|
+
const stale = findStalePrs(ctx.cwd, staleDays);
|
|
2679
|
+
const report = formatStaleReport(stale, staleDays);
|
|
2680
|
+
process.stdout.write(`${report}
|
|
2681
|
+
`);
|
|
2682
|
+
if (reportIssueNumber) {
|
|
2683
|
+
try {
|
|
2684
|
+
postIssueComment(reportIssueNumber, report, ctx.cwd);
|
|
2685
|
+
} catch (err) {
|
|
2686
|
+
process.stderr.write(`[kody2 watch] failed to post to issue #${reportIssueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2687
|
+
`);
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
ctx.output.exitCode = 0;
|
|
2691
|
+
ctx.data.staleCount = stale.length;
|
|
2692
|
+
};
|
|
2693
|
+
|
|
2498
2694
|
// src/scripts/writeRunSummary.ts
|
|
2499
|
-
import * as
|
|
2695
|
+
import * as fs12 from "fs";
|
|
2500
2696
|
var writeRunSummary = async (ctx) => {
|
|
2501
2697
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
2502
2698
|
if (!summaryPath) return;
|
|
@@ -2518,7 +2714,7 @@ var writeRunSummary = async (ctx) => {
|
|
|
2518
2714
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
2519
2715
|
lines.push("");
|
|
2520
2716
|
try {
|
|
2521
|
-
|
|
2717
|
+
fs12.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
2522
2718
|
`);
|
|
2523
2719
|
} catch {
|
|
2524
2720
|
}
|
|
@@ -2533,6 +2729,7 @@ var preflightScripts = {
|
|
|
2533
2729
|
reviewFlow,
|
|
2534
2730
|
initFlow,
|
|
2535
2731
|
releaseFlow,
|
|
2732
|
+
watchStalePrsFlow,
|
|
2536
2733
|
loadConventions,
|
|
2537
2734
|
loadCoverageRules,
|
|
2538
2735
|
composePrompt
|
|
@@ -2637,7 +2834,7 @@ async function runExecutable(profileName, input) {
|
|
|
2637
2834
|
data: {},
|
|
2638
2835
|
output: { exitCode: 0 }
|
|
2639
2836
|
};
|
|
2640
|
-
const ndjsonDir =
|
|
2837
|
+
const ndjsonDir = path11.join(input.cwd, ".kody2");
|
|
2641
2838
|
const invokeAgent = async (prompt) => runAgent({
|
|
2642
2839
|
prompt,
|
|
2643
2840
|
model,
|
|
@@ -2696,17 +2893,17 @@ async function runExecutable(profileName, input) {
|
|
|
2696
2893
|
}
|
|
2697
2894
|
}
|
|
2698
2895
|
function resolveProfilePath(profileName) {
|
|
2699
|
-
const here =
|
|
2896
|
+
const here = path11.dirname(new URL(import.meta.url).pathname);
|
|
2700
2897
|
const candidates = [
|
|
2701
|
-
|
|
2898
|
+
path11.join(here, "executables", profileName, "profile.json"),
|
|
2702
2899
|
// same-dir sibling (dev)
|
|
2703
|
-
|
|
2900
|
+
path11.join(here, "..", "executables", profileName, "profile.json"),
|
|
2704
2901
|
// up one (prod: dist/bin → dist/executables)
|
|
2705
|
-
|
|
2902
|
+
path11.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
2706
2903
|
// fallback
|
|
2707
2904
|
];
|
|
2708
2905
|
for (const c of candidates) {
|
|
2709
|
-
if (
|
|
2906
|
+
if (fs13.existsSync(c)) return c;
|
|
2710
2907
|
}
|
|
2711
2908
|
return candidates[0];
|
|
2712
2909
|
}
|
|
@@ -2785,11 +2982,11 @@ function finish(out) {
|
|
|
2785
2982
|
|
|
2786
2983
|
// src/kody2-cli.ts
|
|
2787
2984
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
2788
|
-
import * as
|
|
2789
|
-
import * as
|
|
2985
|
+
import * as fs15 from "fs";
|
|
2986
|
+
import * as path12 from "path";
|
|
2790
2987
|
|
|
2791
2988
|
// src/dispatch.ts
|
|
2792
|
-
import * as
|
|
2989
|
+
import * as fs14 from "fs";
|
|
2793
2990
|
function autoDispatch(explicit) {
|
|
2794
2991
|
if (explicit?.mode && explicit.target) {
|
|
2795
2992
|
return {
|
|
@@ -2799,10 +2996,10 @@ function autoDispatch(explicit) {
|
|
|
2799
2996
|
}
|
|
2800
2997
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
2801
2998
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2802
|
-
if (!eventName || !eventPath || !
|
|
2999
|
+
if (!eventName || !eventPath || !fs14.existsSync(eventPath)) return null;
|
|
2803
3000
|
let event = {};
|
|
2804
3001
|
try {
|
|
2805
|
-
event = JSON.parse(
|
|
3002
|
+
event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
|
|
2806
3003
|
} catch {
|
|
2807
3004
|
return null;
|
|
2808
3005
|
}
|
|
@@ -2922,9 +3119,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
2922
3119
|
return token;
|
|
2923
3120
|
}
|
|
2924
3121
|
function detectPackageManager2(cwd) {
|
|
2925
|
-
if (
|
|
2926
|
-
if (
|
|
2927
|
-
if (
|
|
3122
|
+
if (fs15.existsSync(path12.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
3123
|
+
if (fs15.existsSync(path12.join(cwd, "yarn.lock"))) return "yarn";
|
|
3124
|
+
if (fs15.existsSync(path12.join(cwd, "bun.lockb"))) return "bun";
|
|
2928
3125
|
return "npm";
|
|
2929
3126
|
}
|
|
2930
3127
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -3001,11 +3198,11 @@ function configureGitIdentity(cwd) {
|
|
|
3001
3198
|
}
|
|
3002
3199
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
3003
3200
|
if (!issueNumber) return;
|
|
3004
|
-
const logPath =
|
|
3201
|
+
const logPath = path12.join(cwd, ".kody2", "last-run.jsonl");
|
|
3005
3202
|
let tail = "";
|
|
3006
3203
|
try {
|
|
3007
|
-
if (
|
|
3008
|
-
const content =
|
|
3204
|
+
if (fs15.existsSync(logPath)) {
|
|
3205
|
+
const content = fs15.readFileSync(logPath, "utf-8");
|
|
3009
3206
|
tail = content.slice(-3e3);
|
|
3010
3207
|
}
|
|
3011
3208
|
} catch {
|
|
@@ -3042,7 +3239,7 @@ async function runCi(argv) {
|
|
|
3042
3239
|
${CI_HELP}`);
|
|
3043
3240
|
return 64;
|
|
3044
3241
|
}
|
|
3045
|
-
const cwd = args.cwd ?
|
|
3242
|
+
const cwd = args.cwd ? path12.resolve(args.cwd) : process.cwd();
|
|
3046
3243
|
const dispatch = autoFallback ?? {
|
|
3047
3244
|
mode: "run",
|
|
3048
3245
|
target: args.issueNumber,
|
|
@@ -3117,67 +3314,6 @@ ${CI_HELP}`);
|
|
|
3117
3314
|
}
|
|
3118
3315
|
}
|
|
3119
3316
|
|
|
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
3317
|
// src/entry.ts
|
|
3182
3318
|
var HELP_TEXT = `kody2 \u2014 single-session autonomous engineer
|
|
3183
3319
|
|
|
@@ -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.7",
|
|
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",
|