@kody-ade/kody-engine 0.2.3 → 0.2.4

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.3",
6
+ version: "0.2.4",
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",
@@ -137,8 +137,8 @@ function getAnthropicApiKeyOrDummy() {
137
137
  }
138
138
 
139
139
  // src/executor.ts
140
- import * as fs10 from "fs";
141
- import * as path8 from "path";
140
+ import * as fs11 from "fs";
141
+ import * as path9 from "path";
142
142
 
143
143
  // src/agent.ts
144
144
  import * as fs2 from "fs";
@@ -519,9 +519,6 @@ function parseClaudeCode(p, raw) {
519
519
  throw new ProfileError(p, `claudeCode.permissionMode must be one of default|acceptEdits|plan|bypassPermissions`);
520
520
  }
521
521
  const tools = Array.isArray(r.tools) ? r.tools : [];
522
- if (tools.length === 0) {
523
- throw new ProfileError(p, `claudeCode.tools must declare at least one SDK tool`);
524
- }
525
522
  const hooksRaw = r.hooks ?? {};
526
523
  const hooks = {
527
524
  PreToolUse: Array.isArray(hooksRaw.PreToolUse) ? hooksRaw.PreToolUse : [],
@@ -1651,6 +1648,174 @@ function tryPostPr2(prNumber, body, cwd) {
1651
1648
  }
1652
1649
  }
1653
1650
 
