@kody-ade/kody-engine-lite 0.1.80 → 0.1.82

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 (71) hide show
  1. package/dist/bin/cli.js +551 -328
  2. package/package.json +1 -1
  3. package/templates/kody.yml +3 -2
  4. package/dist/agent-runner.d.ts +0 -4
  5. package/dist/agent-runner.js +0 -122
  6. package/dist/ci/parse-inputs.d.ts +0 -6
  7. package/dist/ci/parse-inputs.js +0 -76
  8. package/dist/ci/parse-safety.d.ts +0 -6
  9. package/dist/ci/parse-safety.js +0 -22
  10. package/dist/cli/args.d.ts +0 -13
  11. package/dist/cli/args.js +0 -42
  12. package/dist/cli/litellm.d.ts +0 -2
  13. package/dist/cli/litellm.js +0 -85
  14. package/dist/cli/task-resolution.d.ts +0 -2
  15. package/dist/cli/task-resolution.js +0 -41
  16. package/dist/config.d.ts +0 -49
  17. package/dist/config.js +0 -72
  18. package/dist/context.d.ts +0 -4
  19. package/dist/context.js +0 -83
  20. package/dist/definitions.d.ts +0 -3
  21. package/dist/definitions.js +0 -59
  22. package/dist/entry.d.ts +0 -1
  23. package/dist/entry.js +0 -236
  24. package/dist/git-utils.d.ts +0 -13
  25. package/dist/git-utils.js +0 -174
  26. package/dist/github-api.d.ts +0 -14
  27. package/dist/github-api.js +0 -114
  28. package/dist/kody-utils.d.ts +0 -1
  29. package/dist/kody-utils.js +0 -9
  30. package/dist/learning/auto-learn.d.ts +0 -2
  31. package/dist/learning/auto-learn.js +0 -169
  32. package/dist/logger.d.ts +0 -14
  33. package/dist/logger.js +0 -51
  34. package/dist/memory.d.ts +0 -1
  35. package/dist/memory.js +0 -20
  36. package/dist/observer.d.ts +0 -9
  37. package/dist/observer.js +0 -80
  38. package/dist/pipeline/complexity.d.ts +0 -3
  39. package/dist/pipeline/complexity.js +0 -12
  40. package/dist/pipeline/executor-registry.d.ts +0 -3
  41. package/dist/pipeline/executor-registry.js +0 -20
  42. package/dist/pipeline/hooks.d.ts +0 -17
  43. package/dist/pipeline/hooks.js +0 -110
  44. package/dist/pipeline/questions.d.ts +0 -2
  45. package/dist/pipeline/questions.js +0 -44
  46. package/dist/pipeline/runner-selection.d.ts +0 -2
  47. package/dist/pipeline/runner-selection.js +0 -13
  48. package/dist/pipeline/state.d.ts +0 -4
  49. package/dist/pipeline/state.js +0 -37
  50. package/dist/pipeline.d.ts +0 -3
  51. package/dist/pipeline.js +0 -213
  52. package/dist/preflight.d.ts +0 -1
  53. package/dist/preflight.js +0 -69
  54. package/dist/retrospective.d.ts +0 -26
  55. package/dist/retrospective.js +0 -211
  56. package/dist/stages/agent.d.ts +0 -2
  57. package/dist/stages/agent.js +0 -94
  58. package/dist/stages/gate.d.ts +0 -2
  59. package/dist/stages/gate.js +0 -32
  60. package/dist/stages/review.d.ts +0 -2
  61. package/dist/stages/review.js +0 -32
  62. package/dist/stages/ship.d.ts +0 -3
  63. package/dist/stages/ship.js +0 -154
  64. package/dist/stages/verify.d.ts +0 -2
  65. package/dist/stages/verify.js +0 -94
  66. package/dist/types.d.ts +0 -61
  67. package/dist/types.js +0 -1
  68. package/dist/validators.d.ts +0 -8
  69. package/dist/validators.js +0 -42
  70. package/dist/verify-runner.d.ts +0 -11
  71. package/dist/verify-runner.js +0 -110
package/dist/bin/cli.js CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
2
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
4
  var __esm = (fn, res) => function __init() {
4
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
6
  };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
6
11
 
7
12
  // src/agent-runner.ts
8
13
  import { spawn, execFileSync } from "child_process";
