@kody-ade/kody-engine-lite 0.1.7 → 0.1.9

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/README.md CHANGED
@@ -112,7 +112,7 @@ git push
112
112
  Then comment on any issue:
113
113
 
114
114
  ```
115
- @kody full my-task-id
115
+ @kody
116
116
  ```
117
117
 
118
118
  ## CLI Usage
@@ -121,19 +121,25 @@ Then comment on any issue:
121
121
 
122
122
  ```bash
123
123
  # Run against current directory
124
- kody-engine-lite run --task-id my-task --task "Add a sum function to src/math.ts with tests"
124
+ kody-engine-lite run --task "Add a sum function to src/math.ts with tests"
125
125
 
126
126
  # Run against a different project
127
- kody-engine-lite run --task-id my-task --task "Add feature X" --cwd /path/to/project
127
+ kody-engine-lite run --task "Add feature X" --cwd /path/to/project
128
128
 
129
129
  # Run from a GitHub issue (fetches issue body as task)
130
- kody-engine-lite run --task-id my-task --issue-number 1 --cwd /path/to/project
130
+ kody-engine-lite run --issue-number 1 --cwd /path/to/project
131
131
 
132
132
  # Dry run (no agent calls)
133
- kody-engine-lite run --task-id my-task --task "Test" --dry-run
133
+ kody-engine-lite run --task "Test" --dry-run
134
134
 
135
- # Resume from a failed stage
136
- kody-engine-lite rerun --task-id my-task --from review
135
+ # Resume from a failed/paused stage (auto-detects which stage)
136
+ kody-engine-lite rerun --issue-number 1
137
+
138
+ # Fix — rerun from build stage (skip taskify/plan)
139
+ kody-engine-lite fix --issue-number 1
140
+
141
+ # Fix with feedback
142
+ kody-engine-lite fix --issue-number 1 --feedback "Use middleware pattern instead"
137
143
 
138
144
  # Check pipeline status
139
145
  kody-engine-lite status --task-id my-task
@@ -154,9 +160,11 @@ Comment on any issue:
154
160
  ```
155
161
  @kody # Run full pipeline (auto-generates task-id)
156
162
  @kody full <task-id> # Run with specific task-id
157
- @kody rerun --from <stage> # Resume latest task from stage
158
- @kody rerun <task-id> --from <stage> # Resume specific task
159
- @kody approve # Approve + provide answers to Kody's questions
163
+ @kody rerun # Resume latest task (auto-detects stage)
164
+ @kody rerun --from <stage> # Resume from specific stage
165
+ @kody fix # Re-build from build stage (skip taskify/plan)
166
+ @kody fix --feedback "Use X instead of Y" # Fix with specific guidance
167
+ @kody approve # Approve + provide answers to questions
160
168
  @kody status <task-id> # Check status
