@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",
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 fs12 from "fs";
155
- import * as path10 from "path";
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 (fs9.existsSync(path8.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1671
- if (fs9.existsSync(path8.join(cwd, "yarn.lock"))) return "yarn";
1672
- if (fs9.existsSync(path8.join(cwd, "bun.lockb"))) return "bun";
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 = path8.join(cwd, "kody.config.json");
1794
- if (fs9.existsSync(configPath) && !force) {
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
- fs9.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
1867
+ fs10.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
1799
1868
  `);
1800
1869
  wrote.push("kody.config.json");
1801
1870
  }
1802
- const workflowDir = path8.join(cwd, ".github", "workflows");
1803
- const workflowPath = path8.join(workflowDir, "kody2.yml");
1804
- if (fs9.existsSync(workflowPath) && !force) {
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
- fs9.mkdirSync(workflowDir, { recursive: true });
1808
- fs9.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
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, _profile, agentResult) => {
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 execFileSync10, spawnSync } from "child_process";
1966
- import * as fs10 from "fs";
1967
- import * as path9 from "path";
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 = path9.join(cwd, file);
1984
- if (!fs10.existsSync(abs)) return false;
1985
- const content = fs10.readFileSync(abs, "utf-8");
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
- fs10.writeFileSync(abs, updated);
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 = execFileSync10("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
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 = path9.join(cwd, "CHANGELOG.md");
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 (fs10.existsSync(p)) {
2039
- const prior = fs10.readFileSync(p, "utf-8");
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
- fs10.writeFileSync(p, `${prior.slice(0, idx + 1)}
2364
+ fs11.writeFileSync(p, `${prior.slice(0, idx + 1)}
2043
2365
  ${entry}${prior.slice(idx + 1)}`);
2044
2366
  } else {
2045
- fs10.writeFileSync(p, `${header}${entry}${prior}`);
2367
+ fs11.writeFileSync(p, `${header}${entry}${prior}`);
2046
2368
  }
2047
2369
  } else {
2048
- fs10.writeFileSync(p, `${header}${entry}`);
2370
+ fs11.writeFileSync(p, `${header}${entry}`);
2049
2371
  }
2050
2372
  }
2051
2373
  function git3(args, cwd, timeout = 6e4) {
2052
- return execFileSync10("git", args, {
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 = path9.join(cwd, "package.json");
2100
- if (!fs10.existsSync(pkgPath)) {
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(fs10.readFileSync(pkgPath, "utf-8"));
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 = path9.join(cwd, "package.json");
2177
- const pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
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 execFileSync11 } from "child_process";
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 = execFileSync11("git", ["diff", "--name-only", "--diff-filter=U"], {
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 = execFileSync11("cat", [f], { encoding: "utf-8", cwd }).toString();
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 fs11 from "fs";
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
- fs11.appendFileSync(summaryPath, `${lines.join("\n")}
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 execFileSync12 } from "child_process";
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
- execFileSync12("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
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 = path10.join(input.cwd, ".kody2");
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 = path10.dirname(new URL(import.meta.url).pathname);
3132
+ const here = path11.dirname(new URL(import.meta.url).pathname);
2700
3133
  const candidates = [
2701
- path10.join(here, "executables", profileName, "profile.json"),
3134
+ path11.join(here, "executables", profileName, "profile.json"),
2702
3135
  // same-dir sibling (dev)
2703
- path10.join(here, "..", "executables", profileName, "profile.json"),
3136
+ path11.join(here, "..", "executables", profileName, "profile.json"),
2704
3137
  // up one (prod: dist/bin → dist/executables)
2705
- path10.join(here, "..", "src", "executables", profileName, "profile.json")
3138
+ path11.join(here, "..", "src", "executables", profileName, "profile.json")
2706
3139
  // fallback
2707
3140
  ];
2708
3141
  for (const c of candidates) {
2709
- if (fs12.existsSync(c)) return c;
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 execFileSync13 } from "child_process";
2788
- import * as fs14 from "fs";
2789
- import * as path11 from "path";
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 fs13 from "fs";
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 || !fs13.existsSync(eventPath)) return null;
3235
+ if (!eventName || !eventPath || !fs14.existsSync(eventPath)) return null;
2803
3236
  let event = {};
2804
3237
  try {
2805
- event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
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 (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";
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
- execFileSync13(cmd, args, {
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
- execFileSync13("which", [bin], { stdio: "pipe" });
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
- execFileSync13("python3", ["-c", "import litellm"], { stdio: "pipe" });
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 = execFileSync13("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
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
- execFileSync13("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
3427
+ execFileSync14("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
2995
3428
  } catch {
2996
3429
  }
2997
3430
  try {
2998
- execFileSync13("git", ["config", "user.email", "kody2-bot@users.noreply.github.com"], { cwd, stdio: "pipe" });
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 = path11.join(cwd, ".kody2", "last-run.jsonl");
3437
+ const logPath = path12.join(cwd, ".kody2", "last-run.jsonl");
3005
3438
  let tail = "";
3006
3439
  try {
3007
- if (fs14.existsSync(logPath)) {
3008
- const content = fs14.readFileSync(logPath, "utf-8");
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 ? path11.resolve(args.cwd) : process.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.6",
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",