@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.
- package/dist/bin/cli.js +264 -38
- 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,
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
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
|
|
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
|
-
|
|
2179
|
-
|
|
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
|
-
|
|
2411
|
+
fs7.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
|
|
2186
2412
|
|
|
2187
2413
|
## Overview
|
|
2188
2414
|
${archItems.join("\n")}
|
|
2189
|
-
|
|
2190
|
-
|
|
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
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
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
|
}
|