161
169
  ```
162
170
 
@@ -195,6 +203,15 @@ Kody resumes automatically from where it paused, with your answers injected as c
195
203
  | review-fix | sonnet | Applies known fixes from review findings |
196
204
  | ship | — | Pushes branch + creates PR + comments on issue |
197
205
 
206
+ ### Branch Syncing
207
+
208
+ On every run/rerun/fix, Kody automatically:
209
+ 1. Checks out (or creates) the feature branch
210
+ 2. Pulls latest from the default branch and merges into the feature branch
211
+ 3. If there's a merge conflict, skips the sync and warns
212
+
213
+ This ensures the feature branch is always up-to-date before building.
214
+
198
215
  ### Automatic Loops
199
216
 
200
217
  - **Verify + autofix**: If verify fails, runs lint-fix + format-fix + autofix agent, retries up to 2 times
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
  );
@@ -582,6 +588,27 @@ function ensureFeatureBranch(issueNumber, title, cwd) {
582
588
  logger.info(` Created new branch: ${branchName}`);
583
589
  return branchName;
584
590
  }
591
+ function syncWithDefault(cwd) {
592
+ const defaultBranch = getDefaultBranch(cwd);
593
+ const current = getCurrentBranch(cwd);
594
+ if (current === defaultBranch) return;
595
+ try {
596
+ git(["fetch", "origin", defaultBranch], { cwd, timeout: 3e4 });
597
+ } catch {
598
+ logger.warn(" Failed to fetch latest from origin");
599
+ return;
600
+ }
601
+ try {
602
+ git(["merge", `origin/${defaultBranch}`, "--no-edit"], { cwd, timeout: 3e4 });
603
+ logger.info(` Synced with origin/${defaultBranch}`);
604
+ } catch {
605
+ try {
606
+ git(["merge", "--abort"], { cwd });
607
+ } catch {
608
+ }
609
+ logger.warn(` Merge conflict with origin/${defaultBranch} \u2014 skipping sync`);
610
+ }
611
+ }
585
612
  function commitAll(message, cwd) {
586
613
  const status = git(["status", "--porcelain"], { cwd });
587
614
  if (!status) {
@@ -892,8 +919,9 @@ async function executeAgentStage(ctx, def) {
892
919
  }
893
920
  const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
894
921
  const model = resolveModel(def.modelTier, def.name);
895
- logger.info(` model=${model} timeout=${def.timeout / 1e3}s`);
896
922
  const config = getProjectConfig();
923
+ const runnerName = config.agent.stageRunners?.[def.name] ?? config.agent.defaultRunner ?? Object.keys(ctx.runners)[0] ?? "claude";
924
+ logger.info(` runner=${runnerName} model=${model} timeout=${def.timeout / 1e3}s`);
897
925
  const extraEnv = {};
898
926
  if (config.agent.litellmUrl) {
899
927
  extraEnv.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
@@ -1190,8 +1218,9 @@ async function runPipeline(ctx) {
1190
1218
  const taskMdPath = path4.join(ctx.taskDir, "task.md");
1191
1219
  const title = fs4.existsSync(taskMdPath) ? fs4.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
1192
1220
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
1221
+ syncWithDefault(ctx.projectDir);
1193
1222
  } catch (err) {
1194
- logger.warn(` Failed to create feature branch: ${err}`);
1223
+ logger.warn(` Failed to create/sync feature branch: ${err}`);
1195
1224
  }
1196
1225
  }
1197
1226
  let complexity = ctx.input.complexity ?? "high";
@@ -1621,12 +1650,13 @@ function parseArgs() {
1621
1650
  console.log(`Usage:
1622
1651
  kody run --task-id <id> [--task "<desc>"] [--cwd <path>] [--issue-number <n>] [--complexity low|medium|high] [--feedback "<text>"] [--local] [--dry-run]
1623
1652
  kody rerun --task-id <id> --from <stage> [--cwd <path>] [--issue-number <n>]