@@ -132,7 +137,7 @@ var init_agent_runner = __esm({
132
137
  "src/agent-runner.ts"() {
133
138
  "use strict";
134
139
  SIGKILL_GRACE_MS = 5e3;
135
- STDERR_TAIL_CHARS = 500;
140
+ STDERR_TAIL_CHARS = 2e3;
136
141
  RUNNER_FACTORIES = {
137
142
  "claude-code": createClaudeCodeRunner
138
143
  };
@@ -277,7 +282,11 @@ function getProjectConfig() {
277
282
  quality: { ...DEFAULT_CONFIG.quality, ...raw.quality },
278
283
  git: { ...DEFAULT_CONFIG.git, ...raw.git },
279
284
  github: { ...DEFAULT_CONFIG.github, ...raw.github },
280
- agent: { ...DEFAULT_CONFIG.agent, ...raw.agent },
285
+ agent: {
286
+ ...DEFAULT_CONFIG.agent,
287
+ ...raw.agent,
288
+ modelMap: { ...DEFAULT_CONFIG.agent.modelMap, ...raw.agent?.modelMap }
289
+ },
281
290
  contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers,
282
291
  mcp: raw.mcp ? { enabled: false, servers: {}, stages: ["build", "verify", "review", "review-fix"], ...raw.mcp } : void 0
283
292
  };
@@ -290,7 +299,7 @@ function getProjectConfig() {
290
299
  }
291
300
  return _config;
292
301
  }
293
- var DEFAULT_CONFIG, LITELLM_DEFAULT_PORT, LITELLM_DEFAULT_URL, TIER_TO_ANTHROPIC_IDS, VERIFY_COMMAND_TIMEOUT_MS, FIX_COMMAND_TIMEOUT_MS, _config, _configDir;
302
+ var DEFAULT_CONFIG, LITELLM_DEFAULT_PORT, LITELLM_DEFAULT_URL, VERIFY_COMMAND_TIMEOUT_MS, FIX_COMMAND_TIMEOUT_MS, _config, _configDir;
294
303
  var init_config = __esm({
295
304
  "src/config.ts"() {
296
305
  "use strict";
@@ -320,11 +329,6 @@ var init_config = __esm({
320
329
  };
321
330
  LITELLM_DEFAULT_PORT = 4e3;
322
331
  LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
323
- TIER_TO_ANTHROPIC_IDS = {
324
- cheap: ["claude-haiku-4-5-20251001", "claude-haiku-4-5", "haiku"],
325
- mid: ["claude-sonnet-4-6-20250514", "claude-sonnet-4-6", "sonnet"],
326
- strong: ["claude-opus-4-6-20250514", "claude-opus-4-6", "opus"]
327
- };
328
332
  VERIFY_COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
329
333
  FIX_COMMAND_TIMEOUT_MS = 2 * 60 * 1e3;
330
334
  _config = null;
@@ -444,6 +448,41 @@ function syncWithDefault(cwd) {
444
448
  logger.warn(` Merge conflict with origin/${defaultBranch} \u2014 skipping sync`);
445
449
  }
446
450
  }
451
+ function mergeDefault(cwd) {
452
+ const defaultBranch = getDefaultBranch(cwd);
453
+ const current = getCurrentBranch(cwd);
454
+ if (current === defaultBranch) return "clean";
455
+ try {
456
+ git(["fetch", "origin", defaultBranch], { cwd, timeout: 3e4 });
457
+ } catch {
458
+ logger.warn(" Failed to fetch latest from origin");
459
+ return "error";
460
+ }
461
+ try {
462
+ git(["merge", `origin/${defaultBranch}`, "--no-edit"], { cwd, timeout: 3e4 });
463
+ logger.info(` Merged origin/${defaultBranch} cleanly`);
464
+ return "clean";
465
+ } catch {
466
+ try {
467
+ const unmerged = git(["diff", "--name-only", "--diff-filter=U"], { cwd });
468
+ if (unmerged.trim()) return "conflict";
469
+ } catch {
470
+ }
471
+ try {
472
+ git(["merge", "--abort"], { cwd });
473
+ } catch {
474
+ }
475
+ return "error";
476
+ }
477
+ }
478
+ function getConflictedFiles(cwd) {
479
+ try {
480
+ const output = git(["diff", "--name-only", "--diff-filter=U"], { cwd });
481
+ return output ? output.split("\n") : [];
482
+ } catch {
483
+ return [];
484
+ }
485
+ }
447
486
  function commitAll(message, cwd) {
448
487
  const status = git(["status", "--porcelain"], { cwd });
449
488
  if (!status) {
@@ -1483,9 +1522,6 @@ ${prompt}` : prompt;
1483
1522
  }
1484
1523
  function resolveModel(modelTier, stageName) {
1485
1524
  const config = getProjectConfig();
1486
- if (config.agent.provider && config.agent.provider !== "anthropic") {
1487
- return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
1488
- }
1489
1525
  const mapped = config.agent.modelMap[modelTier];
1490
1526
  if (mapped) return mapped;
1491
1527
  return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
@@ -1836,8 +1872,8 @@ async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, mo
1836
1872
  `Stage: ${stageName}`,
1837
1873
  ``,
1838
1874
  `Error output:`,
1839
- errorOutput.slice(-2e3),
1840
- // Last 2000 chars of error
1875
+ errorOutput.slice(-5e3),
1876
+ // Last 5000 chars of error for accurate diagnosis
1841
1877
  ``,
1842
1878
  modifiedFiles.length > 0 ? `Files modified by build stage:
1843
1879
  ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (build may not have run yet)."
@@ -1884,13 +1920,21 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
1884
1920
  }
1885
1921
  function getModifiedFiles(projectDir) {
1886
1922
  try {
1887
- const output = execFileSync5("git", ["diff", "--name-only", "HEAD~1"], {
1923
+ const staged = execFileSync5("git", ["diff", "--name-only", "--cached"], {
1888
1924
  encoding: "utf-8",
1889
1925
  cwd: projectDir,
1890
1926
  timeout: 5e3,
1891
1927
  stdio: ["pipe", "pipe", "pipe"]
1892
1928
  }).trim();
1893
- return output ? output.split("\n").filter(Boolean) : [];
1929
+ const unstaged = execFileSync5("git", ["diff", "--name-only"], {
1930
+ encoding: "utf-8",
1931
+ cwd: projectDir,
1932
+ timeout: 5e3,
1933
+ stdio: ["pipe", "pipe", "pipe"]
1934
+ }).trim();
1935
+ const all = `${staged}
1936
+ ${unstaged}`.split("\n").filter(Boolean);
1937
+ return [...new Set(all)];
1894
1938
  } catch {
1895
1939
  return [];
1896
1940
  }
@@ -2090,55 +2134,198 @@ var init_verify = __esm({
2090
2134
  }
2091
2135
  });
2092
2136
 
2093
- // src/stages/review.ts
2137
+ // src/cli/task-resolution.ts
2094
2138
  import * as fs9 from "fs";
2095
2139
  import * as path9 from "path";
2140
+ import { execFileSync as execFileSync7 } from "child_process";
2141
+ function findLatestTaskForIssue(issueNumber, projectDir) {
2142
+ const tasksDir = path9.join(projectDir, ".kody", "tasks");
2143
+ if (!fs9.existsSync(tasksDir)) return null;
2144
+ const allDirs = fs9.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
2145
+ const prefix = `${issueNumber}-`;
2146
+ const direct = allDirs.find((d) => d.startsWith(prefix));
2147
+ if (direct) return direct;
2148
+ try {
2149
+ const branch = execFileSync7("git", ["branch", "--show-current"], {
2150
+ encoding: "utf-8",
2151
+ cwd: projectDir,
2152
+ timeout: 5e3,
2153
+ stdio: ["pipe", "pipe", "pipe"]
2154
+ }).trim();
2155
+ const branchIssueMatch = branch.match(/^(\d+)-/);
2156
+ if (branchIssueMatch) {
2157
+ const branchIssueNum = branchIssueMatch[1];
2158
+ const branchPrefix = `${branchIssueNum}-`;
2159
+ const fromBranch = allDirs.find((d) => d.startsWith(branchPrefix));
2160
+ if (fromBranch) return fromBranch;
2161
+ }
2162
+ } catch {
2163
+ }
2164
+ return null;
2165
+ }
2166
+ function generateTaskId() {
2167
+ const now = /* @__PURE__ */ new Date();
2168
+ const pad = (n) => String(n).padStart(2, "0");
2169
+ return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
2170
+ }
2171
+ var init_task_resolution = __esm({
2172
+ "src/cli/task-resolution.ts"() {
2173
+ "use strict";
2174
+ }
2175
+ });
2176
+
2177
+ // src/review-standalone.ts
2178
+ import * as fs10 from "fs";
2179
+ import * as path10 from "path";
2180
+ function resolveReviewTarget(input) {
2181
+ if (input.prs.length === 0) {
2182
+ return {
2183
+ action: "none",
2184
+ message: `Issue #${input.issueNumber} has no open PRs. Nothing to review.`
2185
+ };
2186
+ }
2187
+ if (input.prs.length === 1) {
2188
+ return { action: "review", prNumber: input.prs[0].number };
2189
+ }
2190
+ const prList = input.prs.map((pr) => ` - #${pr.number}: ${pr.title}`).join("\n");
2191
+ return {
2192
+ action: "pick",
2193
+ prs: input.prs,
2194
+ message: `\u26A0\uFE0F Issue #${input.issueNumber} has ${input.prs.length} open PRs:
2195
+ ${prList}
2196
+
2197
+ Run: \`pnpm kody review --pr-number <n>\`
2198
+ Or comment on the specific PR: \`@kody review\``
2199
+ };
2200
+ }
2201
+ async function runStandaloneReview(input) {
2202
+ const taskId = input.taskId ?? `review-${generateTaskId()}`;
2203
+ const taskDir = path10.join(input.projectDir, ".kody", "tasks", taskId);
2204
+ fs10.mkdirSync(taskDir, { recursive: true });
2205
+ const taskContent = `# ${input.prTitle}
2206
+
2207
+ ${input.prBody ?? ""}`;
2208
+ fs10.writeFileSync(path10.join(taskDir, "task.md"), taskContent);
2209
+ const reviewDef = STAGES.find((s) => s.name === "review");
2210
+ const ctx = {
2211
+ taskId,
2212
+ taskDir,
2213
+ projectDir: input.projectDir,
2214
+ runners: input.runners,
2215
+ sessions: {},
2216
+ input: {
2217
+ mode: "full",
2218
+ local: input.local
2219
+ }
2220
+ };
2221
+ logger.info(`[review] standalone review for: ${input.prTitle}`);
2222
+ const result = await executeAgentStage(ctx, reviewDef);
2223
+ if (result.outcome !== "completed") {
2224
+ return {
2225
+ outcome: "failed",
2226
+ taskDir,
2227
+ error: result.error ?? "Review stage failed"
2228
+ };
2229
+ }
2230
+ const reviewPath = path10.join(taskDir, "review.md");
2231
+ let reviewContent;
2232
+ if (fs10.existsSync(reviewPath)) {
2233
+ reviewContent = fs10.readFileSync(reviewPath, "utf-8");
2234
+ }
2235
+ return {
2236
+ outcome: "completed",
2237
+ reviewContent,
2238
+ taskDir
2239
+ };
2240
+ }
2241
+ function detectReviewVerdict(reviewContent) {
2242
+ const verdictMatch = reviewContent.match(/##\s*Verdict:\s*(PASS|FAIL)/i);
2243
+ if (verdictMatch) {
2244
+ return verdictMatch[1].toLowerCase();
2245
+ }
2246
+ const hasCritical = /###\s*Critical\s*\n(?!None\.)/i.test(reviewContent);
2247
+ const hasMajor = /###\s*Major\s*\n(?!None\.)/i.test(reviewContent);
2248
+ if (hasCritical || hasMajor) return "fail";
2249
+ return "pass";
2250
+ }
2251
+ function formatReviewComment(reviewContent, taskId) {
2252
+ const verdict = detectReviewVerdict(reviewContent);
2253
+ const cta = verdict === "fail" ? "\n\n> To fix these issues, comment: `@kody fix`\n> The review findings will be used automatically as context." : "";
2254
+ return `## \u{1F50D} Kody Review (\`${taskId}\`)
2255
+
2256
+ ${reviewContent}${cta}
2257
+
2258
+ ---
2259
+ \u{1F916} Generated by Kody`;
2260
+ }
2261
+ var init_review_standalone = __esm({
2262
+ "src/review-standalone.ts"() {
2263
+ "use strict";
2264
+ init_definitions();
2265
+ init_agent();
2266
+ init_task_resolution();
2267
+ init_logger();
2268
+ }
2269
+ });
2270
+
2271
+ // src/stages/review.ts
2272
+ import * as fs11 from "fs";
2273
+ import * as path11 from "path";
2096
2274
  async function executeReviewWithFix(ctx, def) {
2097
2275
  if (ctx.input.dryRun) {
2098
2276
  return { outcome: "completed", retries: 0 };
2099
2277
  }
2100
2278
  const reviewDef = STAGES.find((s) => s.name === "review");
2101
2279
  const reviewFixDef = STAGES.find((s) => s.name === "review-fix");
2102
- const reviewResult = await executeAgentStage(ctx, reviewDef);
2103
- if (reviewResult.outcome !== "completed") {
2104
- return reviewResult;
2105
- }
2106
- const reviewFile = path9.join(ctx.taskDir, "review.md");
2107
- if (!fs9.existsSync(reviewFile)) {
2108
- return { outcome: "failed", retries: 0, error: "review.md not found" };
2109
- }
2110
- const content = fs9.readFileSync(reviewFile, "utf-8");
2111
- const hasIssues = /\bfail\b/i.test(content) && !/pass/i.test(content);
2112
- if (!hasIssues) {
2113
- return reviewResult;
2114
- }
2115
- logger.info(` review found issues, running review-fix...`);
2116
- const fixResult = await executeAgentStage(ctx, reviewFixDef);
2117
- if (fixResult.outcome !== "completed") {
2118
- return fixResult;
2280
+ for (let iteration = 0; iteration <= MAX_REVIEW_FIX_ITERATIONS; iteration++) {
2281
+ const label = iteration === 0 ? "initial review" : `review after fix #${iteration}`;
2282
+ logger.info(` running ${label}...`);
2283
+ const reviewResult = await executeAgentStage(ctx, reviewDef);
2284
+ if (reviewResult.outcome !== "completed") {
2285
+ return reviewResult;
2286
+ }
2287
+ const reviewFile = path11.join(ctx.taskDir, "review.md");
2288
+ if (!fs11.existsSync(reviewFile)) {
2289
+ return { outcome: "failed", retries: iteration, error: "review.md not found" };
2290
+ }
2291
+ const content = fs11.readFileSync(reviewFile, "utf-8");
2292
+ if (detectReviewVerdict(content) !== "fail") {
2293
+ return { ...reviewResult, retries: iteration };
2294
+ }
2295
+ if (iteration === MAX_REVIEW_FIX_ITERATIONS) {
2296
+ logger.warn(` review still failing after ${MAX_REVIEW_FIX_ITERATIONS} fix attempts`);
2297
+ return { ...reviewResult, retries: iteration };
2298
+ }
2299
+ logger.info(` review found issues (iteration ${iteration + 1}/${MAX_REVIEW_FIX_ITERATIONS}), running review-fix...`);
2300
+ const fixResult = await executeAgentStage(ctx, reviewFixDef);
2301
+ if (fixResult.outcome !== "completed") {
2302
+ return fixResult;
2303
+ }
2119
2304
  }
2120
- logger.info(` re-running review after fix...`);
2121
- return executeAgentStage(ctx, reviewDef);
2305
+ return { outcome: "failed", retries: MAX_REVIEW_FIX_ITERATIONS, error: "Unexpected review-fix loop exit" };
2122
2306
  }
2307
+ var MAX_REVIEW_FIX_ITERATIONS;
2123
2308
  var init_review = __esm({
2124
2309
  "src/stages/review.ts"() {
2125
2310
  "use strict";
2126
2311
  init_definitions();
2127
2312
  init_logger();
2128
2313
  init_agent();
2314
+ init_review_standalone();
2315
+ MAX_REVIEW_FIX_ITERATIONS = 2;
2129
2316
  }
2130
2317
  });
2131
2318
 
2132
2319
  // src/stages/ship.ts
2133
- import * as fs10 from "fs";
2134
- import * as path10 from "path";
2135
- import { execFileSync as execFileSync7 } from "child_process";
2320
+ import * as fs12 from "fs";
2321
+ import * as path12 from "path";
2322
+ import { execFileSync as execFileSync8 } from "child_process";
2136
2323
  function buildPrBody(ctx) {
2137
2324
  const sections = [];
2138
- const taskJsonPath = path10.join(ctx.taskDir, "task.json");
2139
- if (fs10.existsSync(taskJsonPath)) {
2325
+ const taskJsonPath = path12.join(ctx.taskDir, "task.json");
2326
+ if (fs12.existsSync(taskJsonPath)) {
2140
2327
  try {
2141
- const raw = fs10.readFileSync(taskJsonPath, "utf-8");
2328
+ const raw = fs12.readFileSync(taskJsonPath, "utf-8");
2142
2329
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2143
2330
  const task = JSON.parse(cleaned);
2144
2331
  if (task.description) {
@@ -2157,9 +2344,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
2157
2344
  } catch {
2158
2345
  }
2159
2346
  }
2160
- const reviewPath = path10.join(ctx.taskDir, "review.md");
2161
- if (fs10.existsSync(reviewPath)) {
2162
- const review = fs10.readFileSync(reviewPath, "utf-8");
2347
+ const reviewPath = path12.join(ctx.taskDir, "review.md");
2348
+ if (fs12.existsSync(reviewPath)) {
2349
+ const review = fs12.readFileSync(reviewPath, "utf-8");
2163
2350
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
2164
2351
  if (summaryMatch) {
2165
2352
  const summary = summaryMatch[1].trim();
@@ -2176,14 +2363,14 @@ ${summary}`);
2176
2363
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
2177
2364
  }
2178
2365
  }
2179
- const verifyPath = path10.join(ctx.taskDir, "verify.md");
2180
- if (fs10.existsSync(verifyPath)) {
2181
- const verify = fs10.readFileSync(verifyPath, "utf-8");
2366
+ const verifyPath = path12.join(ctx.taskDir, "verify.md");
2367
+ if (fs12.existsSync(verifyPath)) {
2368
+ const verify = fs12.readFileSync(verifyPath, "utf-8");
2182
2369
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
2183
2370
  }
2184
- const planPath = path10.join(ctx.taskDir, "plan.md");
2185
- if (fs10.existsSync(planPath)) {
2186
- const plan = fs10.readFileSync(planPath, "utf-8").trim();
2371
+ const planPath = path12.join(ctx.taskDir, "plan.md");
2372
+ if (fs12.existsSync(planPath)) {
2373
+ const plan = fs12.readFileSync(planPath, "utf-8").trim();
2187
2374
  if (plan) {
2188
2375
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
2189
2376
  sections.push(`
@@ -2203,25 +2390,25 @@ Closes #${ctx.input.issueNumber}`);
2203
2390
  return sections.join("\n");
2204
2391
  }
2205
2392
  function executeShipStage(ctx, _def) {
2206
- const shipPath = path10.join(ctx.taskDir, "ship.md");
2393
+ const shipPath = path12.join(ctx.taskDir, "ship.md");
2207
2394
  if (ctx.input.dryRun) {
2208
- fs10.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
2395
+ fs12.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
2209
2396
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
2210
2397
  }
2211
2398
  if (ctx.input.local && !ctx.input.issueNumber) {
2212
- fs10.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
2399
+ fs12.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
2213
2400
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
2214
2401
  }
2215
2402
  try {
2216
2403
  const head = getCurrentBranch(ctx.projectDir);
2217
2404
  const base = getDefaultBranch(ctx.projectDir);
2218
2405
  try {
2219
- execFileSync7("git", ["add", ctx.taskDir], {
2406
+ execFileSync8("git", ["add", ctx.taskDir], {
2220
2407
  cwd: ctx.projectDir,
2221
2408
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
2222
2409
  stdio: "pipe"
2223
2410
  });
2224
- execFileSync7("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
2411
+ execFileSync8("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
2225
2412
  cwd: ctx.projectDir,
2226
2413
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
2227
2414
  stdio: "pipe"
@@ -2235,7 +2422,7 @@ function executeShipStage(ctx, _def) {
2235
2422
  let repo = config.github?.repo;
2236
2423
  if (!owner || !repo) {
2237
2424
  try {
2238
- const remoteUrl = execFileSync7("git", ["remote", "get-url", "origin"], {
2425
+ const remoteUrl = execFileSync8("git", ["remote", "get-url", "origin"], {
2239
2426
  encoding: "utf-8",
2240
2427
  cwd: ctx.projectDir
2241
2428
  }).trim();
@@ -2255,10 +2442,10 @@ function executeShipStage(ctx, _def) {
2255
2442
  docs: "docs",
2256
2443
  chore: "chore"
2257
2444
  };
2258
- const taskJsonPath = path10.join(ctx.taskDir, "task.json");
2259
- if (fs10.existsSync(taskJsonPath)) {
2445
+ const taskJsonPath = path12.join(ctx.taskDir, "task.json");
2446
+ if (fs12.existsSync(taskJsonPath)) {
2260
2447
  try {
2261
- const raw = fs10.readFileSync(taskJsonPath, "utf-8");
2448
+ const raw = fs12.readFileSync(taskJsonPath, "utf-8");
2262
2449
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2263
2450
  const task = JSON.parse(cleaned);
2264
2451
  const prefix = TYPE_PREFIX[task.task_type] ?? "chore";
@@ -2268,9 +2455,9 @@ function executeShipStage(ctx, _def) {
2268
2455
  }
2269
2456
  }
2270
2457
  if (title === "Update") {
2271
- const taskMdPath = path10.join(ctx.taskDir, "task.md");
2272
- if (fs10.existsSync(taskMdPath)) {
2273
- const content = fs10.readFileSync(taskMdPath, "utf-8");
2458
+ const taskMdPath = path12.join(ctx.taskDir, "task.md");
2459
+ if (fs12.existsSync(taskMdPath)) {
2460
+ const content = fs12.readFileSync(taskMdPath, "utf-8");
2274
2461
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("*"));
2275
2462
  if (firstLine) title = `chore: ${firstLine.trim()}`.slice(0, 72);
2276
2463
  }
@@ -2290,7 +2477,7 @@ function executeShipStage(ctx, _def) {
2290
2477
  } catch {
2291
2478
  }
2292
2479
  }
2293
- fs10.writeFileSync(shipPath, `# Ship
2480
+ fs12.writeFileSync(shipPath, `# Ship
2294
2481
 
2295
2482
  Updated existing PR: ${existingPr.url}
2296
2483
  PR #${existingPr.number}
@@ -2311,19 +2498,19 @@ PR #${existingPr.number}
2311
2498
  } catch {
2312
2499
  }
2313
2500
  }
2314
- fs10.writeFileSync(shipPath, `# Ship
2501
+ fs12.writeFileSync(shipPath, `# Ship
2315
2502
 
2316
2503
  PR created: ${pr.url}
2317
2504
  PR #${pr.number}
2318
2505
  `);
2319
2506
  } else {
2320
- fs10.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
2507
+ fs12.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
2321
2508
  }
2322
2509
  }
2323
2510
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
2324
2511
  } catch (err) {
2325
2512
  const msg = err instanceof Error ? err.message : String(err);
2326
- fs10.writeFileSync(shipPath, `# Ship
2513
+ fs12.writeFileSync(shipPath, `# Ship
2327
2514
 
2328
2515
  Failed: ${msg}
2329
2516
  `);
@@ -2369,15 +2556,15 @@ var init_executor_registry = __esm({
2369
2556
  });
2370
2557
 
2371
2558
  // src/pipeline/questions.ts
2372
- import * as fs11 from "fs";
2373
- import * as path11 from "path";
2559
+ import * as fs13 from "fs";
2560
+ import * as path13 from "path";
2374
2561
  function checkForQuestions(ctx, stageName) {
2375
2562
  if (ctx.input.local || !ctx.input.issueNumber) return false;
2376
2563
  try {
2377
2564
  if (stageName === "taskify") {
2378
- const taskJsonPath = path11.join(ctx.taskDir, "task.json");
2379
- if (!fs11.existsSync(taskJsonPath)) return false;
2380
- const raw = fs11.readFileSync(taskJsonPath, "utf-8");
2565
+ const taskJsonPath = path13.join(ctx.taskDir, "task.json");
2566
+ if (!fs13.existsSync(taskJsonPath)) return false;
2567
+ const raw = fs13.readFileSync(taskJsonPath, "utf-8");
2381
2568
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2382
2569
  const taskJson = JSON.parse(cleaned);
2383
2570
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -2392,9 +2579,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
2392
2579
  }
2393
2580
  }
2394
2581
  if (stageName === "plan") {
2395
- const planPath = path11.join(ctx.taskDir, "plan.md");
2396
- if (!fs11.existsSync(planPath)) return false;
2397
- const plan = fs11.readFileSync(planPath, "utf-8");
2582
+ const planPath = path13.join(ctx.taskDir, "plan.md");
2583
+ if (!fs13.existsSync(planPath)) return false;
2584
+ const plan = fs13.readFileSync(planPath, "utf-8");
2398
2585
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
2399
2586
  if (questionsMatch) {
2400
2587
  const questionsText = questionsMatch[1].trim();
@@ -2423,8 +2610,8 @@ var init_questions = __esm({
2423
2610
  });
2424
2611
 
2425
2612
  // src/pipeline/hooks.ts
2426
- import * as fs12 from "fs";
2427
- import * as path12 from "path";
2613
+ import * as fs14 from "fs";
2614
+ import * as path14 from "path";
2428
2615
  function applyPreStageLabel(ctx, def) {
2429
2616
  if (!ctx.input.issueNumber || ctx.input.local) return;
2430
2617
  if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
@@ -2450,9 +2637,9 @@ function autoDetectComplexity(ctx, def) {
2450
2637
  if (def.name !== "taskify") return null;
2451
2638
  if (ctx.input.complexity) return null;
2452
2639
  try {
2453
- const taskJsonPath = path12.join(ctx.taskDir, "task.json");
2454
- if (!fs12.existsSync(taskJsonPath)) return null;
2455
- const raw = fs12.readFileSync(taskJsonPath, "utf-8");
2640
+ const taskJsonPath = path14.join(ctx.taskDir, "task.json");
2641
+ if (!fs14.existsSync(taskJsonPath)) return null;
2642
+ const raw = fs14.readFileSync(taskJsonPath, "utf-8");
2456
2643
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2457
2644
  const taskJson = JSON.parse(cleaned);
2458
2645
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -2482,8 +2669,8 @@ function checkRiskGate(ctx, def, state, complexity) {
2482
2669
  if (ctx.input.dryRun || ctx.input.local) return null;
2483
2670
  if (ctx.input.mode === "rerun") return null;
2484
2671
  if (!ctx.input.issueNumber) return null;
2485
- const planPath = path12.join(ctx.taskDir, "plan.md");
2486
- const plan = fs12.existsSync(planPath) ? fs12.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
2672
+ const planPath = path14.join(ctx.taskDir, "plan.md");
2673
+ const plan = fs14.existsSync(planPath) ? fs14.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
2487
2674
  try {
2488
2675
  postComment(
2489
2676
  ctx.input.issueNumber,
@@ -2550,22 +2737,22 @@ var init_hooks = __esm({
2550
2737
  });
2551
2738
 
2552
2739
  // src/learning/auto-learn.ts
2553
- import * as fs13 from "fs";
2554
- import * as path13 from "path";
2740
+ import * as fs15 from "fs";
2741
+ import * as path15 from "path";
2555
2742
  function stripAnsi(str) {
2556
2743
  return str.replace(/\x1b\[[0-9;]*m/g, "");
2557
2744
  }
2558
2745
  function autoLearn(ctx) {
2559
2746
  try {
2560
- const memoryDir = path13.join(ctx.projectDir, ".kody", "memory");
2561
- if (!fs13.existsSync(memoryDir)) {
2562
- fs13.mkdirSync(memoryDir, { recursive: true });
2747
+ const memoryDir = path15.join(ctx.projectDir, ".kody", "memory");
2748
+ if (!fs15.existsSync(memoryDir)) {
2749
+ fs15.mkdirSync(memoryDir, { recursive: true });
2563
2750
  }
2564
2751
  const learnings = [];
2565
2752
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2566
- const verifyPath = path13.join(ctx.taskDir, "verify.md");
2567
- if (fs13.existsSync(verifyPath)) {
2568
- const verify = stripAnsi(fs13.readFileSync(verifyPath, "utf-8"));
2753
+ const verifyPath = path15.join(ctx.taskDir, "verify.md");
2754
+ if (fs15.existsSync(verifyPath)) {
2755
+ const verify = stripAnsi(fs15.readFileSync(verifyPath, "utf-8"));
2569
2756
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
2570
2757
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
2571
2758
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -2574,18 +2761,18 @@ function autoLearn(ctx) {
2574
2761
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
2575
2762
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
2576
2763
  }
2577
- const reviewPath = path13.join(ctx.taskDir, "review.md");
2578
- if (fs13.existsSync(reviewPath)) {
2579
- const review = fs13.readFileSync(reviewPath, "utf-8");
2764
+ const reviewPath = path15.join(ctx.taskDir, "review.md");
2765
+ if (fs15.existsSync(reviewPath)) {
2766
+ const review = fs15.readFileSync(reviewPath, "utf-8");
2580
2767
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
2581
2768
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
2582
2769
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
2583
2770
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
2584
2771
  }
2585
- const taskJsonPath = path13.join(ctx.taskDir, "task.json");
2586
- if (fs13.existsSync(taskJsonPath)) {
2772
+ const taskJsonPath = path15.join(ctx.taskDir, "task.json");
2773
+ if (fs15.existsSync(taskJsonPath)) {
2587
2774
  try {
2588
- const raw = stripAnsi(fs13.readFileSync(taskJsonPath, "utf-8"));
2775
+ const raw = stripAnsi(fs15.readFileSync(taskJsonPath, "utf-8"));
2589
2776
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2590
2777
  const task = JSON.parse(cleaned);
2591
2778
  if (task.scope && Array.isArray(task.scope)) {
@@ -2596,13 +2783,13 @@ function autoLearn(ctx) {
2596
2783
  }
2597
2784
  }
2598
2785
  if (learnings.length > 0) {
2599
- const conventionsPath = path13.join(memoryDir, "conventions.md");
2786
+ const conventionsPath = path15.join(memoryDir, "conventions.md");
2600
2787
  const entry = `
2601
2788
  ## Learned ${timestamp2} (task: ${ctx.taskId})
2602
2789
  ${learnings.join("\n")}
2603
2790
  `;
2604
- fs13.appendFileSync(conventionsPath, entry);
2605
- invalidateCache(conventionsPath, path13.join(memoryDir, ".tiers"));
2791
+ fs15.appendFileSync(conventionsPath, entry);
2792
+ invalidateCache(conventionsPath, path15.join(memoryDir, ".tiers"));
2606
2793
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
2607
2794
  }
2608
2795
  autoLearnDecisions(ctx.taskDir, memoryDir, ctx.taskId, timestamp2);
@@ -2611,13 +2798,13 @@ ${learnings.join("\n")}
2611
2798
  }
2612
2799
  }
2613
2800
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2614
- const archPath = path13.join(memoryDir, "architecture.md");
2615
- if (fs13.existsSync(archPath)) return;
2801
+ const archPath = path15.join(memoryDir, "architecture.md");
2802
+ if (fs15.existsSync(archPath)) return;
2616
2803
  const detected = [];
2617
- const pkgPath = path13.join(projectDir, "package.json");
2618
- if (fs13.existsSync(pkgPath)) {
2804
+ const pkgPath = path15.join(projectDir, "package.json");
2805
+ if (fs15.existsSync(pkgPath)) {
2619
2806
  try {
2620
- const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
2807
+ const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
2621
2808
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
2622
2809
  if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
2623
2810
  else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
@@ -2633,15 +2820,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2633
2820
  if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push(`- CMS: Payload CMS`);
2634
2821
  if (pkg.type === "module") detected.push("- Module system: ESM");
2635
2822
  else detected.push("- Module system: CommonJS");
2636
- if (fs13.existsSync(path13.join(projectDir, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
2637
- else if (fs13.existsSync(path13.join(projectDir, "yarn.lock"))) detected.push("- Package manager: yarn");
2638
- else if (fs13.existsSync(path13.join(projectDir, "package-lock.json"))) detected.push("- Package manager: npm");
2823
+ if (fs15.existsSync(path15.join(projectDir, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
2824
+ else if (fs15.existsSync(path15.join(projectDir, "yarn.lock"))) detected.push("- Package manager: yarn");
2825
+ else if (fs15.existsSync(path15.join(projectDir, "package-lock.json"))) detected.push("- Package manager: npm");
2639
2826
  } catch {
2640
2827
  }
2641
2828
  }
2642
2829
  const topDirs = [];
2643
2830
  try {
2644
- const entries = fs13.readdirSync(projectDir, { withFileTypes: true });
2831
+ const entries = fs15.readdirSync(projectDir, { withFileTypes: true });
2645
2832
  for (const entry of entries) {
2646
2833
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
2647
2834
  topDirs.push(entry.name);
@@ -2650,10 +2837,10 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2650
2837
  if (topDirs.length > 0) detected.push(`- Top-level directories: ${topDirs.join(", ")}`);
2651
2838
  } catch {
2652
2839
  }
2653
- const srcDir = path13.join(projectDir, "src");
2654
- if (fs13.existsSync(srcDir)) {
2840
+ const srcDir = path15.join(projectDir, "src");
2841
+ if (fs15.existsSync(srcDir)) {
2655
2842
  try {
2656
- const srcEntries = fs13.readdirSync(srcDir, { withFileTypes: true });
2843
+ const srcEntries = fs15.readdirSync(srcDir, { withFileTypes: true });
2657
2844
  const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
2658
2845
  if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
2659
2846
  } catch {
@@ -2665,15 +2852,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2665
2852
  ## Overview
2666
2853
  ${detected.join("\n")}
2667
2854
  `;
2668
- fs13.writeFileSync(archPath, content);
2669
- invalidateCache(archPath, path13.join(memoryDir, ".tiers"));
2855
+ fs15.writeFileSync(archPath, content);
2856
+ invalidateCache(archPath, path15.join(memoryDir, ".tiers"));
2670
2857
  logger.info(`Auto-detected architecture (${detected.length} items)`);
2671
2858
  }
2672
2859
  }
2673
2860
  function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
2674
- const reviewPath = path13.join(taskDir, "review.md");
2675
- if (!fs13.existsSync(reviewPath)) return;
2676
- const review = fs13.readFileSync(reviewPath, "utf-8");
2861
+ const reviewPath = path15.join(taskDir, "review.md");
2862
+ if (!fs15.existsSync(reviewPath)) return;
2863
+ const review = fs15.readFileSync(reviewPath, "utf-8");
2677
2864
  const decisions = [];
2678
2865
  const existingPatternRe = /(?:use|follow|reuse|match|adopt)\s+(?:the\s+)?existing\s+(.+?)(?:\.|$)/gim;
2679
2866
  for (const match of review.matchAll(existingPatternRe)) {
@@ -2692,10 +2879,10 @@ function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
2692
2879
  decisions.push(`- Avoid ${match[1].trim()} for ${match[2].trim()}`);
2693
2880
  }
2694
2881
  if (decisions.length === 0) return;
2695
- const decisionsPath = path13.join(memoryDir, "decisions.md");
2882
+ const decisionsPath = path15.join(memoryDir, "decisions.md");
2696
2883
  let existing = "";
2697
- if (fs13.existsSync(decisionsPath)) {
2698
- existing = fs13.readFileSync(decisionsPath, "utf-8");
2884
+ if (fs15.existsSync(decisionsPath)) {
2885
+ existing = fs15.readFileSync(decisionsPath, "utf-8");
2699
2886
  } else {
2700
2887
  existing = "# Architectural Decisions\n\nDecisions extracted from code reviews. The planning agent MUST follow these.\n";
2701
2888
  }
@@ -2705,8 +2892,8 @@ function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
2705
2892
  ## From task ${taskId} (${timestamp2})
2706
2893
  ${newDecisions.join("\n")}
2707
2894
  `;
2708
- fs13.appendFileSync(decisionsPath, existing ? entry : existing + entry);
2709
- invalidateCache(decisionsPath, path13.join(memoryDir, ".tiers"));
2895
+ fs15.appendFileSync(decisionsPath, existing ? entry : existing + entry);
2896
+ invalidateCache(decisionsPath, path15.join(memoryDir, ".tiers"));
2710
2897
  logger.info(`Auto-learned ${newDecisions.length} architectural decision(s)`);
2711
2898
  }
2712
2899
  var init_auto_learn = __esm({
@@ -2718,13 +2905,13 @@ var init_auto_learn = __esm({
2718
2905
  });
2719
2906
 
2720
2907
  // src/retrospective.ts
2721
- import * as fs14 from "fs";
2722
- import * as path14 from "path";
2908
+ import * as fs16 from "fs";
2909
+ import * as path16 from "path";
2723
2910
  function readArtifact(taskDir, filename, maxChars) {
2724
- const p = path14.join(taskDir, filename);
2725
- if (!fs14.existsSync(p)) return null;
2911
+ const p = path16.join(taskDir, filename);
2912
+ if (!fs16.existsSync(p)) return null;
2726
2913
  try {
2727
- const content = fs14.readFileSync(p, "utf-8");
2914
+ const content = fs16.readFileSync(p, "utf-8");
2728
2915
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
2729
2916
  } catch {
2730
2917
  return null;
@@ -2777,13 +2964,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
2777
2964
  return lines.join("\n");
2778
2965
  }
2779
2966
  function getLogPath(projectDir) {
2780
- return path14.join(projectDir, ".kody", "memory", "observer-log.jsonl");
2967
+ return path16.join(projectDir, ".kody", "memory", "observer-log.jsonl");
2781
2968
  }
2782
2969
  function readPreviousRetrospectives(projectDir, limit = 10) {
2783
2970
  const logPath = getLogPath(projectDir);
2784
- if (!fs14.existsSync(logPath)) return [];
2971
+ if (!fs16.existsSync(logPath)) return [];
2785
2972
  try {
2786
- const content = fs14.readFileSync(logPath, "utf-8");
2973
+ const content = fs16.readFileSync(logPath, "utf-8");
2787
2974
  const lines = content.split("\n").filter(Boolean);
2788
2975
  const entries = [];
2789
2976
  const start = Math.max(0, lines.length - limit);
@@ -2810,11 +2997,11 @@ function formatPreviousEntries(entries) {
2810
2997
  }
2811
2998
  function appendRetrospectiveEntry(projectDir, entry) {
2812
2999
  const logPath = getLogPath(projectDir);
2813
- const dir = path14.dirname(logPath);
2814
- if (!fs14.existsSync(dir)) {
2815
- fs14.mkdirSync(dir, { recursive: true });
3000
+ const dir = path16.dirname(logPath);
3001
+ if (!fs16.existsSync(dir)) {
3002
+ fs16.mkdirSync(dir, { recursive: true });
2816
3003
  }
2817
- fs14.appendFileSync(logPath, JSON.stringify(entry) + "\n");
3004
+ fs16.appendFileSync(logPath, JSON.stringify(entry) + "\n");
2818
3005
  }
2819
3006
  async function runRetrospective(ctx, state, pipelineStartTime) {
2820
3007
  if (ctx.input.dryRun) return;
@@ -2925,14 +3112,22 @@ If no pipeline flaw is detected, set "pipelineFlaw" to null.
2925
3112
  });
2926
3113
 
2927
3114
  // src/pipeline.ts
2928
- import * as fs15 from "fs";
2929
- import * as path15 from "path";
3115
+ import * as fs17 from "fs";
3116
+ import * as path17 from "path";
2930
3117
  function ensureFeatureBranchIfNeeded(ctx) {
2931
- if (ctx.input.prNumber) return;
2932
- if (!ctx.input.issueNumber || ctx.input.dryRun) return;
3118
+ if (ctx.input.dryRun) return;
3119
+ if (ctx.input.prNumber) {
3120
+ try {
3121
+ syncWithDefault(ctx.projectDir);
3122
+ } catch (err) {
3123
+ logger.warn(` Failed to sync with default branch: ${err}`);
3124
+ }
3125
+ return;
3126
+ }
3127
+ if (!ctx.input.issueNumber) return;
2933
3128
  try {
2934
- const taskMdPath = path15.join(ctx.taskDir, "task.md");
2935
- const title = fs15.existsSync(taskMdPath) ? fs15.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
3129
+ const taskMdPath = path17.join(ctx.taskDir, "task.md");
3130
+ const title = fs17.existsSync(taskMdPath) ? fs17.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
2936
3131
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
2937
3132
  syncWithDefault(ctx.projectDir);
2938
3133
  } catch (err) {
@@ -2940,10 +3135,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
2940
3135
  }
2941
3136
  }
2942
3137
  function acquireLock(taskDir) {
2943
- const lockPath = path15.join(taskDir, ".lock");
2944
- if (fs15.existsSync(lockPath)) {
3138
+ const lockPath = path17.join(taskDir, ".lock");
3139
+ if (fs17.existsSync(lockPath)) {
2945
3140
  try {
2946
- const pid = parseInt(fs15.readFileSync(lockPath, "utf-8").trim(), 10);
3141
+ const pid = parseInt(fs17.readFileSync(lockPath, "utf-8").trim(), 10);
2947
3142
  try {
2948
3143
  process.kill(pid, 0);
2949
3144
  throw new Error(`Pipeline already running (PID ${pid})`);
@@ -2954,11 +3149,11 @@ function acquireLock(taskDir) {
2954
3149
  if (e instanceof Error && e.message.startsWith("Pipeline already")) throw e;
2955
3150
  }
2956
3151
  }
2957
- fs15.writeFileSync(lockPath, String(process.pid));
3152
+ fs17.writeFileSync(lockPath, String(process.pid));
2958
3153
  }
2959
3154
  function releaseLock(taskDir) {
2960
3155
  try {
2961
- fs15.unlinkSync(path15.join(taskDir, ".lock"));
3156
+ fs17.unlinkSync(path17.join(taskDir, ".lock"));
2962
3157
  } catch {
2963
3158
  }
2964
3159
  }
@@ -3126,8 +3321,8 @@ var init_pipeline = __esm({
3126
3321
  });
3127
3322
 
3128
3323
  // src/preflight.ts
3129
- import { execFileSync as execFileSync8 } from "child_process";
3130
- import * as fs16 from "fs";
3324
+ import { execFileSync as execFileSync9 } from "child_process";
3325
+ import * as fs18 from "fs";
3131
3326
  function check(name, fn) {
3132
3327
  try {
3133
3328
  const detail = fn() ?? void 0;
@@ -3139,7 +3334,7 @@ function check(name, fn) {
3139
3334
  function runPreflight() {
3140
3335
  const checks = [
3141
3336
  check("claude CLI", () => {
3142
- const v = execFileSync8("claude", ["--version"], {
3337
+ const v = execFileSync9("claude", ["--version"], {
3143
3338
  encoding: "utf-8",
3144
3339
  timeout: 1e4,
3145
3340
  stdio: ["pipe", "pipe", "pipe"]
@@ -3147,14 +3342,14 @@ function runPreflight() {
3147
3342
  return v;
3148
3343
  }),
3149
3344
  check("git repo", () => {
3150
- execFileSync8("git", ["rev-parse", "--is-inside-work-tree"], {
3345
+ execFileSync9("git", ["rev-parse", "--is-inside-work-tree"], {
3151
3346
  encoding: "utf-8",
3152
3347
  timeout: 5e3,
3153
3348
  stdio: ["pipe", "pipe", "pipe"]
3154
3349
  });
3155
3350
  }),
3156
3351
  check("pnpm", () => {
3157
- const v = execFileSync8("pnpm", ["--version"], {
3352
+ const v = execFileSync9("pnpm", ["--version"], {
3158
3353
  encoding: "utf-8",
3159
3354
  timeout: 5e3,
3160
3355
  stdio: ["pipe", "pipe", "pipe"]
@@ -3162,7 +3357,7 @@ function runPreflight() {
3162
3357
  return v;
3163
3358
  }),
3164
3359
  check("node >= 18", () => {
3165
- const v = execFileSync8("node", ["--version"], {
3360
+ const v = execFileSync9("node", ["--version"], {
3166
3361
  encoding: "utf-8",
3167
3362
  timeout: 5e3,
3168
3363
  stdio: ["pipe", "pipe", "pipe"]
@@ -3172,7 +3367,7 @@ function runPreflight() {
3172
3367
  return v;
3173
3368
  }),
3174
3369
  check("gh CLI", () => {
3175
- const v = execFileSync8("gh", ["--version"], {
3370
+ const v = execFileSync9("gh", ["--version"], {
3176
3371
  encoding: "utf-8",
3177
3372
  timeout: 5e3,
3178
3373
  stdio: ["pipe", "pipe", "pipe"]
@@ -3180,7 +3375,7 @@ function runPreflight() {
3180
3375
  return v;
3181
3376
  }),
3182
3377
  check("package.json", () => {
3183
- if (!fs16.existsSync("package.json")) throw new Error("not found");
3378
+ if (!fs18.existsSync("package.json")) throw new Error("not found");
3184
3379
  })
3185
3380
  ];
3186
3381
  const failed = checks.filter((c) => !c.ok);
@@ -3200,140 +3395,6 @@ var init_preflight = __esm({
3200
3395
  }
3201
3396
  });
3202
3397
 
3203
- // src/cli/task-resolution.ts
3204
- import * as fs17 from "fs";
3205
- import * as path16 from "path";
3206
- import { execFileSync as execFileSync9 } from "child_process";
3207
- function findLatestTaskForIssue(issueNumber, projectDir) {
3208
- const tasksDir = path16.join(projectDir, ".kody", "tasks");
3209
- if (!fs17.existsSync(tasksDir)) return null;
3210
- const allDirs = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
3211
- const prefix = `${issueNumber}-`;
3212
- const direct = allDirs.find((d) => d.startsWith(prefix));
3213
- if (direct) return direct;
3214
- try {
3215
- const branch = execFileSync9("git", ["branch", "--show-current"], {
3216
- encoding: "utf-8",
3217
- cwd: projectDir,
3218
- timeout: 5e3,
3219
- stdio: ["pipe", "pipe", "pipe"]
3220
- }).trim();
3221
- const branchIssueMatch = branch.match(/^(\d+)-/);
3222
- if (branchIssueMatch) {
3223
- const branchIssueNum = branchIssueMatch[1];
3224
- const branchPrefix = `${branchIssueNum}-`;
3225
- const fromBranch = allDirs.find((d) => d.startsWith(branchPrefix));
3226
- if (fromBranch) return fromBranch;
3227
- }
3228
- } catch {
3229
- }
3230
- return null;
3231
- }
3232
- function generateTaskId() {
3233
- const now = /* @__PURE__ */ new Date();
3234
- const pad = (n) => String(n).padStart(2, "0");
3235
- return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
3236
- }
3237
- var init_task_resolution = __esm({
3238
- "src/cli/task-resolution.ts"() {
3239
- "use strict";
3240
- }
3241
- });
3242
-
3243
- // src/review-standalone.ts
3244
- import * as fs18 from "fs";
3245
- import * as path17 from "path";
3246
- function resolveReviewTarget(input) {
3247
- if (input.prs.length === 0) {
3248
- return {
3249
- action: "none",
3250
- message: `Issue #${input.issueNumber} has no open PRs. Nothing to review.`
3251
- };
3252
- }
3253
- if (input.prs.length === 1) {
3254
- return { action: "review", prNumber: input.prs[0].number };
3255
- }
3256
- const prList = input.prs.map((pr) => ` - #${pr.number}: ${pr.title}`).join("\n");
3257
- return {
3258
- action: "pick",
3259
- prs: input.prs,
3260
- message: `\u26A0\uFE0F Issue #${input.issueNumber} has ${input.prs.length} open PRs:
3261
- ${prList}
3262
-
3263
- Run: \`pnpm kody review --pr-number <n>\`
3264
- Or comment on the specific PR: \`@kody review\``
3265
- };
3266
- }
3267
- async function runStandaloneReview(input) {
3268
- const taskId = input.taskId ?? `review-${generateTaskId()}`;
3269
- const taskDir = path17.join(input.projectDir, ".kody", "tasks", taskId);
3270
- fs18.mkdirSync(taskDir, { recursive: true });
3271
- const taskContent = `# ${input.prTitle}
3272
-
3273
- ${input.prBody ?? ""}`;
3274
- fs18.writeFileSync(path17.join(taskDir, "task.md"), taskContent);
3275
- const reviewDef = STAGES.find((s) => s.name === "review");
3276
- const ctx = {
3277
- taskId,
3278
- taskDir,
3279
- projectDir: input.projectDir,
3280
- runners: input.runners,
3281
- sessions: {},
3282
- input: {
3283
- mode: "full",
3284
- local: input.local
3285
- }
3286
- };
3287
- logger.info(`[review] standalone review for: ${input.prTitle}`);
3288
- const result = await executeAgentStage(ctx, reviewDef);
3289
- if (result.outcome !== "completed") {
3290
- return {
3291
- outcome: "failed",
3292
- taskDir,
3293
- error: result.error ?? "Review stage failed"
3294
- };
3295
- }
3296
- const reviewPath = path17.join(taskDir, "review.md");
3297
- let reviewContent;
3298
- if (fs18.existsSync(reviewPath)) {
3299
- reviewContent = fs18.readFileSync(reviewPath, "utf-8");
3300
- }
3301
- return {
3302
- outcome: "completed",
3303
- reviewContent,
3304
- taskDir
3305
- };
3306
- }
3307
- function detectReviewVerdict(reviewContent) {
3308
- const verdictMatch = reviewContent.match(/##\s*Verdict:\s*(PASS|FAIL)/i);
3309
- if (verdictMatch) {
3310
- return verdictMatch[1].toLowerCase();
3311
- }
3312
- const hasCritical = /###\s*Critical\s*\n(?!None\.)/i.test(reviewContent);
3313
- const hasMajor = /###\s*Major\s*\n(?!None\.)/i.test(reviewContent);
3314
- if (hasCritical || hasMajor) return "fail";
3315
- return "pass";
3316
- }
3317
- function formatReviewComment(reviewContent, taskId) {
3318
- const verdict = detectReviewVerdict(reviewContent);
3319
- const cta = verdict === "fail" ? "\n\n> To fix these issues, comment: `@kody fix`\n> The review findings will be used automatically as context." : "";
3320
- return `## \u{1F50D} Kody Review (\`${taskId}\`)
3321
-
3322
- ${reviewContent}${cta}
3323
-
3324
- ---
3325
- \u{1F916} Generated by Kody`;
3326
- }
3327
- var init_review_standalone = __esm({
3328
- "src/review-standalone.ts"() {
3329
- "use strict";
3330
- init_definitions();
3331
- init_agent();
3332
- init_task_resolution();
3333
- init_logger();
3334
- }
3335
- });
3336
-
3337
3398
  // src/cli/args.ts
3338
3399
  function getArg(args2, flag) {
3339
3400
  const idx = args2.indexOf(flag);
@@ -3354,12 +3415,13 @@ function parseArgs() {
3354
3415
  kody fix --task-id <id> [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
3355
3416
  kody fix-ci [--pr-number <n>] [--ci-run-id <id>] [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
3356
3417
  kody review [--pr-number <n>] [--issue-number <n>] [--cwd <path>] [--local]
3418
+ kody resolve --pr-number <n> [--cwd <path>] [--local]
3357
3419
  kody status --task-id <id> [--cwd <path>]
3358
3420
  kody --help`);
3359
3421
  process.exit(0);
3360
3422
  }
3361
3423
  const command2 = args2[0];
3362
- if (!["run", "rerun", "fix", "fix-ci", "status", "review"].includes(command2)) {
3424
+ if (!["run", "rerun", "fix", "fix-ci", "status", "review", "resolve"].includes(command2)) {
3363
3425
  console.error(`Unknown command: ${command2}`);
3364
3426
  process.exit(1);
3365
3427
  }
@@ -3437,15 +3499,14 @@ async function checkModelHealth(baseUrl, apiKey, model = "claude-haiku-4-5") {
3437
3499
  function generateLitellmConfig(provider, modelMap) {
3438
3500
  const apiKeyVar = providerApiKeyEnvVar(provider);
3439
3501
  const entries = ["model_list:"];
3440
- for (const [tier, providerModel] of Object.entries(modelMap)) {
3441
- const anthropicIds = TIER_TO_ANTHROPIC_IDS[tier];
3442
- if (!anthropicIds) continue;
3443
- for (const modelName of anthropicIds) {
3444
- entries.push(` - model_name: ${modelName}`);
3445
- entries.push(` litellm_params:`);
3446
- entries.push(` model: ${provider}/${providerModel}`);
3447
- entries.push(` api_key: os.environ/${apiKeyVar}`);
3448
- }
3502
+ const seen = /* @__PURE__ */ new Set();
3503
+ for (const providerModel of Object.values(modelMap)) {
3504
+ if (seen.has(providerModel)) continue;
3505
+ seen.add(providerModel);
3506
+ entries.push(` - model_name: ${providerModel}`);
3507
+ entries.push(` litellm_params:`);
3508
+ entries.push(` model: ${provider}/${providerModel}`);
3509
+ entries.push(` api_key: os.environ/${apiKeyVar}`);
3449
3510
  }
3450
3511
  return entries.join("\n") + "\n";
3451
3512
  }
@@ -3593,6 +3654,133 @@ var init_task_state = __esm({
3593
3654
  }
3594
3655
  });
3595
3656
 
3657
+ // src/resolve.ts
3658
+ var resolve_exports = {};
3659
+ __export(resolve_exports, {
3660
+ runResolve: () => runResolve
3661
+ });
3662
+ import { execFileSync as execFileSync11 } from "child_process";
3663
+ function getConflictContext(cwd, files) {
3664
+ const parts = [];
3665
+ for (const file of files.slice(0, 10)) {
3666
+ try {
3667
+ const content = execFileSync11("git", ["diff", file], {
3668
+ cwd,
3669
+ encoding: "utf-8",
3670
+ stdio: ["pipe", "pipe", "pipe"]
3671
+ }).trim();
3672
+ parts.push(`### ${file}
3673
+ \`\`\`diff
3674
+ ${content.slice(0, 3e3)}
3675
+ \`\`\``);
3676
+ } catch {
3677
+ parts.push(`### ${file}
3678
+ (could not read diff)`);
3679
+ }
3680
+ }
3681
+ return parts.join("\n\n");
3682
+ }
3683
+ async function runResolve(options) {
3684
+ const { prNumber, projectDir, runners, local } = options;
3685
+ const defaultBranch = getDefaultBranch(projectDir);
3686
+ logger.info(`Resolving PR #${prNumber} \u2014 merging ${defaultBranch}...`);
3687
+ const mergeResult = mergeDefault(projectDir);
3688
+ if (mergeResult === "error") {
3689
+ return { outcome: "failed", error: "Failed to merge default branch" };
3690
+ }
3691
+ if (mergeResult === "clean") {
3692
+ logger.info(" Clean merge \u2014 no conflicts");
3693
+ if (!local) {
3694
+ pushBranch(projectDir);
3695
+ }
3696
+ return { outcome: "merged" };
3697
+ }
3698
+ const conflictedFiles = getConflictedFiles(projectDir);
3699
+ if (conflictedFiles.length === 0) {
3700
+ return { outcome: "failed", error: "Merge reported conflict but no conflicted files found" };
3701
+ }
3702
+ logger.info(` ${conflictedFiles.length} conflicted file(s): ${conflictedFiles.join(", ")}`);
3703
+ const conflictContext = getConflictContext(projectDir, conflictedFiles);
3704
+ const prompt = buildResolvePrompt(conflictedFiles, conflictContext, defaultBranch);
3705
+ const config = getProjectConfig();
3706
+ const runnerName = config.agent.defaultRunner ?? Object.keys(runners)[0] ?? "claude";
3707
+ const runner = runners[runnerName];
3708
+ if (!runner) {
3709
+ return { outcome: "failed", error: `Runner "${runnerName}" not found` };
3710
+ }
3711
+ const model = resolveModel("mid");
3712
+ const extraEnv = {};
3713
+ if (needsLitellmProxy(config)) {
3714
+ extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
3715
+ }
3716
+ logger.info(` Running agent to resolve conflicts (model=${model})...`);
3717
+ const result = await runner.run("resolve", prompt, model, 3e5, projectDir, {
3718
+ cwd: projectDir,
3719
+ env: extraEnv
3720
+ });
3721
+ if (result.outcome !== "completed") {
3722
+ return { outcome: "failed", error: `Agent failed: ${result.error}` };
3723
+ }
3724
+ logger.info(" Verifying resolution...");
3725
+ const verify = runQualityGates(projectDir, projectDir);
3726
+ if (!verify.pass) {
3727
+ const errorSummary = verify.errors.slice(0, 5).join("\n");
3728
+ logger.error(` Verification failed:
3729
+ ${errorSummary}`);
3730
+ return { outcome: "failed", error: `Conflict resolution failed verification:
3731
+ ${errorSummary}` };
3732
+ }
3733
+ logger.info(" Verification passed");
3734
+ commitAll(`chore: resolve merge conflicts with ${defaultBranch}`, projectDir);
3735
+ if (!local) {
3736
+ pushBranch(projectDir);
3737
+ try {
3738
+ const fileList = conflictedFiles.map((f) => `- \`${f}\``).join("\n");
3739
+ postPRComment(
3740
+ prNumber,
3741
+ `\u2705 **Merge conflicts resolved** with \`${defaultBranch}\`
3742
+
3743
+ **Conflicted files:**
3744
+ ${fileList}
3745
+
3746
+ _Verification passed. Please review the resolution._`
3747
+ );
3748
+ } catch {
3749
+ }
3750
+ }
3751
+ return { outcome: "resolved" };
3752
+ }
3753
+ function buildResolvePrompt(files, conflictContext, defaultBranch) {
3754
+ return `You are resolving merge conflicts between a feature branch and the \`${defaultBranch}\` branch.
3755
+
3756
+ ## Conflicted files
3757
+ ${files.map((f) => `- ${f}`).join("\n")}
3758
+
3759
+ ## Conflict diffs
3760
+ ${conflictContext}
3761
+
3762
+ ## Instructions
3763
+ 1. Read each conflicted file
3764
+ 2. Resolve the conflict markers (<<<<<<< / ======= / >>>>>>>) by combining both sides correctly:
3765
+ - For feature/business logic: preserve the PR branch's intent
3766
+ - For infrastructure/config/dependencies: prefer the \`${defaultBranch}\` branch
3767
+ - For imports/types: merge both sides
3768
+ 3. Write the resolved file using the Edit or Write tool
3769
+ 4. Do NOT add new features or refactor \u2014 only resolve the conflicts
3770
+ 5. After resolving all files, run \`git add .\` to stage the resolution`;
3771
+ }
3772
+ var init_resolve = __esm({
3773
+ "src/resolve.ts"() {
3774
+ "use strict";
3775
+ init_git_utils();
3776
+ init_github_api();
3777
+ init_verify_runner();
3778
+ init_logger();
3779
+ init_context();
3780
+ init_config();
3781
+ }
3782
+ });
3783
+
3596
3784
  // src/entry.ts
3597
3785
  var entry_exports = {};
3598
3786
  import * as fs21 from "fs";
@@ -3638,7 +3826,7 @@ async function runModelHealthCheck(config) {
3638
3826
  logger.warn(`Skipping model health check \u2014 ${keyName} not set`);
3639
3827
  return;
3640
3828
  }
3641
- const model = usesProxy ? "claude-haiku-4-5" : config.agent.modelMap.cheap;
3829
+ const model = config.agent.modelMap.cheap;
3642
3830
  logger.info(`Model health check (${model} via ${usesProxy ? "LiteLLM" : "Anthropic"})...`);
3643
3831
  const result = await checkModelHealth(baseUrl, apiKey, model);
3644
3832
  if (result.ok) {
@@ -3771,7 +3959,6 @@ async function main() {
3771
3959
  console.log(result.reviewContent);
3772
3960
  if (!input.local && prNumber) {
3773
3961
  const comment = formatReviewComment(result.reviewContent, taskId);
3774
- postPRComment(prNumber, comment);
3775
3962
  const verdict = detectReviewVerdict(result.reviewContent);
3776
3963
  if (verdict === "fail") {
3777
3964
  submitPRReview(prNumber, comment, "request-changes");
@@ -3782,6 +3969,42 @@ async function main() {
3782
3969
  }
3783
3970
  process.exit(0);
3784
3971
  }
3972
+ if (input.command === "resolve") {
3973
+ if (!input.prNumber) {
3974
+ console.error("--pr-number is required for resolve command");
3975
+ process.exit(1);
3976
+ }
3977
+ runPreflight();
3978
+ const config2 = getProjectConfig();
3979
+ const litellmProcess2 = await ensureLitellmProxy(config2, projectDir);
3980
+ await runModelHealthCheck(config2);
3981
+ const runners2 = createRunners(config2);
3982
+ const defaultRunnerName2 = config2.agent.defaultRunner ?? Object.keys(runners2)[0] ?? "claude";
3983
+ const defaultRunner2 = runners2[defaultRunnerName2];
3984
+ if (!defaultRunner2) {
3985
+ console.error(`Default runner "${defaultRunnerName2}" not configured`);
3986
+ process.exit(1);
3987
+ }
3988
+ const healthy2 = await defaultRunner2.healthCheck();
3989
+ if (!healthy2) {
3990
+ console.error(`Runner "${defaultRunnerName2}" health check failed`);
3991
+ process.exit(1);
3992
+ }
3993
+ const { runResolve: runResolve2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
3994
+ const result = await runResolve2({
3995
+ prNumber: input.prNumber,
3996
+ projectDir,
3997
+ runners: runners2,
3998
+ local: input.local ?? true
3999
+ });
4000
+ if (litellmProcess2) litellmProcess2.kill?.();
4001
+ if (result.outcome === "failed") {
4002
+ console.error(`Resolve failed: ${result.error}`);
4003
+ process.exit(1);
4004
+ }
4005
+ console.log(`Resolve: ${result.outcome}`);
4006
+ process.exit(0);
4007
+ }
3785
4008
  logger.info("Preflight checks:");
3786
4009
  runPreflight();
3787
4010
  if (input.task) {
@@ -4001,7 +4224,7 @@ var init_entry = __esm({
4001
4224
  // src/bin/cli.ts
4002
4225
  import * as fs22 from "fs";
4003
4226
  import * as path21 from "path";
4004
- import { execFileSync as execFileSync11 } from "child_process";
4227
+ import { execFileSync as execFileSync12 } from "child_process";
4005
4228
  import { fileURLToPath } from "url";
4006
4229
  var __dirname = path21.dirname(fileURLToPath(import.meta.url));
4007
4230
  var PKG_ROOT = path21.resolve(__dirname, "..", "..");
@@ -4012,7 +4235,7 @@ function getVersion() {
4012
4235
  }
4013
4236
  function checkCommand2(name, args2, fix) {
4014
4237
  try {
4015
- const output = execFileSync11(name, args2, {
4238
+ const output = execFileSync12(name, args2, {
4016
4239
  encoding: "utf-8",
4017
4240
  timeout: 1e4,
4018
4241
  stdio: ["pipe", "pipe", "pipe"]
@@ -4030,7 +4253,7 @@ function checkFile(filePath, description, fix) {
4030
4253
  }
4031
4254
  function checkGhAuth(cwd) {
4032
4255
  try {
4033
- const output = execFileSync11("gh", ["auth", "status"], {
4256
+ const output = execFileSync12("gh", ["auth", "status"], {
4034
4257
  encoding: "utf-8",
4035
4258
  timeout: 1e4,
4036
4259
  cwd,
@@ -4048,7 +4271,7 @@ function checkGhAuth(cwd) {
4048
4271
  }
4049
4272
  function checkGhRepoAccess(cwd) {
4050
4273
  try {
4051
- const remote = execFileSync11("git", ["remote", "get-url", "origin"], {
4274
+ const remote = execFileSync12("git", ["remote", "get-url", "origin"], {
4052
4275
  encoding: "utf-8",
4053
4276
  timeout: 5e3,
4054
4277
  cwd,
@@ -4059,7 +4282,7 @@ function checkGhRepoAccess(cwd) {
4059
4282
  return { name: "GitHub repo", ok: false, fix: "Set git remote origin to a GitHub URL" };
4060
4283
  }
4061
4284
  const repoSlug = `${match[1]}/${match[2]}`;
4062
- execFileSync11("gh", ["repo", "view", repoSlug, "--json", "name"], {
4285
+ execFileSync12("gh", ["repo", "view", repoSlug, "--json", "name"], {
4063
4286
  encoding: "utf-8",
4064
4287
  timeout: 1e4,
4065
4288
  cwd,
@@ -4072,7 +4295,7 @@ function checkGhRepoAccess(cwd) {
4072
4295
  }
4073
4296
  function checkGhSecret(repoSlug, secretName) {
4074
4297
  try {
4075
- const output = execFileSync11("gh", ["secret", "list", "--repo", repoSlug], {
4298
+ const output = execFileSync12("gh", ["secret", "list", "--repo", repoSlug], {
4076
4299
  encoding: "utf-8",
4077
4300
  timeout: 1e4,
4078
4301
  stdio: ["pipe", "pipe", "pipe"]
@@ -4100,7 +4323,7 @@ function detectBasicConfig(cwd) {
4100
4323
  else if (!fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml")) && fs22.existsSync(path21.join(cwd, "package-lock.json"))) pm = "npm";
4101
4324
  let defaultBranch = "main";
4102
4325
  try {
4103
- const ref = execFileSync11("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
4326
+ const ref = execFileSync12("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
4104
4327
  encoding: "utf-8",
4105
4328
  timeout: 5e3,
4106
4329
  cwd,
@@ -4109,7 +4332,7 @@ function detectBasicConfig(cwd) {
4109
4332
  defaultBranch = ref.replace("refs/remotes/origin/", "");
4110
4333
  } catch {
4111
4334
  try {
4112
- execFileSync11("git", ["rev-parse", "--verify", "origin/dev"], {
4335
+ execFileSync12("git", ["rev-parse", "--verify", "origin/dev"], {
4113
4336
  encoding: "utf-8",
4114
4337
  timeout: 5e3,
4115
4338
  cwd,
@@ -4122,7 +4345,7 @@ function detectBasicConfig(cwd) {
4122
4345
  let owner = "";
4123
4346
  let repo = "";
4124
4347
  try {
4125
- const remote = execFileSync11("git", ["remote", "get-url", "origin"], {
4348
+ const remote = execFileSync12("git", ["remote", "get-url", "origin"], {
4126
4349
  encoding: "utf-8",
4127
4350
  timeout: 5e3,
4128
4351
  cwd,
@@ -4314,7 +4537,7 @@ function initCommand(opts) {
4314
4537
  if (filesToCommit.length > 0) {
4315
4538
  try {
4316
4539
  const fullPaths = filesToCommit.map((f) => path21.join(cwd, f));
4317
- execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
4540
+ execFileSync12("npx", ["prettier", "--write", ...fullPaths], {
4318
4541
  cwd,
4319
4542
  encoding: "utf-8",
4320
4543
  timeout: 3e4,
@@ -4325,13 +4548,13 @@ function initCommand(opts) {
4325
4548
  }
4326
4549
  if (filesToCommit.length > 0) {
4327
4550
  try {
4328
- execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
4329
- const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
4551
+ execFileSync12("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
4552
+ const staged = execFileSync12("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
4330
4553
  if (staged) {
4331
- execFileSync11("git", ["commit", "-m", "chore: Add Kody Engine workflow and config\n\nAdd GitHub Actions workflow and auto-detected configuration for Kody Engine Lite."], { cwd, stdio: "pipe" });
4554
+ execFileSync12("git", ["commit", "-m", "chore: Add Kody Engine workflow and config\n\nAdd GitHub Actions workflow and auto-detected configuration for Kody Engine Lite."], { cwd, stdio: "pipe" });
4332
4555
  console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
4333
4556
  try {
4334
- execFileSync11("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
4557
+ execFileSync12("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
4335
4558
  console.log(" \u2713 Pushed to origin");
4336
4559
  } catch {
4337
4560
  console.log(" \u25CB Push failed \u2014 run 'git push' manually");
@@ -4425,7 +4648,7 @@ function ghComment(issueNumber, body, cwd) {
4425
4648
  } catch {
4426
4649
  }
4427
4650
  if (!repoSlug) return;
4428
- execFileSync11("gh", [
4651
+ execFileSync12("gh", [
4429
4652
  "issue",
4430
4653
  "comment",
4431
4654
  String(issueNumber),
@@ -4558,7 +4781,7 @@ Output ONLY valid JSON. No markdown fences. No explanation.
4558
4781
  ${repoContext}`;
4559
4782
  console.log(" \u23F3 Analyzing project...");
4560
4783
  try {
4561
- const output = execFileSync11("claude", [
4784
+ const output = execFileSync12("claude", [
4562
4785
  "--print",
4563
4786
  "--model",
4564
4787
  "haiku",
@@ -4661,7 +4884,7 @@ ${repoContext}
4661
4884
 
4662
4885
  REMINDER: Output the full prompt template first (unchanged), then your three appended sections. Do NOT include "${contextPlaceholder}".`;
4663
4886
  try {
4664
- const output = execFileSync11("claude", [
4887
+ const output = execFileSync12("claude", [
4665
4888
  "--print",
4666
4889
  "--model",
4667
4890
  "haiku",
@@ -4718,7 +4941,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
4718
4941
  ];
4719
4942
  for (const label of labels) {
4720
4943
  try {
4721
- execFileSync11("gh", [
4944
+ execFileSync12("gh", [
4722
4945
  "label",
4723
4946
  "create",
4724
4947
  label.name,
@@ -4738,7 +4961,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
4738
4961
  console.log(` \u2713 ${label.name}`);
4739
4962
  } catch {
4740
4963
  try {
4741
- execFileSync11("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
4964
+ execFileSync12("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
4742
4965
  cwd,
4743
4966
  encoding: "utf-8",
4744
4967
  timeout: 1e4,
@@ -4777,7 +5000,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
4777
5000
  try {
4778
5001
  const fullPaths = filesToCommit.map((f) => path21.join(cwd, f));
4779
5002
  for (let pass = 0; pass < 2; pass++) {
4780
- execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
5003
+ execFileSync12("npx", ["prettier", "--write", ...fullPaths], {
4781
5004
  cwd,
4782
5005
  encoding: "utf-8",
4783
5006
  timeout: 3e4,
@@ -4793,12 +5016,12 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
4793
5016
  try {
4794
5017
  if (isCI3) {
4795
5018
  const branchName = `kody-bootstrap-${Date.now()}`;
4796
- execFileSync11("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
4797
- execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
4798
- const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
5019
+ execFileSync12("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
5020
+ execFileSync12("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
5021
+ const staged = execFileSync12("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
4799
5022
  if (staged) {
4800
- execFileSync11("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
4801
- execFileSync11("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
5023
+ execFileSync12("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
5024
+ execFileSync12("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
4802
5025
  console.log(` \u2713 Pushed branch: ${branchName}`);
4803
5026
  let baseBranch = "main";
4804
5027
  try {
@@ -4810,7 +5033,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
4810
5033
  } catch {
4811
5034
  }
4812
5035
  try {
4813
- const prUrl = execFileSync11("gh", [
5036
+ const prUrl = execFileSync12("gh", [
4814
5037
  "pr",
4815
5038
  "create",
4816
5039
  "--title",
@@ -4849,13 +5072,13 @@ Create it manually.`, cwd);
4849
5072
  console.log(" \u25CB No new changes to commit");
4850
5073
  }
4851
5074
  } else {
4852
- execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
4853
- const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
5075
+ execFileSync12("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
5076
+ const staged = execFileSync12("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
4854
5077
  if (staged) {
4855
- execFileSync11("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
5078
+ execFileSync12("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
4856
5079
  console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
4857
5080
  try {
4858
- execFileSync11("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
5081
+ execFileSync12("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
4859
5082
  console.log(" \u2713 Pushed to origin");
4860
5083
  } catch {
4861
5084
  console.log(" \u25CB Push failed \u2014 run 'git push' manually");
@@ -4976,7 +5199,7 @@ function installSkillsForProject(cwd) {
4976
5199
  }
4977
5200
  try {
4978
5201
  console.log(` Installing: ${skill.label} (${skill.package})`);
4979
- execFileSync11("npx", ["skills", "add", skill.package, "--yes"], {
5202
+ execFileSync12("npx", ["skills", "add", skill.package, "--yes"], {
4980
5203
  cwd,
4981
5204
  encoding: "utf-8",
4982
5205
  timeout: 6e4,