1651
+ // src/scripts/initFlow.ts
1652
+ import { execFileSync as execFileSync9 } from "child_process";
1653
+ import * as fs9 from "fs";
1654
+ import * as path8 from "path";
1655
+ function detectPackageManager(cwd) {
1656
+ if (fs9.existsSync(path8.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1657
+ if (fs9.existsSync(path8.join(cwd, "yarn.lock"))) return "yarn";
1658
+ if (fs9.existsSync(path8.join(cwd, "bun.lockb"))) return "bun";
1659
+ return "npm";
1660
+ }
1661
+ function qualityCommandsFor(pm) {
1662
+ return {
1663
+ typecheck: `${pm} tsc --noEmit`,
1664
+ lint: "",
1665
+ testUnit: `${pm} test`
1666
+ };
1667
+ }
1668
+ function detectOwnerRepo(cwd) {
1669
+ let url;
1670
+ try {
1671
+ url = execFileSync9("git", ["remote", "get-url", "origin"], {
1672
+ cwd,
1673
+ encoding: "utf-8",
1674
+ stdio: ["ignore", "pipe", "pipe"]
1675
+ }).trim();
1676
+ } catch {
1677
+ return null;
1678
+ }
1679
+ const m = url.match(/[:/]([^/:]+)\/([^/]+?)(?:\.git)?$/) ?? null;
1680
+ if (!m) return null;
1681
+ return { owner: m[1], repo: m[2] };
1682
+ }
1683
+ function makeConfig(pm, ownerRepo, defaultBranch) {
1684
+ return {
1685
+ $schema: "https://raw.githubusercontent.com/aharonyaircohen/kody-engine/main/kody.config.schema.json",
1686
+ quality: qualityCommandsFor(pm),
1687
+ git: { defaultBranch },
1688
+ github: {
1689
+ owner: ownerRepo?.owner ?? "OWNER",
1690
+ repo: ownerRepo?.repo ?? "REPO"
1691
+ },
1692
+ agent: {
1693
+ model: "minimax/MiniMax-M2.7-highspeed"
1694
+ }
1695
+ };
1696
+ }
1697
+ var WORKFLOW_TEMPLATE = `# Drop this file at .github/workflows/kody2.yml in your repo.
1698
+ #
1699
+ # Triggers: @kody2 comment on an issue or PR, or manual workflow_dispatch.
1700
+ # Everything else (install deps, set up LiteLLM, run the agent, open the PR)
1701
+ # is handled inside the @kody-ade/kody-engine package.
1702
+ #
1703
+ # Required repo secrets: at least one model provider key (e.g. MINIMAX_API_KEY,
1704
+ # ANTHROPIC_API_KEY). kody2 reads any *_API_KEY secret automatically via
1705
+ # toJSON(secrets) \u2014 no need to list them here.
1706
+ #
1707
+ # Recommended: KODY_TOKEN secret \u2014 a PAT or GitHub App token with repo
1708
+ # scope so kody2's pushes trigger downstream CI and PR-body edits succeed.
1709
+
1710
+ name: kody2
1711
+
1712
+ on:
1713
+ workflow_dispatch:
1714
+ inputs:
1715
+ issue_number:
1716
+ description: "GitHub issue number"
1717
+ required: true
1718
+ type: string
1719
+ issue_comment:
1720
+ types: [created]
1721
+
1722
+ jobs:
1723
+ run:
1724
+ if: >-
1725
+ \${{ github.event_name == 'workflow_dispatch' ||
1726
+ (github.event_name == 'issue_comment' &&
1727
+ !github.event.issue.pull_request &&
1728
+ contains(github.event.comment.body, '@kody2')) }}
1729
+ runs-on: ubuntu-latest
1730
+ timeout-minutes: 60
1731
+ permissions:
1732
+ issues: write
1733
+ pull-requests: write
1734
+ contents: write
1735
+ steps:
1736
+ - uses: actions/checkout@v4
1737
+ with:
1738
+ fetch-depth: 0
1739
+ token: \${{ secrets.KODY_TOKEN || github.token }}
1740
+
1741
+ - uses: actions/setup-node@v4
1742
+ with:
1743
+ node-version: 22
1744
+
1745
+ - uses: actions/setup-python@v5
1746
+ with:
1747
+ python-version: "3.12"
1748
+
1749
+ - env:
1750
+ ALL_SECRETS: \${{ toJSON(secrets) }}
1751
+ run: npx -y -p @kody-ade/kody-engine@latest kody2 ci --issue \${{ github.event.inputs.issue_number || github.event.issue.number }}
1752
+ `;
1753
+ function defaultBranchFromGit(cwd) {
1754
+ try {
1755
+ const ref = execFileSync9("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
1756
+ cwd,
1757
+ encoding: "utf-8",
1758
+ stdio: ["ignore", "pipe", "pipe"]
1759
+ }).trim();
1760
+ return ref.replace("refs/remotes/origin/", "");
1761
+ } catch {
1762
+ try {
1763
+ return execFileSync9("git", ["branch", "--show-current"], {
1764
+ cwd,
1765
+ encoding: "utf-8",
1766
+ stdio: ["ignore", "pipe", "pipe"]
1767
+ }).trim() || "main";
1768
+ } catch {
1769
+ return "main";
1770
+ }
1771
+ }
1772
+ }
1773
+ function performInit(cwd, force) {
1774
+ const wrote = [];
1775
+ const skipped = [];
1776
+ const pm = detectPackageManager(cwd);
1777
+ const ownerRepo = detectOwnerRepo(cwd);
1778
+ const defaultBranch = defaultBranchFromGit(cwd);
1779
+ const configPath = path8.join(cwd, "kody.config.json");
1780
+ if (fs9.existsSync(configPath) && !force) {
1781
+ skipped.push("kody.config.json");
1782
+ } else {
1783
+ const cfg = makeConfig(pm, ownerRepo, defaultBranch);
1784
+ fs9.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
1785
+ `);
1786
+ wrote.push("kody.config.json");
1787
+ }
1788
+ const workflowDir = path8.join(cwd, ".github", "workflows");
1789
+ const workflowPath = path8.join(workflowDir, "kody2.yml");
1790
+ if (fs9.existsSync(workflowPath) && !force) {
1791
+ skipped.push(".github/workflows/kody2.yml");
1792
+ } else {
1793
+ fs9.mkdirSync(workflowDir, { recursive: true });
1794
+ fs9.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
1795
+ wrote.push(".github/workflows/kody2.yml");
1796
+ }
1797
+ return { wrote, skipped };
1798
+ }
1799
+ var initFlow = async (ctx) => {
1800
+ const force = ctx.args.force === true;
1801
+ const cwd = ctx.cwd;
1802
+ const { wrote, skipped } = performInit(cwd, force);
1803
+ process.stdout.write("\u2192 kody2 init\n");
1804
+ for (const f of wrote) process.stdout.write(` wrote ${f}
1805
+ `);
1806
+ for (const f of skipped) process.stdout.write(` skipped ${f} (already exists; pass --force to overwrite)
1807
+ `);
1808
+ process.stdout.write(
1809
+ wrote.length > 0 ? `
1810
+ Done. Edit kody.config.json to pick your model, then push the workflow file.
1811
+ ` : `
1812
+ Nothing to do. All files already present. (Use --force to overwrite.)
1813
+ `
1814
+ );
1815
+ ctx.skipAgent = true;
1816
+ ctx.output.exitCode = 0;
1817
+ };
1818
+
1654
1819
  // src/scripts/loadConventions.ts
1655
1820
  var loadConventions = async (ctx) => {
1656
1821
  const conventions = loadProjectConventions(ctx.cwd);
@@ -1734,7 +1899,7 @@ function postWith(type, n, body, cwd) {
1734
1899
  }
1735
1900
 
1736
1901
  // src/scripts/resolveFlow.ts
1737
- import { execFileSync as execFileSync9 } from "child_process";
1902
+ import { execFileSync as execFileSync10 } from "child_process";
1738
1903
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
1739
1904
  var resolveFlow = async (ctx) => {
1740
1905
  const prNumber = ctx.args.pr;
@@ -1786,7 +1951,7 @@ var resolveFlow = async (ctx) => {
1786
1951
  };
1787
1952
  function getConflictedFiles(cwd) {
1788
1953
  try {
1789
- const out = execFileSync9("git", ["diff", "--name-only", "--diff-filter=U"], {
1954
+ const out = execFileSync10("git", ["diff", "--name-only", "--diff-filter=U"], {
1790
1955
  encoding: "utf-8",
1791
1956
  cwd,
1792
1957
  env: { ...process.env, HUSKY: "0" }
@@ -1801,7 +1966,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
1801
1966
  let total = 0;
1802
1967
  for (const f of files) {
1803
1968
  try {
1804
- const content = execFileSync9("cat", [f], { encoding: "utf-8", cwd }).toString();
1969
+ const content = execFileSync10("cat", [f], { encoding: "utf-8", cwd }).toString();
1805
1970
  const snippet = `### ${f}
1806
1971
 
1807
1972
  \`\`\`
@@ -1939,7 +2104,7 @@ var verify = async (ctx) => {
1939
2104
  };
1940
2105
 
1941
2106
  // src/scripts/writeRunSummary.ts
1942
- import * as fs9 from "fs";
2107
+ import * as fs10 from "fs";
1943
2108
  var writeRunSummary = async (ctx) => {
1944
2109
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
1945
2110
  if (!summaryPath) return;
@@ -1961,7 +2126,7 @@ var writeRunSummary = async (ctx) => {
1961
2126
  if (reason) lines.push(`- **Reason:** ${reason}`);
1962
2127
  lines.push("");
1963
2128
  try {
1964
- fs9.appendFileSync(summaryPath, `${lines.join("\n")}
2129
+ fs10.appendFileSync(summaryPath, `${lines.join("\n")}
1965
2130
  `);
1966
2131
  } catch {
1967
2132
  }
@@ -1973,6 +2138,7 @@ var preflightScripts = {
1973
2138
  fixFlow,
1974
2139
  fixCiFlow,
1975
2140
  resolveFlow,
2141
+ initFlow,
1976
2142
  loadConventions,
1977
2143
  loadCoverageRules,
1978
2144
  composePrompt
@@ -1992,7 +2158,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
1992
2158
  ]);
1993
2159
 
1994
2160
  // src/tools.ts
1995
- import { execFileSync as execFileSync10 } from "child_process";
2161
+ import { execFileSync as execFileSync11 } from "child_process";
1996
2162
  function verifyCliTools(tools, cwd) {
1997
2163
  const out = [];
1998
2164
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -2025,7 +2191,7 @@ function verifyOne(tool, cwd) {
2025
2191
  }
2026
2192
  function runShell(cmd, cwd, timeoutMs = 3e4) {
2027
2193
  try {
2028
- execFileSync10("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
2194
+ execFileSync11("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
2029
2195
  return true;
2030
2196
  } catch {
2031
2197
  return false;
@@ -2076,7 +2242,7 @@ async function runExecutable(profileName, input) {
2076
2242
  data: {},
2077
2243
  output: { exitCode: 0 }
2078
2244
  };
2079
- const ndjsonDir = path8.join(input.cwd, ".kody2");
2245
+ const ndjsonDir = path9.join(input.cwd, ".kody2");
2080
2246
  const invokeAgent = async (prompt) => runAgent({
2081
2247
  prompt,
2082
2248
  model,
@@ -2135,17 +2301,17 @@ async function runExecutable(profileName, input) {
2135
2301
  }
2136
2302
  }
2137
2303
  function resolveProfilePath(profileName) {
2138
- const here = path8.dirname(new URL(import.meta.url).pathname);
2304
+ const here = path9.dirname(new URL(import.meta.url).pathname);
2139
2305
  const candidates = [
2140
- path8.join(here, "executables", profileName, "profile.json"),
2306
+ path9.join(here, "executables", profileName, "profile.json"),
2141
2307
  // same-dir sibling (dev)
2142
- path8.join(here, "..", "executables", profileName, "profile.json"),
2308
+ path9.join(here, "..", "executables", profileName, "profile.json"),
2143
2309
  // up one (prod: dist/bin → dist/executables)
2144
- path8.join(here, "..", "src", "executables", profileName, "profile.json")
2310
+ path9.join(here, "..", "src", "executables", profileName, "profile.json")
2145
2311
  // fallback
2146
2312
  ];
2147
2313
  for (const c of candidates) {
2148
- if (fs10.existsSync(c)) return c;
2314
+ if (fs11.existsSync(c)) return c;
2149
2315
  }
2150
2316
  return candidates[0];
2151
2317
  }
@@ -2223,12 +2389,12 @@ function finish(out) {
2223
2389
  }
2224
2390
 
2225
2391
  // src/kody2-cli.ts
2226
- import { execFileSync as execFileSync11 } from "child_process";
2227
- import * as fs12 from "fs";
2228
- import * as path9 from "path";
2392
+ import { execFileSync as execFileSync12 } from "child_process";
2393
+ import * as fs13 from "fs";
2394
+ import * as path10 from "path";
2229
2395
 
2230
2396
  // src/dispatch.ts
2231
- import * as fs11 from "fs";
2397
+ import * as fs12 from "fs";
2232
2398
  function autoDispatch(explicit) {
2233
2399
  if (explicit?.mode && explicit.target) {
2234
2400
  return {
@@ -2238,10 +2404,10 @@ function autoDispatch(explicit) {
2238
2404
  }
2239
2405
  const eventName = process.env.GITHUB_EVENT_NAME;
2240
2406
  const eventPath = process.env.GITHUB_EVENT_PATH;
2241
- if (!eventName || !eventPath || !fs11.existsSync(eventPath)) return null;
2407
+ if (!eventName || !eventPath || !fs12.existsSync(eventPath)) return null;
2242
2408
  let event = {};
2243
2409
  try {
2244
- event = JSON.parse(fs11.readFileSync(eventPath, "utf-8"));
2410
+ event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
2245
2411
  } catch {
2246
2412
  return null;
2247
2413
  }
@@ -2360,15 +2526,15 @@ function resolveAuthToken(env = process.env) {
2360
2526
  if (token && !env.GH_TOKEN) env.GH_TOKEN = token;
2361
2527
  return token;
2362
2528
  }
2363
- function detectPackageManager(cwd) {
2364
- if (fs12.existsSync(path9.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
2365
- if (fs12.existsSync(path9.join(cwd, "yarn.lock"))) return "yarn";
2366
- if (fs12.existsSync(path9.join(cwd, "bun.lockb"))) return "bun";
2529
+ function detectPackageManager2(cwd) {
2530
+ if (fs13.existsSync(path10.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
2531
+ if (fs13.existsSync(path10.join(cwd, "yarn.lock"))) return "yarn";
2532
+ if (fs13.existsSync(path10.join(cwd, "bun.lockb"))) return "bun";
2367
2533
  return "npm";
2368
2534
  }
2369
2535
  function shellOut(cmd, args, cwd, stream = true) {
2370
2536
  try {
2371
- execFileSync11(cmd, args, {
2537
+ execFileSync12(cmd, args, {
2372
2538
  cwd,
2373
2539
  stdio: stream ? "inherit" : "pipe",
2374
2540
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -2381,7 +2547,7 @@ function shellOut(cmd, args, cwd, stream = true) {
2381
2547
  }
2382
2548
  function isOnPath(bin) {
2383
2549
  try {
2384
- execFileSync11("which", [bin], { stdio: "pipe" });
2550
+ execFileSync12("which", [bin], { stdio: "pipe" });
2385
2551
  return true;
2386
2552
  } catch {
2387
2553
  return false;
@@ -2415,7 +2581,7 @@ function installLitellmIfNeeded(cwd) {
2415
2581
  } catch {
2416
2582
  }
2417
2583
  try {
2418
- execFileSync11("python3", ["-c", "import litellm"], { stdio: "pipe" });
2584
+ execFileSync12("python3", ["-c", "import litellm"], { stdio: "pipe" });
2419
2585
  process.stdout.write("\u2192 kody2: litellm already installed\n");
2420
2586
  return 0;
2421
2587
  } catch {
@@ -2425,26 +2591,26 @@ function installLitellmIfNeeded(cwd) {
2425
2591
  }
2426
2592
  function configureGitIdentity(cwd) {
2427
2593
  try {
2428
- const name = execFileSync11("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
2594
+ const name = execFileSync12("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
2429
2595
  if (name) return;
2430
2596
  } catch {
2431
2597
  }
2432
2598
  try {
2433
- execFileSync11("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
2599
+ execFileSync12("git", ["config", "user.name", "kody2-bot"], { cwd, stdio: "pipe" });
2434
2600
  } catch {
2435
2601
  }
2436
2602
  try {
2437
- execFileSync11("git", ["config", "user.email", "kody2-bot@users.noreply.github.com"], { cwd, stdio: "pipe" });
2603
+ execFileSync12("git", ["config", "user.email", "kody2-bot@users.noreply.github.com"], { cwd, stdio: "pipe" });
2438
2604
  } catch {
2439
2605
  }
2440
2606
  }
2441
2607
  function postFailureTail(issueNumber, cwd, reason) {
2442
2608
  if (!issueNumber) return;
2443
- const logPath = path9.join(cwd, ".kody2", "last-run.jsonl");
2609
+ const logPath = path10.join(cwd, ".kody2", "last-run.jsonl");
2444
2610
  let tail = "";
2445
2611
  try {
2446
- if (fs12.existsSync(logPath)) {
2447
- const content = fs12.readFileSync(logPath, "utf-8");
2612
+ if (fs13.existsSync(logPath)) {
2613
+ const content = fs13.readFileSync(logPath, "utf-8");
2448
2614
  tail = content.slice(-3e3);
2449
2615
  }
2450
2616
  } catch {
@@ -2481,7 +2647,7 @@ async function runCi(argv) {
2481
2647
  ${CI_HELP}`);
2482
2648
  return 64;
2483
2649
  }
2484
- const cwd = args.cwd ? path9.resolve(args.cwd) : process.cwd();
2650
+ const cwd = args.cwd ? path10.resolve(args.cwd) : process.cwd();
2485
2651
  const dispatch = autoFallback ?? {
2486
2652
  mode: "run",
2487
2653
  target: args.issueNumber,
@@ -2496,7 +2662,7 @@ ${CI_HELP}`);
2496
2662
  `);
2497
2663
  resolveAuthToken();
2498
2664
  reactToTriggerComment(cwd);
2499
- const pm = args.packageManager ?? detectPackageManager(cwd);
2665
+ const pm = args.packageManager ?? detectPackageManager2(cwd);
2500
2666
  process.stdout.write(`\u2192 kody2: package manager = ${pm}
2501
2667
  `);
2502
2668
  if (!args.skipInstall) {
@@ -2557,31 +2723,31 @@ ${CI_HELP}`);
2557
2723
  }
2558
2724
 
2559
2725
  // src/registry.ts
2560
- import * as fs13 from "fs";
2561
- import * as path10 from "path";
2726
+ import * as fs14 from "fs";
2727
+ import * as path11 from "path";
2562
2728
  function getExecutablesRoot() {
2563
- const here = path10.dirname(new URL(import.meta.url).pathname);
2729
+ const here = path11.dirname(new URL(import.meta.url).pathname);
2564
2730
  const candidates = [
2565
- path10.join(here, "executables"),
2731
+ path11.join(here, "executables"),
2566
2732
  // dev: src/
2567
- path10.join(here, "..", "executables"),
2733
+ path11.join(here, "..", "executables"),
2568
2734
  // built: dist/bin → dist/executables
2569
- path10.join(here, "..", "src", "executables")
2735
+ path11.join(here, "..", "src", "executables")
2570
2736
  // fallback
2571
2737
  ];
2572
2738
  for (const c of candidates) {
2573
- if (fs13.existsSync(c) && fs13.statSync(c).isDirectory()) return c;
2739
+ if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
2574
2740
  }
2575
2741
  return candidates[0];
2576
2742
  }
2577
2743
  function listExecutables(root = getExecutablesRoot()) {
2578
- if (!fs13.existsSync(root)) return [];
2579
- const entries = fs13.readdirSync(root, { withFileTypes: true });
2744
+ if (!fs14.existsSync(root)) return [];
2745
+ const entries = fs14.readdirSync(root, { withFileTypes: true });
2580
2746
  const out = [];
2581
2747
  for (const ent of entries) {
2582
2748
  if (!ent.isDirectory()) continue;
2583
- const profilePath = path10.join(root, ent.name, "profile.json");
2584
- if (fs13.existsSync(profilePath) && fs13.statSync(profilePath).isFile()) {
2749
+ const profilePath = path11.join(root, ent.name, "profile.json");
2750
+ if (fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile()) {
2585
2751
  out.push({ name: ent.name, profilePath });
2586
2752
  }
2587
2753
  }
@@ -2589,8 +2755,8 @@ function listExecutables(root = getExecutablesRoot()) {
2589
2755
  }
2590
2756
  function hasExecutable(name, root = getExecutablesRoot()) {
2591
2757
  if (!isSafeName(name)) return false;
2592
- const profilePath = path10.join(root, name, "profile.json");
2593
- return fs13.existsSync(profilePath) && fs13.statSync(profilePath).isFile();
2758
+ const profilePath = path11.join(root, name, "profile.json");
2759
+ return fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile();
2594
2760
  }
2595
2761
  function isSafeName(name) {
2596
2762
  return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
@@ -2728,16 +2894,27 @@ ${HELP_TEXT}`);
2728
2894
  }
2729
2895
  }
2730
2896
  const cwd = args.cwd ?? process.cwd();
2897
+ const configlessCommands = /* @__PURE__ */ new Set(["init"]);
2898
+ const needsConfig = !(args.command === "__executable__" && configlessCommands.has(args.executableName ?? ""));
2731
2899
  let config;
2732
- try {
2733
- config = loadConfig(cwd);
2734
- } catch (err) {
2735
- const msg = err instanceof Error ? err.message : String(err);
2736
- process.stderr.write(`[kody2] config error: ${msg}
2900
+ if (needsConfig) {
2901
+ try {
2902
+ config = loadConfig(cwd);
2903
+ } catch (err) {
2904
+ const msg = err instanceof Error ? err.message : String(err);
2905
+ process.stderr.write(`[kody2] config error: ${msg}
2737
2906
  `);
2738
- process.stdout.write(`PR_URL=FAILED: config error: ${msg}
2907
+ process.stdout.write(`PR_URL=FAILED: config error: ${msg}
2739
2908
  `);
2740
- return 99;
2909
+ return 99;
2910
+ }
2911
+ } else {
2912
+ config = {
2913
+ quality: { typecheck: "", lint: "", testUnit: "" },
2914
+ git: { defaultBranch: "main" },
2915
+ github: { owner: "", repo: "" },
2916
+ agent: { model: "claude/claude-haiku-4-5-20251001" }
2917
+ };
2741
2918
  }
2742
2919
  if (args.command === "__executable__") {
2743
2920
  try {
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "init",
3
+ "describe": "Scaffold a consumer repo with kody.config.json and the @kody2 workflow. No agent.",
4
+
5
+ "inputs": [
6
+ {
7
+ "name": "force",
8
+ "flag": "--force",
9
+ "type": "bool",
10
+ "required": false,
11
+ "describe": "Overwrite existing files instead of skipping them."
12
+ }
13
+ ],
14
+
15
+ "claudeCode": {
16
+ "model": "inherit",
17
+ "permissionMode": "acceptEdits",
18
+ "maxTurns": null,
19
+ "systemPromptAppend": null,
20
+ "tools": [],
21
+ "hooks": { "PreToolUse": [], "PostToolUse": [], "Stop": [] },
22
+ "skills": [],
23
+ "commands": [],
24
+ "subagents": [],
25
+ "plugins": [],
26
+ "mcpServers": []
27
+ },
28
+
29
+ "cliTools": [],
30
+
31
+ "scripts": {
32
+ "preflight": [
33
+ { "script": "initFlow" }
34
+ ],
35
+ "postflight": []
36
+ }
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
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",