1653
+ kody fix --task-id <id> [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
1624
1654
  kody status --task-id <id> [--cwd <path>]
1625
1655
  kody --help`);
1626
1656
  process.exit(0);
1627
1657
  }
1628
1658
  const command2 = args2[0];
1629
- if (!["run", "rerun", "status"].includes(command2)) {
1659
+ if (!["run", "rerun", "fix", "status"].includes(command2)) {
1630
1660
  console.error(`Unknown command: ${command2}`);
1631
1661
  process.exit(1);
1632
1662
  }
@@ -1671,7 +1701,7 @@ async function main() {
1671
1701
  }
1672
1702
  let taskId = input.taskId;
1673
1703
  if (!taskId) {
1674
- if (input.command === "rerun" && input.issueNumber) {
1704
+ if ((input.command === "rerun" || input.command === "fix") && input.issueNumber) {
1675
1705
  const found = findLatestTaskForIssue(input.issueNumber, projectDir);
1676
1706
  if (!found) {
1677
1707
  console.error(`No previous task found for issue #${input.issueNumber}`);
@@ -1717,6 +1747,9 @@ ${issue.body ?? ""}`;
1717
1747
  process.exit(1);
1718
1748
  }
1719
1749
  }
1750
+ if (input.command === "fix" && !input.fromStage) {
1751
+ input.fromStage = "build";
1752
+ }
1720
1753
  if (input.command === "rerun" && !input.fromStage) {
1721
1754
  const statusPath = path5.join(taskDir, "status.json");
1722
1755
  if (fs6.existsSync(statusPath)) {
@@ -1774,7 +1807,7 @@ ${issue.body ?? ""}`;
1774
1807
  projectDir,
1775
1808
  runners,
1776
1809
  input: {
1777
- mode: input.command === "rerun" ? "rerun" : "full",
1810
+ mode: input.command === "rerun" || input.command === "fix" ? "rerun" : "full",
1778
1811
  fromStage: input.fromStage,
1779
1812
  dryRun: input.dryRun,
1780
1813
  issueNumber: input.issueNumber,
@@ -2004,6 +2037,270 @@ function detectArchitecture(cwd) {
2004
2037
  if (configs.length > 0) detected.push(`- Config files: ${configs.join(", ")}`);
2005
2038
  return detected;
2006
2039
  }
2040
+ function detectBasicConfig(cwd) {
2041
+ let pm = "pnpm";
2042
+ if (fs7.existsSync(path6.join(cwd, "yarn.lock"))) pm = "yarn";
2043
+ else if (fs7.existsSync(path6.join(cwd, "bun.lockb"))) pm = "bun";
2044
+ else if (!fs7.existsSync(path6.join(cwd, "pnpm-lock.yaml")) && fs7.existsSync(path6.join(cwd, "package-lock.json"))) pm = "npm";
2045
+ let defaultBranch = "main";
2046
+ try {
2047
+ const ref = execFileSync7("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
2048
+ encoding: "utf-8",
2049
+ timeout: 5e3,
2050
+ cwd,
2051
+ stdio: ["pipe", "pipe", "pipe"]
2052
+ }).trim();
2053
+ defaultBranch = ref.replace("refs/remotes/origin/", "");
2054
+ } catch {
2055
+ try {
2056
+ execFileSync7("git", ["rev-parse", "--verify", "origin/dev"], {
2057
+ encoding: "utf-8",
2058
+ timeout: 5e3,
2059
+ cwd,
2060
+ stdio: ["pipe", "pipe", "pipe"]
2061
+ });
2062
+ defaultBranch = "dev";
2063
+ } catch {
2064
+ }
2065
+ }
2066
+ let owner = "";
2067
+ let repo = "";
2068
+ try {
2069
+ const remote = execFileSync7("git", ["remote", "get-url", "origin"], {
2070
+ encoding: "utf-8",
2071
+ timeout: 5e3,
2072
+ cwd,
2073
+ stdio: ["pipe", "pipe", "pipe"]
2074
+ }).trim();
2075
+ const match = remote.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
2076
+ if (match) {
2077
+ owner = match[1];
2078
+ repo = match[2];
2079
+ }
2080
+ } catch {
2081
+ }
2082
+ const hasOpenCode = fs7.existsSync(path6.join(cwd, "opencode.json"));
2083
+ return { defaultBranch, owner, repo, pm, hasOpenCode };
2084
+ }
2085
+ function smartInit(cwd) {
2086
+ const basic = detectBasicConfig(cwd);
2087
+ let context = "";
2088
+ const readIfExists = (rel, maxChars = 3e3) => {
2089
+ const p = path6.join(cwd, rel);
2090
+ if (fs7.existsSync(p)) {
2091
+ const content = fs7.readFileSync(p, "utf-8");
2092
+ return content.slice(0, maxChars);
2093
+ }
2094
+ return null;
2095
+ };
2096
+ const pkgJson = readIfExists("package.json");
2097
+ if (pkgJson) context += `## package.json
2098
+ ${pkgJson}
2099
+
2100
+ `;
2101
+ const tsconfig = readIfExists("tsconfig.json", 1e3);
2102
+ if (tsconfig) context += `## tsconfig.json
2103
+ ${tsconfig}
2104
+
2105
+ `;
2106
+ const readme = readIfExists("README.md", 2e3);
2107
+ if (readme) context += `## README.md (first 2000 chars)
2108
+ ${readme}
2109
+
2110
+ `;
2111
+ const claudeMd = readIfExists("CLAUDE.md", 3e3);
2112
+ if (claudeMd) context += `## CLAUDE.md
2113
+ ${claudeMd}
2114
+
2115
+ `;
2116
+ try {
2117
+ const topDirs = fs7.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
2118
+ context += `## Top-level directories
2119
+ ${topDirs.join(", ")}
2120
+
2121
+ `;
2122
+ const srcDir = path6.join(cwd, "src");
2123
+ if (fs7.existsSync(srcDir)) {
2124
+ const srcDirs = fs7.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
2125
+ context += `## src/ subdirectories
2126
+ ${srcDirs.join(", ")}
2127
+
2128
+ `;
2129
+ }
2130
+ } catch {
2131
+ }
2132
+ const existingFiles = [];
2133
+ 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"]) {
2134
+ if (fs7.existsSync(path6.join(cwd, f))) existingFiles.push(f);
2135
+ }
2136
+ if (existingFiles.length) context += `## Config files present
2137
+ ${existingFiles.join(", ")}
2138
+
2139
+ `;
2140
+ context += `## Detected: package manager=${basic.pm}, default branch=${basic.defaultBranch}, github=${basic.owner}/${basic.repo}, opencode=${basic.hasOpenCode}
2141
+ `;
2142
+ const prompt = `You are analyzing a project to configure Kody (an autonomous SDLC pipeline).
2143
+
2144
+ Given this project context, output ONLY a JSON object with EXACTLY this structure:
2145
+
2146
+ {
2147
+ "config": {
2148
+ "quality": {
2149
+ "typecheck": "${basic.pm} <script or command>",
2150
+ "lint": "${basic.pm} <script or command>",
2151
+ "lintFix": "${basic.pm} <script or command>",
2152
+ "format": "${basic.pm} <script or command>",
2153
+ "formatFix": "${basic.pm} <script or command>",
2154
+ "testUnit": "${basic.pm} <script or command>"
2155
+ },
2156
+ "git": { "defaultBranch": "${basic.defaultBranch}" },
2157
+ "github": { "owner": "${basic.owner}", "repo": "${basic.repo}" },
2158
+ "paths": { "taskDir": ".tasks" },
2159
+ "agent": {
2160
+ "runner": "${basic.hasOpenCode ? "opencode" : "claude-code"}",
2161
+ "defaultRunner": "${basic.hasOpenCode ? "opencode" : "claude"}",
2162
+ "modelMap": { "cheap": "haiku", "mid": "sonnet", "strong": "opus" }
2163
+ }
2164
+ },
2165
+ "architecture": "# Architecture\\n\\n<markdown content>",
2166
+ "conventions": "# Conventions\\n\\n<markdown content>"
2167
+ }
2168
+
2169
+ CRITICAL rules for config.quality:
2170
+ - Every command MUST start with "${basic.pm}" (e.g., "${basic.pm} typecheck", "${basic.pm} lint")
2171
+ - Look at the package.json "scripts" section to find the correct script names
2172
+ - 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.
2173
+ - If a script doesn't exist and can't be inferred, set the value to ""
2174
+ - Do NOT invent commands that don't exist in package.json scripts
2175
+
2176
+ Rules for architecture (markdown string):
2177
+ - Be specific about THIS project
2178
+ - Include: framework, language, database, testing, key directories, data flow
2179
+ - Reference CLAUDE.md and .ai-docs/ if they exist
2180
+ - Keep under 50 lines
2181
+
2182
+ Rules for conventions (markdown string):
2183
+ - Extract actual patterns from the project
2184
+ - If CLAUDE.md exists, reference it
2185
+ - If .ai-docs/ exists, reference it
2186
+ - Keep under 30 lines
2187
+
2188
+ Output ONLY valid JSON. No markdown fences. No explanation before or after.
2189
+
2190
+ ${context}`;
2191
+ console.log(" \u23F3 Analyzing project with Claude Code...");
2192
+ try {
2193
+ const output = execFileSync7("claude", [
2194
+ "--print",
2195
+ "--model",
2196
+ "haiku",
2197
+ "--dangerously-skip-permissions",
2198
+ prompt
2199
+ ], {
2200
+ encoding: "utf-8",
2201
+ timeout: 12e4,
2202
+ cwd,
2203
+ stdio: ["pipe", "pipe", "pipe"]
2204
+ }).trim();
2205
+ const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2206
+ const parsed = JSON.parse(cleaned);
2207
+ const config = parsed.config ?? {};
2208
+ if (!config.git) config.git = {};
2209
+ if (!config.github) config.github = {};
2210
+ if (!config.paths) config.paths = {};
2211
+ if (!config.agent) config.agent = {};
2212
+ config.git.defaultBranch = config.git.defaultBranch || basic.defaultBranch;
2213
+ config.github.owner = config.github.owner || basic.owner;
2214
+ config.github.repo = config.github.repo || basic.repo;
2215
+ config.paths.taskDir = config.paths.taskDir || ".tasks";
2216
+ config.agent.runner = config.agent.runner || (basic.hasOpenCode ? "opencode" : "claude-code");
2217
+ config.agent.defaultRunner = config.agent.defaultRunner || (basic.hasOpenCode ? "opencode" : "claude");
2218
+ if (!config.agent.modelMap) {
2219
+ config.agent.modelMap = { cheap: "haiku", mid: "sonnet", strong: "opus" };
2220
+ }
2221
+ validateQualityCommands(cwd, config, basic.pm);
2222
+ return {
2223
+ config,
2224
+ architecture: parsed.architecture ?? "",
2225
+ conventions: parsed.conventions ?? ""
2226
+ };
2227
+ } catch (err) {
2228
+ console.log(" \u26A0 Smart detection failed, falling back to basic detection");
2229
+ return {
2230
+ config: buildFallbackConfig(cwd, basic),
2231
+ architecture: "",
2232
+ conventions: ""
2233
+ };
2234
+ }
2235
+ }
2236
+ function validateQualityCommands(cwd, config, pm) {
2237
+ let scripts = {};
2238
+ try {
2239
+ const pkg = JSON.parse(fs7.readFileSync(path6.join(cwd, "package.json"), "utf-8"));
2240
+ scripts = pkg.scripts ?? {};
2241
+ } catch {
2242
+ return;
2243
+ }
2244
+ const quality = config.quality ?? {};
2245
+ const overrides = [
2246
+ { key: "typecheck", preferred: ["typecheck", "type-check"] },
2247
+ { key: "lint", preferred: ["lint"] },
2248
+ { key: "lintFix", preferred: ["lint:fix", "lint-fix"] },
2249
+ { key: "format", preferred: ["format:check", "format-check", "prettier:check"] },
2250
+ { key: "formatFix", preferred: ["format", "format:fix", "format-fix"] },
2251
+ { key: "testUnit", preferred: ["test:unit", "test-unit", "test:ci"] }
2252
+ ];
2253
+ for (const { key, preferred } of overrides) {
2254
+ const match = preferred.find((s) => scripts[s]);
2255
+ if (match) {
2256
+ const correct = `${pm} ${match}`;
2257
+ if (quality[key] !== correct) {
2258
+ quality[key] = correct;
2259
+ }
2260
+ }
2261
+ if (quality[key]) {
2262
+ const scriptName = quality[key].replace(`${pm} `, "");
2263
+ if (scriptName && !scripts[scriptName] && !scriptName.includes(" ")) {
2264
+ quality[key] = "";
2265
+ }
2266
+ }
2267
+ }
2268
+ config.quality = quality;
2269
+ }
2270
+ function buildFallbackConfig(cwd, basic) {
2271
+ const pkg = (() => {
2272
+ try {
2273
+ return JSON.parse(fs7.readFileSync(path6.join(cwd, "package.json"), "utf-8"));
2274
+ } catch {
2275
+ return {};
2276
+ }
2277
+ })();
2278
+ const scripts = pkg.scripts ?? {};
2279
+ const find = (...c) => {
2280
+ for (const s of c) {
2281
+ if (scripts[s]) return `${basic.pm} ${s}`;
2282
+ }
2283
+ return "";
2284
+ };
2285
+ return {
2286
+ quality: {
2287
+ typecheck: find("typecheck", "type-check") || (pkg.devDependencies?.typescript ? `${basic.pm} tsc --noEmit` : ""),
2288
+ lint: find("lint"),
2289
+ lintFix: find("lint:fix", "lint-fix"),
2290
+ format: find("format:check"),
2291
+ formatFix: find("format", "format:fix"),
2292
+ testUnit: find("test:unit", "test", "test:ci")
2293
+ },
2294
+ git: { defaultBranch: basic.defaultBranch },
2295
+ github: { owner: basic.owner, repo: basic.repo },
2296
+ paths: { taskDir: ".tasks" },
2297
+ agent: {
2298
+ runner: basic.hasOpenCode ? "opencode" : "claude-code",
2299
+ defaultRunner: basic.hasOpenCode ? "opencode" : "claude",
2300
+ modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
2301
+ }
2302
+ };
2303
+ }
2007
2304
  function initCommand(opts) {
2008
2305
  const cwd = process.cwd();
2009
2306
  console.log(`
@@ -2027,26 +2324,11 @@ function initCommand(opts) {
2027
2324
  console.log(" \u2713 .github/workflows/kody.yml");
2028
2325
  }
2029
2326
  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)");
2327
+ let smartResult = null;
2328
+ if (!fs7.existsSync(configDest) || opts.force) {
2329
+ smartResult = smartInit(cwd);
2330
+ fs7.writeFileSync(configDest, JSON.stringify(smartResult.config, null, 2) + "\n");
2331
+ console.log(" \u2713 kody.config.json (auto-configured)");
2050
2332
  } else {
2051
2333
  console.log(" \u25CB kody.config.json (exists)");
2052
2334
  }
@@ -2172,33 +2454,38 @@ function initCommand(opts) {
2172
2454
  console.log(" \u2717 kody.config.json \u2014 invalid JSON");
2173
2455
  }
2174
2456
  }
2175
- console.log("\n\u2500\u2500 Architecture Detection \u2500\u2500");
2457
+ console.log("\n\u2500\u2500 Project Memory \u2500\u2500");
2176
2458
  const memoryDir = path6.join(cwd, ".kody", "memory");
2459
+ fs7.mkdirSync(memoryDir, { recursive: true });
2177
2460
  const archPath = path6.join(memoryDir, "architecture.md");
2178
- if (fs7.existsSync(archPath)) {
2179
- console.log(" \u25CB .kody/memory/architecture.md (exists, not overwriting)");
2461
+ const conventionsPath = path6.join(memoryDir, "conventions.md");
2462
+ if (fs7.existsSync(archPath) && !opts.force) {
2463
+ console.log(" \u25CB .kody/memory/architecture.md (exists, use --force to regenerate)");
2464
+ } else if (smartResult?.architecture) {
2465
+ fs7.writeFileSync(archPath, smartResult.architecture);
2466
+ const lineCount = smartResult.architecture.split("\n").length;
2467
+ console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines, LLM-generated)`);
2180
2468
  } else {
2181
2469
  const archItems = detectArchitecture(cwd);
2182
2470
  if (archItems.length > 0) {
2183
- fs7.mkdirSync(memoryDir, { recursive: true });
2184
2471
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2185
- const content = `# Architecture (auto-detected ${timestamp2})
2472
+ fs7.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
2186
2473
 
2187
2474
  ## Overview
2188
2475
  ${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
- }
2476
+ `);
2477
+ console.log(` \u2713 .kody/memory/architecture.md (${archItems.length} items, basic detection)`);
2195
2478
  } else {
2196
2479
  console.log(" \u25CB No architecture detected");
2197
2480
  }
