@kody-ade/kody-engine-lite 0.1.7 → 0.1.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.
Files changed (2) hide show
  1. package/dist/bin/cli.js +264 -38
  2. package/package.json +1 -1
package/dist/bin/cli.js CHANGED
@@ -109,11 +109,17 @@ function createClaudeCodeRunner() {
109
109
  }
110
110
  function createOpenCodeRunner() {
111
111
  return {
112
- async run(stageName, prompt, _model, timeout, _taskDir, options) {
112
+ async run(stageName, prompt, model, timeout, _taskDir, options) {
113
+ const args2 = ["run"];
114
+ if (model) {
115
+ args2.push("--model", model);
116
+ }
117
+ args2.push(prompt);
113
118
  return runSubprocess(
114
119
  "opencode",
115
- ["github", "run", "--agent", stageName],
116
- prompt,
120
+ args2,
121
+ "",
122
+ // opencode takes message as positional, not stdin
117
123
  timeout,
118
124
  options
119
125
  );
@@ -892,8 +898,9 @@ async function executeAgentStage(ctx, def) {
892
898
  }
893
899
  const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
894
900
  const model = resolveModel(def.modelTier, def.name);
895
- logger.info(` model=${model} timeout=${def.timeout / 1e3}s`);
896
901
  const config = getProjectConfig();
902
+ const runnerName = config.agent.stageRunners?.[def.name] ?? config.agent.defaultRunner ?? Object.keys(ctx.runners)[0] ?? "claude";
903
+ logger.info(` runner=${runnerName} model=${model} timeout=${def.timeout / 1e3}s`);
897
904
  const extraEnv = {};
898
905
  if (config.agent.litellmUrl) {
899
906
  extraEnv.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
@@ -2004,6 +2011,235 @@ function detectArchitecture(cwd) {
2004
2011
  if (configs.length > 0) detected.push(`- Config files: ${configs.join(", ")}`);
2005
2012
  return detected;
2006
2013
  }
2014
+ function detectBasicConfig(cwd) {
2015
+ let pm = "pnpm";
2016
+ if (fs7.existsSync(path6.join(cwd, "yarn.lock"))) pm = "yarn";
2017
+ else if (fs7.existsSync(path6.join(cwd, "bun.lockb"))) pm = "bun";
2018
+ else if (!fs7.existsSync(path6.join(cwd, "pnpm-lock.yaml")) && fs7.existsSync(path6.join(cwd, "package-lock.json"))) pm = "npm";
2019
+ let defaultBranch = "main";
2020
+ try {
2021
+ const ref = execFileSync7("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
2022
+ encoding: "utf-8",
2023
+ timeout: 5e3,
2024
+ cwd,
2025
+ stdio: ["pipe", "pipe", "pipe"]
2026
+ }).trim();
2027
+ defaultBranch = ref.replace("refs/remotes/origin/", "");
2028
+ } catch {
2029
+ try {
2030
+ execFileSync7("git", ["rev-parse", "--verify", "origin/dev"], {
2031
+ encoding: "utf-8",
2032
+ timeout: 5e3,
2033
+ cwd,
2034
+ stdio: ["pipe", "pipe", "pipe"]
2035
+ });
2036
+ defaultBranch = "dev";
2037
+ } catch {
2038
+ }
2039
+ }
2040
+ let owner = "";
2041
+ let repo = "";
2042
+ try {
2043
+ const remote = execFileSync7("git", ["remote", "get-url", "origin"], {
2044
+ encoding: "utf-8",
2045
+ timeout: 5e3,
2046
+ cwd,
2047
+ stdio: ["pipe", "pipe", "pipe"]
2048
+ }).trim();
2049
+ const match = remote.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
2050
+ if (match) {
2051
+ owner = match[1];
2052
+ repo = match[2];
2053
+ }
2054
+ } catch {
2055
+ }
2056
+ const hasOpenCode = fs7.existsSync(path6.join(cwd, "opencode.json"));
2057
+ return { defaultBranch, owner, repo, pm, hasOpenCode };
2058
+ }
2059
+ function smartInit(cwd) {
2060
+ const basic = detectBasicConfig(cwd);
2061
+ let context = "";
2062
+ const readIfExists = (rel, maxChars = 3e3) => {
2063
+ const p = path6.join(cwd, rel);
2064
+ if (fs7.existsSync(p)) {
2065
+ const content = fs7.readFileSync(p, "utf-8");
2066
+ return content.slice(0, maxChars);
2067
+ }
2068
+ return null;
2069
+ };
2070
+ const pkgJson = readIfExists("package.json");
2071
+ if (pkgJson) context += `## package.json
2072
+ ${pkgJson}
2073
+
2074
+ `;
2075
+ const tsconfig = readIfExists("tsconfig.json", 1e3);
2076
+ if (tsconfig) context += `## tsconfig.json
2077
+ ${tsconfig}
2078
+
2079
+ `;
2080
+ const readme = readIfExists("README.md", 2e3);
2081
+ if (readme) context += `## README.md (first 2000 chars)
2082
+ ${readme}
2083
+
2084
+ `;
2085
+ const claudeMd = readIfExists("CLAUDE.md", 3e3);
2086
+ if (claudeMd) context += `## CLAUDE.md
2087
+ ${claudeMd}
2088
+
2089
+ `;
2090
+ try {
2091
+ const topDirs = fs7.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
2092
+ context += `## Top-level directories
2093
+ ${topDirs.join(", ")}
2094
+
2095
+ `;
2096
+ const srcDir = path6.join(cwd, "src");
2097
+ if (fs7.existsSync(srcDir)) {
2098
+ const srcDirs = fs7.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
2099
+ context += `## src/ subdirectories
2100
+ ${srcDirs.join(", ")}
2101
+
2102
+ `;
2103
+ }
2104
+ } catch {
2105
+ }
2106
+ const existingFiles = [];
2107
+ for (const f of [".env.example", "CLAUDE.md", ".ai-docs", "opencode.json", "vitest.config.ts", "vitest.config.mts", "jest.config.ts", "playwright.config.ts", ".eslintrc.js", "eslint.config.mjs", ".prettierrc"]) {
2108
+ if (fs7.existsSync(path6.join(cwd, f))) existingFiles.push(f);
2109
+ }
2110
+ if (existingFiles.length) context += `## Config files present
2111
+ ${existingFiles.join(", ")}
2112
+
2113
+ `;
2114
+ context += `## Detected: package manager=${basic.pm}, default branch=${basic.defaultBranch}, github=${basic.owner}/${basic.repo}, opencode=${basic.hasOpenCode}
2115
+ `;
2116
+ const prompt = `You are analyzing a project to configure Kody (an autonomous SDLC pipeline).
2117
+
2118
+ Given this project context, output ONLY a JSON object with EXACTLY this structure:
2119
+
2120
+ {
2121
+ "config": {
2122
+ "quality": {
2123
+ "typecheck": "${basic.pm} <script or command>",
2124
+ "lint": "${basic.pm} <script or command>",
2125
+ "lintFix": "${basic.pm} <script or command>",
2126
+ "format": "${basic.pm} <script or command>",
2127
+ "formatFix": "${basic.pm} <script or command>",
2128
+ "testUnit": "${basic.pm} <script or command>"
2129
+ },
2130
+ "git": { "defaultBranch": "${basic.defaultBranch}" },
2131
+ "github": { "owner": "${basic.owner}", "repo": "${basic.repo}" },
2132
+ "paths": { "taskDir": ".tasks" },
2133
+ "agent": {
2134
+ "runner": "${basic.hasOpenCode ? "opencode" : "claude-code"}",
2135
+ "defaultRunner": "${basic.hasOpenCode ? "opencode" : "claude"}",
2136
+ "modelMap": { "cheap": "haiku", "mid": "sonnet", "strong": "opus" }
2137
+ }
2138
+ },
2139
+ "architecture": "# Architecture\\n\\n<markdown content>",
2140
+ "conventions": "# Conventions\\n\\n<markdown content>"
2141
+ }
2142
+
2143
+ CRITICAL rules for config.quality:
2144
+ - Every command MUST start with "${basic.pm}" (e.g., "${basic.pm} typecheck", "${basic.pm} lint")
2145
+ - Look at the package.json "scripts" section to find the correct script names
2146
+ - testUnit must run ONLY unit tests \u2014 exclude integration and e2e tests. If there's a "test:unit" script use it. Otherwise use "test" but add exclude flags for int/e2e.
2147
+ - If a script doesn't exist and can't be inferred, set the value to ""
2148
+ - Do NOT invent commands that don't exist in package.json scripts
2149
+
2150
+ Rules for architecture (markdown string):
2151
+ - Be specific about THIS project
2152
+ - Include: framework, language, database, testing, key directories, data flow
2153
+ - Reference CLAUDE.md and .ai-docs/ if they exist
2154
+ - Keep under 50 lines
2155
+
2156
+ Rules for conventions (markdown string):
2157
+ - Extract actual patterns from the project
2158
+ - If CLAUDE.md exists, reference it
2159
+ - If .ai-docs/ exists, reference it
2160
+ - Keep under 30 lines
2161
+
2162
+ Output ONLY valid JSON. No markdown fences. No explanation before or after.
2163
+
2164
+ ${context}`;
2165
+ console.log(" \u23F3 Analyzing project with Claude Code...");
2166
+ try {
2167
+ const output = execFileSync7("claude", [
2168
+ "--print",
2169
+ "--model",
2170
+ "haiku",
2171
+ "--dangerously-skip-permissions",
2172
+ prompt
2173
+ ], {
2174
+ encoding: "utf-8",
2175
+ timeout: 12e4,
2176
+ cwd,
2177
+ stdio: ["pipe", "pipe", "pipe"]
2178
+ }).trim();
2179
+ const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2180
+ const parsed = JSON.parse(cleaned);
2181
+ const config = parsed.config ?? {};
2182
+ if (!config.git) config.git = {};
2183
+ if (!config.github) config.github = {};
2184
+ if (!config.paths) config.paths = {};
2185
+ if (!config.agent) config.agent = {};
2186
+ config.git.defaultBranch = config.git.defaultBranch || basic.defaultBranch;
2187
+ config.github.owner = config.github.owner || basic.owner;
2188
+ config.github.repo = config.github.repo || basic.repo;
2189
+ config.paths.taskDir = config.paths.taskDir || ".tasks";
2190
+ config.agent.runner = config.agent.runner || (basic.hasOpenCode ? "opencode" : "claude-code");
2191
+ config.agent.defaultRunner = config.agent.defaultRunner || (basic.hasOpenCode ? "opencode" : "claude");
2192
+ if (!config.agent.modelMap) {
2193
+ config.agent.modelMap = { cheap: "haiku", mid: "sonnet", strong: "opus" };
2194
+ }
2195
+ return {
2196
+ config,
2197
+ architecture: parsed.architecture ?? "",
2198
+ conventions: parsed.conventions ?? ""
2199
+ };
2200
+ } catch (err) {
2201
+ console.log(" \u26A0 Smart detection failed, falling back to basic detection");
2202
+ return {
2203
+ config: buildFallbackConfig(cwd, basic),
2204
+ architecture: "",
2205
+ conventions: ""
2206
+ };
2207
+ }
2208
+ }
2209
+ function buildFallbackConfig(cwd, basic) {
2210
+ const pkg = (() => {
2211
+ try {
2212
+ return JSON.parse(fs7.readFileSync(path6.join(cwd, "package.json"), "utf-8"));
2213
+ } catch {
2214
+ return {};
2215
+ }
2216
+ })();
2217
+ const scripts = pkg.scripts ?? {};
2218
+ const find = (...c) => {
2219
+ for (const s of c) {
2220
+ if (scripts[s]) return `${basic.pm} ${s}`;
2221
+ }
2222
+ return "";
2223
+ };
2224
+ return {
2225
+ quality: {
2226
+ typecheck: find("typecheck", "type-check") || (pkg.devDependencies?.typescript ? `${basic.pm} tsc --noEmit` : ""),
2227
+ lint: find("lint"),
2228
+ lintFix: find("lint:fix", "lint-fix"),
2229
+ format: find("format:check"),
2230
+ formatFix: find("format", "format:fix"),
2231
+ testUnit: find("test:unit", "test", "test:ci")
2232
+ },
2233
+ git: { defaultBranch: basic.defaultBranch },
2234
+ github: { owner: basic.owner, repo: basic.repo },
2235
+ paths: { taskDir: ".tasks" },
2236
+ agent: {
2237
+ runner: basic.hasOpenCode ? "opencode" : "claude-code",
2238
+ defaultRunner: basic.hasOpenCode ? "opencode" : "claude",
2239
+ modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
2240
+ }
2241
+ };
2242
+ }
2007
2243
  function initCommand(opts) {
2008
2244
  const cwd = process.cwd();
2009
2245
  console.log(`
@@ -2027,26 +2263,11 @@ function initCommand(opts) {
2027
2263
  console.log(" \u2713 .github/workflows/kody.yml");
2028
2264
  }
2029
2265
  const configDest = path6.join(cwd, "kody.config.json");
2030
- if (!fs7.existsSync(configDest)) {
2031
- const defaultConfig = {
2032
- quality: {
2033
- typecheck: "pnpm tsc --noEmit",
2034
- lint: "",
2035
- lintFix: "",
2036
- format: "",
2037
- formatFix: "",
2038
- testUnit: "pnpm test"
2039
- },
2040
- git: { defaultBranch: "main" },
2041
- github: { owner: "", repo: "" },
2042
- paths: { taskDir: ".tasks" },
2043
- agent: {
2044
- runner: "claude-code",
2045
- modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
2046
- }
2047
- };
2048
- fs7.writeFileSync(configDest, JSON.stringify(defaultConfig, null, 2) + "\n");
2049
- console.log(" \u2713 kody.config.json (created \u2014 edit github.owner and github.repo)");
2266
+ let smartResult = null;
2267
+ if (!fs7.existsSync(configDest) || opts.force) {
2268
+ smartResult = smartInit(cwd);
2269
+ fs7.writeFileSync(configDest, JSON.stringify(smartResult.config, null, 2) + "\n");
2270
+ console.log(" \u2713 kody.config.json (auto-configured)");
2050
2271
  } else {
2051
2272
  console.log(" \u25CB kody.config.json (exists)");
2052
2273
  }
@@ -2172,33 +2393,38 @@ function initCommand(opts) {
2172
2393
  console.log(" \u2717 kody.config.json \u2014 invalid JSON");
2173
2394
  }
2174
2395
  }
2175
- console.log("\n\u2500\u2500 Architecture Detection \u2500\u2500");
2396
+ console.log("\n\u2500\u2500 Project Memory \u2500\u2500");
2176
2397
  const memoryDir = path6.join(cwd, ".kody", "memory");
2398
+ fs7.mkdirSync(memoryDir, { recursive: true });
2177
2399
  const archPath = path6.join(memoryDir, "architecture.md");
2178
- if (fs7.existsSync(archPath)) {
2179
- console.log(" \u25CB .kody/memory/architecture.md (exists, not overwriting)");
2400
+ const conventionsPath = path6.join(memoryDir, "conventions.md");
2401
+ if (fs7.existsSync(archPath) && !opts.force) {
2402
+ console.log(" \u25CB .kody/memory/architecture.md (exists, use --force to regenerate)");
2403
+ } else if (smartResult?.architecture) {
2404
+ fs7.writeFileSync(archPath, smartResult.architecture);
2405
+ const lineCount = smartResult.architecture.split("\n").length;
2406
+ console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines, LLM-generated)`);
2180
2407
  } else {
2181
2408
  const archItems = detectArchitecture(cwd);
2182
2409
  if (archItems.length > 0) {
2183
- fs7.mkdirSync(memoryDir, { recursive: true });
2184
2410
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2185
- const content = `# Architecture (auto-detected ${timestamp2})
2411
+ fs7.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
2186
2412
 
2187
2413
  ## Overview
2188
2414
  ${archItems.join("\n")}
2189
- `;
2190
- fs7.writeFileSync(archPath, content);
2191
- console.log(` \u2713 .kody/memory/architecture.md (${archItems.length} items detected)`);
2192
- for (const item of archItems) {
2193
- console.log(` ${item}`);
2194
- }
2415
+ `);
2416
+ console.log(` \u2713 .kody/memory/architecture.md (${archItems.length} items, basic detection)`);
2195
2417
  } else {
2196
2418
  console.log(" \u25CB No architecture detected");
2197
2419
  }
2198
2420
  }
2199
- const conventionsPath = path6.join(memoryDir, "conventions.md");
2200
- if (!fs7.existsSync(conventionsPath)) {
2201
- fs7.mkdirSync(memoryDir, { recursive: true });
2421
+ if (fs7.existsSync(conventionsPath) && !opts.force) {
2422
+ console.log(" \u25CB .kody/memory/conventions.md (exists, use --force to regenerate)");
2423
+ } else if (smartResult?.conventions) {
2424
+ fs7.writeFileSync(conventionsPath, smartResult.conventions);
2425
+ const lineCount = smartResult.conventions.split("\n").length;
2426
+ console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines, LLM-generated)`);
2427
+ } else {
2202
2428
  fs7.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
2203
2429
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
2204
2430
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",