2198
2481
  }
2199
- const conventionsPath = path6.join(memoryDir, "conventions.md");
2200
- if (!fs7.existsSync(conventionsPath)) {
2201
- fs7.mkdirSync(memoryDir, { recursive: true });
2482
+ if (fs7.existsSync(conventionsPath) && !opts.force) {
2483
+ console.log(" \u25CB .kody/memory/conventions.md (exists, use --force to regenerate)");
2484
+ } else if (smartResult?.conventions) {
2485
+ fs7.writeFileSync(conventionsPath, smartResult.conventions);
2486
+ const lineCount = smartResult.conventions.split("\n").length;
2487
+ console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines, LLM-generated)`);
2488
+ } else {
2202
2489
  fs7.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
2203
2490
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
2204
2491
  }
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.9",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -103,7 +103,7 @@ jobs:
103
103
 
104
104
  # Validate mode
105
105
  case "$MODE" in
106
- full|rerun|status|approve) ;;
106
+ full|rerun|fix|status|approve) ;;
107
107
  *)
108
108
  # If first arg isn't a mode, it might be a task-id or nothing
109
109
  if [ -n "$MODE" ] && [ "$MODE" != "" ]; then
@@ -196,7 +196,15 @@ jobs:
196
196
  FEEDBACK: ${{ github.event.inputs.feedback || needs.parse.outputs.feedback }}
197
197
  DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
198
198
  RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
199
- run: kody-engine-lite run --task-id "$TASK_ID" --issue-number "$ISSUE_NUMBER"
199
+ run: |
200
+ CMD="run"
201
+ [ "$MODE" = "rerun" ] && CMD="rerun"
202
+ [ "$MODE" = "fix" ] && CMD="fix"
203
+ ARGS="--task-id $TASK_ID --issue-number $ISSUE_NUMBER"
204
+ [ -n "$FROM_STAGE" ] && ARGS="$ARGS --from $FROM_STAGE"
205
+ [ -n "$FEEDBACK" ] && ARGS="$ARGS --feedback \"$FEEDBACK\""
206
+ [ "$DRY_RUN" = "true" ] && ARGS="$ARGS --dry-run"
207
+ kody-engine-lite $CMD $ARGS
200
208
 
201
209
  - name: Pipeline summary
202
210
  if: always()