@kody-ade/kody-engine-lite 0.1.81 → 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 (2) hide show
  1. package/dist/bin/cli.js +295 -275
  2. package/package.json +1 -1
package/dist/bin/cli.js CHANGED
@@ -137,7 +137,7 @@ var init_agent_runner = __esm({
137
137
  "src/agent-runner.ts"() {
138
138
  "use strict";
139
139
  SIGKILL_GRACE_MS = 5e3;
140
- STDERR_TAIL_CHARS = 500;
140
+ STDERR_TAIL_CHARS = 2e3;
141
141
  RUNNER_FACTORIES = {
142
142
  "claude-code": createClaudeCodeRunner
143
143
  };
@@ -282,7 +282,11 @@ function getProjectConfig() {
282
282
  quality: { ...DEFAULT_CONFIG.quality, ...raw.quality },
283
283
  git: { ...DEFAULT_CONFIG.git, ...raw.git },
284
284
  github: { ...DEFAULT_CONFIG.github, ...raw.github },
285
- 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
+ },
286
290
  contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers,
287
291
  mcp: raw.mcp ? { enabled: false, servers: {}, stages: ["build", "verify", "review", "review-fix"], ...raw.mcp } : void 0
288
292
  };
@@ -1868,8 +1872,8 @@ async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, mo
1868
1872
  `Stage: ${stageName}`,
1869
1873
  ``,
1870
1874
  `Error output:`,
1871
- errorOutput.slice(-2e3),
1872
- // Last 2000 chars of error
1875
+ errorOutput.slice(-5e3),
1876
+ // Last 5000 chars of error for accurate diagnosis
1873
1877
  ``,
1874
1878
  modifiedFiles.length > 0 ? `Files modified by build stage:
1875
1879
  ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (build may not have run yet)."
@@ -1916,13 +1920,21 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
1916
1920
  }
1917
1921
  function getModifiedFiles(projectDir) {
1918
1922
  try {
1919
- const output = execFileSync5("git", ["diff", "--name-only", "HEAD~1"], {
1923
+ const staged = execFileSync5("git", ["diff", "--name-only", "--cached"], {
1920
1924
  encoding: "utf-8",
1921
1925
  cwd: projectDir,
1922
1926
  timeout: 5e3,
1923
1927
  stdio: ["pipe", "pipe", "pipe"]
1924
1928
  }).trim();
1925
- 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)];
1926
1938
  } catch {
1927
1939
  return [];
1928
1940
  }
@@ -2122,55 +2134,198 @@ var init_verify = __esm({
2122
2134
  }
2123
2135
  });
2124
2136
 
2125
- // src/stages/review.ts
2137
+ // src/cli/task-resolution.ts
2126
2138
  import * as fs9 from "fs";
2127
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";
2128
2274
  async function executeReviewWithFix(ctx, def) {
2129
2275
  if (ctx.input.dryRun) {
2130
2276
  return { outcome: "completed", retries: 0 };
2131
2277
  }
2132
2278
  const reviewDef = STAGES.find((s) => s.name === "review");
2133
2279
  const reviewFixDef = STAGES.find((s) => s.name === "review-fix");
2134
- const reviewResult = await executeAgentStage(ctx, reviewDef);
2135
- if (reviewResult.outcome !== "completed") {
2136
- return reviewResult;
2137
- }
2138
- const reviewFile = path9.join(ctx.taskDir, "review.md");
2139
- if (!fs9.existsSync(reviewFile)) {
2140
- return { outcome: "failed", retries: 0, error: "review.md not found" };
2141
- }
2142
- const content = fs9.readFileSync(reviewFile, "utf-8");
2143
- const hasIssues = /\bfail\b/i.test(content) && !/pass/i.test(content);
2144
- if (!hasIssues) {
2145
- return reviewResult;
2146
- }
2147
- logger.info(` review found issues, running review-fix...`);
2148
- const fixResult = await executeAgentStage(ctx, reviewFixDef);
2149
- if (fixResult.outcome !== "completed") {
2150
- 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
+ }
2151
2304
  }
2152
- logger.info(` re-running review after fix...`);
2153
- return executeAgentStage(ctx, reviewDef);
2305
+ return { outcome: "failed", retries: MAX_REVIEW_FIX_ITERATIONS, error: "Unexpected review-fix loop exit" };
2154
2306
  }
2307
+ var MAX_REVIEW_FIX_ITERATIONS;
2155
2308
  var init_review = __esm({
2156
2309
  "src/stages/review.ts"() {
2157
2310
  "use strict";
2158
2311
  init_definitions();
2159
2312
  init_logger();
2160
2313
  init_agent();
2314
+ init_review_standalone();
2315
+ MAX_REVIEW_FIX_ITERATIONS = 2;
2161
2316
  }
2162
2317
  });
2163
2318
 
2164
2319
  // src/stages/ship.ts
2165
- import * as fs10 from "fs";
2166
- import * as path10 from "path";
2167
- 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";
2168
2323
  function buildPrBody(ctx) {
2169
2324
  const sections = [];
2170
- const taskJsonPath = path10.join(ctx.taskDir, "task.json");
2171
- if (fs10.existsSync(taskJsonPath)) {
2325
+ const taskJsonPath = path12.join(ctx.taskDir, "task.json");
2326
+ if (fs12.existsSync(taskJsonPath)) {
2172
2327
  try {
2173
- const raw = fs10.readFileSync(taskJsonPath, "utf-8");
2328
+ const raw = fs12.readFileSync(taskJsonPath, "utf-8");
2174
2329
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2175
2330
  const task = JSON.parse(cleaned);
2176
2331
  if (task.description) {
@@ -2189,9 +2344,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
2189
2344
  } catch {
2190
2345
  }
2191
2346
  }
2192
- const reviewPath = path10.join(ctx.taskDir, "review.md");
2193
- if (fs10.existsSync(reviewPath)) {
2194
- 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");
2195
2350
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
2196
2351
  if (summaryMatch) {
2197
2352
  const summary = summaryMatch[1].trim();
@@ -2208,14 +2363,14 @@ ${summary}`);
2208
2363
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
2209
2364
  }
2210
2365
  }
2211
- const verifyPath = path10.join(ctx.taskDir, "verify.md");
2212
- if (fs10.existsSync(verifyPath)) {
2213
- 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");
2214
2369
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
2215
2370
  }
2216
- const planPath = path10.join(ctx.taskDir, "plan.md");
2217
- if (fs10.existsSync(planPath)) {
2218
- 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();
2219
2374
  if (plan) {
2220
2375
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
2221
2376
  sections.push(`
@@ -2235,25 +2390,25 @@ Closes #${ctx.input.issueNumber}`);
2235
2390
  return sections.join("\n");
2236
2391
  }
2237
2392
  function executeShipStage(ctx, _def) {
2238
- const shipPath = path10.join(ctx.taskDir, "ship.md");
2393
+ const shipPath = path12.join(ctx.taskDir, "ship.md");
2239
2394
  if (ctx.input.dryRun) {
2240
- 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");
2241
2396
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
2242
2397
  }
2243
2398
  if (ctx.input.local && !ctx.input.issueNumber) {
2244
- 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");
2245
2400
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
2246
2401
  }
2247
2402
  try {
2248
2403
  const head = getCurrentBranch(ctx.projectDir);
2249
2404
  const base = getDefaultBranch(ctx.projectDir);
2250
2405
  try {
2251
- execFileSync7("git", ["add", ctx.taskDir], {
2406
+ execFileSync8("git", ["add", ctx.taskDir], {
2252
2407
  cwd: ctx.projectDir,
2253
2408
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
2254
2409
  stdio: "pipe"
2255
2410
  });
2256
- 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]`], {
2257
2412
  cwd: ctx.projectDir,
2258
2413
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
2259
2414
  stdio: "pipe"
@@ -2267,7 +2422,7 @@ function executeShipStage(ctx, _def) {
2267
2422
  let repo = config.github?.repo;
2268
2423
  if (!owner || !repo) {
2269
2424
  try {
2270
- const remoteUrl = execFileSync7("git", ["remote", "get-url", "origin"], {
2425
+ const remoteUrl = execFileSync8("git", ["remote", "get-url", "origin"], {
2271
2426
  encoding: "utf-8",
2272
2427
  cwd: ctx.projectDir
2273
2428
  }).trim();
@@ -2287,10 +2442,10 @@ function executeShipStage(ctx, _def) {
2287
2442
  docs: "docs",
2288
2443
  chore: "chore"
2289
2444
  };
2290
- const taskJsonPath = path10.join(ctx.taskDir, "task.json");
2291
- if (fs10.existsSync(taskJsonPath)) {
2445
+ const taskJsonPath = path12.join(ctx.taskDir, "task.json");
2446
+ if (fs12.existsSync(taskJsonPath)) {
2292
2447
  try {
2293
- const raw = fs10.readFileSync(taskJsonPath, "utf-8");
2448
+ const raw = fs12.readFileSync(taskJsonPath, "utf-8");
2294
2449
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2295
2450
  const task = JSON.parse(cleaned);
2296
2451
  const prefix = TYPE_PREFIX[task.task_type] ?? "chore";
@@ -2300,9 +2455,9 @@ function executeShipStage(ctx, _def) {
2300
2455
  }
2301
2456
  }
2302
2457
  if (title === "Update") {
2303
- const taskMdPath = path10.join(ctx.taskDir, "task.md");
2304
- if (fs10.existsSync(taskMdPath)) {
2305
- 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");
2306
2461
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("*"));
2307
2462
  if (firstLine) title = `chore: ${firstLine.trim()}`.slice(0, 72);
2308
2463
  }
@@ -2322,7 +2477,7 @@ function executeShipStage(ctx, _def) {
2322
2477
  } catch {
2323
2478
  }
2324
2479
  }
2325
- fs10.writeFileSync(shipPath, `# Ship
2480
+ fs12.writeFileSync(shipPath, `# Ship
2326
2481
 
2327
2482
  Updated existing PR: ${existingPr.url}
2328
2483
  PR #${existingPr.number}
@@ -2343,19 +2498,19 @@ PR #${existingPr.number}
2343
2498
  } catch {
2344
2499
  }
2345
2500
  }
2346
- fs10.writeFileSync(shipPath, `# Ship
2501
+ fs12.writeFileSync(shipPath, `# Ship
2347
2502
 
2348
2503
  PR created: ${pr.url}
2349
2504
  PR #${pr.number}
2350
2505
  `);
2351
2506
  } else {
2352
- 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");
2353
2508
  }
2354
2509
  }
2355
2510
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
2356
2511
  } catch (err) {
2357
2512
  const msg = err instanceof Error ? err.message : String(err);
2358
- fs10.writeFileSync(shipPath, `# Ship
2513
+ fs12.writeFileSync(shipPath, `# Ship
2359
2514
 
2360
2515
  Failed: ${msg}
2361
2516
  `);
@@ -2401,15 +2556,15 @@ var init_executor_registry = __esm({
2401
2556
  });
2402
2557
 
2403
2558
  // src/pipeline/questions.ts
2404
- import * as fs11 from "fs";
2405
- import * as path11 from "path";
2559
+ import * as fs13 from "fs";
2560
+ import * as path13 from "path";
2406
2561
  function checkForQuestions(ctx, stageName) {
2407
2562
  if (ctx.input.local || !ctx.input.issueNumber) return false;
2408
2563
  try {
2409
2564
  if (stageName === "taskify") {
2410
- const taskJsonPath = path11.join(ctx.taskDir, "task.json");
2411
- if (!fs11.existsSync(taskJsonPath)) return false;
2412
- 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");
2413
2568
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2414
2569
  const taskJson = JSON.parse(cleaned);
2415
2570
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -2424,9 +2579,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
2424
2579
  }
2425
2580
  }
2426
2581
  if (stageName === "plan") {
2427
- const planPath = path11.join(ctx.taskDir, "plan.md");
2428
- if (!fs11.existsSync(planPath)) return false;
2429
- 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");
2430
2585
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
2431
2586
  if (questionsMatch) {
2432
2587
  const questionsText = questionsMatch[1].trim();
@@ -2455,8 +2610,8 @@ var init_questions = __esm({
2455
2610
  });
2456
2611
 
2457
2612
  // src/pipeline/hooks.ts
2458
- import * as fs12 from "fs";
2459
- import * as path12 from "path";
2613
+ import * as fs14 from "fs";
2614
+ import * as path14 from "path";
2460
2615
  function applyPreStageLabel(ctx, def) {
2461
2616
  if (!ctx.input.issueNumber || ctx.input.local) return;
2462
2617
  if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
@@ -2482,9 +2637,9 @@ function autoDetectComplexity(ctx, def) {
2482
2637
  if (def.name !== "taskify") return null;
2483
2638
  if (ctx.input.complexity) return null;
2484
2639
  try {
2485
- const taskJsonPath = path12.join(ctx.taskDir, "task.json");
2486
- if (!fs12.existsSync(taskJsonPath)) return null;
2487
- 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");
2488
2643
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2489
2644
  const taskJson = JSON.parse(cleaned);
2490
2645
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -2514,8 +2669,8 @@ function checkRiskGate(ctx, def, state, complexity) {
2514
2669
  if (ctx.input.dryRun || ctx.input.local) return null;
2515
2670
  if (ctx.input.mode === "rerun") return null;
2516
2671
  if (!ctx.input.issueNumber) return null;
2517
- const planPath = path12.join(ctx.taskDir, "plan.md");
2518
- 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)";
2519
2674
  try {
2520
2675
  postComment(
2521
2676
  ctx.input.issueNumber,
@@ -2582,22 +2737,22 @@ var init_hooks = __esm({
2582
2737
  });
2583
2738
 
2584
2739
  // src/learning/auto-learn.ts
2585
- import * as fs13 from "fs";
2586
- import * as path13 from "path";
2740
+ import * as fs15 from "fs";
2741
+ import * as path15 from "path";
2587
2742
  function stripAnsi(str) {
2588
2743
  return str.replace(/\x1b\[[0-9;]*m/g, "");
2589
2744
  }
2590
2745
  function autoLearn(ctx) {
2591
2746
  try {
2592
- const memoryDir = path13.join(ctx.projectDir, ".kody", "memory");
2593
- if (!fs13.existsSync(memoryDir)) {
2594
- 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 });
2595
2750
  }
2596
2751
  const learnings = [];
2597
2752
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2598
- const verifyPath = path13.join(ctx.taskDir, "verify.md");
2599
- if (fs13.existsSync(verifyPath)) {
2600
- 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"));
2601
2756
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
2602
2757
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
2603
2758
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -2606,18 +2761,18 @@ function autoLearn(ctx) {
2606
2761
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
2607
2762
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
2608
2763
  }
2609
- const reviewPath = path13.join(ctx.taskDir, "review.md");
2610
- if (fs13.existsSync(reviewPath)) {
2611
- 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");
2612
2767
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
2613
2768
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
2614
2769
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
2615
2770
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
2616
2771
  }
2617
- const taskJsonPath = path13.join(ctx.taskDir, "task.json");
2618
- if (fs13.existsSync(taskJsonPath)) {
2772
+ const taskJsonPath = path15.join(ctx.taskDir, "task.json");
2773
+ if (fs15.existsSync(taskJsonPath)) {
2619
2774
  try {
2620
- const raw = stripAnsi(fs13.readFileSync(taskJsonPath, "utf-8"));
2775
+ const raw = stripAnsi(fs15.readFileSync(taskJsonPath, "utf-8"));
2621
2776
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
2622
2777
  const task = JSON.parse(cleaned);
2623
2778
  if (task.scope && Array.isArray(task.scope)) {
@@ -2628,13 +2783,13 @@ function autoLearn(ctx) {
2628
2783
  }
2629
2784
  }
2630
2785
  if (learnings.length > 0) {
2631
- const conventionsPath = path13.join(memoryDir, "conventions.md");
2786
+ const conventionsPath = path15.join(memoryDir, "conventions.md");
2632
2787
  const entry = `
2633
2788
  ## Learned ${timestamp2} (task: ${ctx.taskId})
2634
2789
  ${learnings.join("\n")}
2635
2790
  `;
2636
- fs13.appendFileSync(conventionsPath, entry);
2637
- invalidateCache(conventionsPath, path13.join(memoryDir, ".tiers"));
2791
+ fs15.appendFileSync(conventionsPath, entry);
2792
+ invalidateCache(conventionsPath, path15.join(memoryDir, ".tiers"));
2638
2793
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
2639
2794
  }
2640
2795
  autoLearnDecisions(ctx.taskDir, memoryDir, ctx.taskId, timestamp2);
@@ -2643,13 +2798,13 @@ ${learnings.join("\n")}
2643
2798
  }
2644
2799
  }
2645
2800
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2646
- const archPath = path13.join(memoryDir, "architecture.md");
2647
- if (fs13.existsSync(archPath)) return;
2801
+ const archPath = path15.join(memoryDir, "architecture.md");
2802
+ if (fs15.existsSync(archPath)) return;
2648
2803
  const detected = [];
2649
- const pkgPath = path13.join(projectDir, "package.json");
2650
- if (fs13.existsSync(pkgPath)) {
2804
+ const pkgPath = path15.join(projectDir, "package.json");
2805
+ if (fs15.existsSync(pkgPath)) {
2651
2806
  try {
2652
- const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
2807
+ const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
2653
2808
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
2654
2809
  if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
2655
2810
  else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
@@ -2665,15 +2820,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2665
2820
  if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push(`- CMS: Payload CMS`);
2666
2821
  if (pkg.type === "module") detected.push("- Module system: ESM");
2667
2822
  else detected.push("- Module system: CommonJS");
2668
- if (fs13.existsSync(path13.join(projectDir, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
2669
- else if (fs13.existsSync(path13.join(projectDir, "yarn.lock"))) detected.push("- Package manager: yarn");
2670
- 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");
2671
2826
  } catch {
2672
2827
  }
2673
2828
  }
2674
2829
  const topDirs = [];
2675
2830
  try {
2676
- const entries = fs13.readdirSync(projectDir, { withFileTypes: true });
2831
+ const entries = fs15.readdirSync(projectDir, { withFileTypes: true });
2677
2832
  for (const entry of entries) {
2678
2833
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
2679
2834
  topDirs.push(entry.name);
@@ -2682,10 +2837,10 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2682
2837
  if (topDirs.length > 0) detected.push(`- Top-level directories: ${topDirs.join(", ")}`);
2683
2838
  } catch {
2684
2839
  }
2685
- const srcDir = path13.join(projectDir, "src");
2686
- if (fs13.existsSync(srcDir)) {
2840
+ const srcDir = path15.join(projectDir, "src");
2841
+ if (fs15.existsSync(srcDir)) {
2687
2842
  try {
2688
- const srcEntries = fs13.readdirSync(srcDir, { withFileTypes: true });
2843
+ const srcEntries = fs15.readdirSync(srcDir, { withFileTypes: true });
2689
2844
  const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
2690
2845
  if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
2691
2846
  } catch {
@@ -2697,15 +2852,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2697
2852
  ## Overview
2698
2853
  ${detected.join("\n")}
2699
2854
  `;
2700
- fs13.writeFileSync(archPath, content);
2701
- invalidateCache(archPath, path13.join(memoryDir, ".tiers"));
2855
+ fs15.writeFileSync(archPath, content);
2856
+ invalidateCache(archPath, path15.join(memoryDir, ".tiers"));
2702
2857
  logger.info(`Auto-detected architecture (${detected.length} items)`);
2703
2858
  }
2704
2859
  }
2705
2860
  function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
2706
- const reviewPath = path13.join(taskDir, "review.md");
2707
- if (!fs13.existsSync(reviewPath)) return;
2708
- 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");
2709
2864
  const decisions = [];
2710
2865
  const existingPatternRe = /(?:use|follow|reuse|match|adopt)\s+(?:the\s+)?existing\s+(.+?)(?:\.|$)/gim;
2711
2866
  for (const match of review.matchAll(existingPatternRe)) {
@@ -2724,10 +2879,10 @@ function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
2724
2879
  decisions.push(`- Avoid ${match[1].trim()} for ${match[2].trim()}`);
2725
2880
  }
2726
2881
  if (decisions.length === 0) return;
2727
- const decisionsPath = path13.join(memoryDir, "decisions.md");
2882
+ const decisionsPath = path15.join(memoryDir, "decisions.md");
2728
2883
  let existing = "";
2729
- if (fs13.existsSync(decisionsPath)) {
2730
- existing = fs13.readFileSync(decisionsPath, "utf-8");
2884
+ if (fs15.existsSync(decisionsPath)) {
2885
+ existing = fs15.readFileSync(decisionsPath, "utf-8");
2731
2886
  } else {
2732
2887
  existing = "# Architectural Decisions\n\nDecisions extracted from code reviews. The planning agent MUST follow these.\n";
2733
2888
  }
@@ -2737,8 +2892,8 @@ function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
2737
2892
  ## From task ${taskId} (${timestamp2})
2738
2893
  ${newDecisions.join("\n")}
2739
2894
  `;
2740
- fs13.appendFileSync(decisionsPath, existing ? entry : existing + entry);
2741
- invalidateCache(decisionsPath, path13.join(memoryDir, ".tiers"));
2895
+ fs15.appendFileSync(decisionsPath, existing ? entry : existing + entry);
2896
+ invalidateCache(decisionsPath, path15.join(memoryDir, ".tiers"));
2742
2897
  logger.info(`Auto-learned ${newDecisions.length} architectural decision(s)`);
2743
2898
  }
2744
2899
  var init_auto_learn = __esm({
@@ -2750,13 +2905,13 @@ var init_auto_learn = __esm({
2750
2905
  });
2751
2906
 
2752
2907
  // src/retrospective.ts
2753
- import * as fs14 from "fs";
2754
- import * as path14 from "path";
2908
+ import * as fs16 from "fs";
2909
+ import * as path16 from "path";
2755
2910
  function readArtifact(taskDir, filename, maxChars) {
2756
- const p = path14.join(taskDir, filename);
2757
- if (!fs14.existsSync(p)) return null;
2911
+ const p = path16.join(taskDir, filename);
2912
+ if (!fs16.existsSync(p)) return null;
2758
2913
  try {
2759
- const content = fs14.readFileSync(p, "utf-8");
2914
+ const content = fs16.readFileSync(p, "utf-8");
2760
2915
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
2761
2916
  } catch {
2762
2917
  return null;
@@ -2809,13 +2964,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
2809
2964
  return lines.join("\n");
2810
2965
  }
2811
2966
  function getLogPath(projectDir) {
2812
- return path14.join(projectDir, ".kody", "memory", "observer-log.jsonl");
2967
+ return path16.join(projectDir, ".kody", "memory", "observer-log.jsonl");
2813
2968
  }
2814
2969
  function readPreviousRetrospectives(projectDir, limit = 10) {
2815
2970
  const logPath = getLogPath(projectDir);
2816
- if (!fs14.existsSync(logPath)) return [];
2971
+ if (!fs16.existsSync(logPath)) return [];
2817
2972
  try {
2818
- const content = fs14.readFileSync(logPath, "utf-8");
2973
+ const content = fs16.readFileSync(logPath, "utf-8");
2819
2974
  const lines = content.split("\n").filter(Boolean);
2820
2975
  const entries = [];
2821
2976
  const start = Math.max(0, lines.length - limit);
@@ -2842,11 +2997,11 @@ function formatPreviousEntries(entries) {
2842
2997
  }
2843
2998
  function appendRetrospectiveEntry(projectDir, entry) {
2844
2999
  const logPath = getLogPath(projectDir);
2845
- const dir = path14.dirname(logPath);
2846
- if (!fs14.existsSync(dir)) {
2847
- fs14.mkdirSync(dir, { recursive: true });
3000
+ const dir = path16.dirname(logPath);
3001
+ if (!fs16.existsSync(dir)) {
3002
+ fs16.mkdirSync(dir, { recursive: true });
2848
3003
  }
2849
- fs14.appendFileSync(logPath, JSON.stringify(entry) + "\n");
3004
+ fs16.appendFileSync(logPath, JSON.stringify(entry) + "\n");
2850
3005
  }
2851
3006
  async function runRetrospective(ctx, state, pipelineStartTime) {
2852
3007
  if (ctx.input.dryRun) return;
@@ -2957,8 +3112,8 @@ If no pipeline flaw is detected, set "pipelineFlaw" to null.
2957
3112
  });
2958
3113
 
2959
3114
  // src/pipeline.ts
2960
- import * as fs15 from "fs";
2961
- import * as path15 from "path";
3115
+ import * as fs17 from "fs";
3116
+ import * as path17 from "path";
2962
3117
  function ensureFeatureBranchIfNeeded(ctx) {
2963
3118
  if (ctx.input.dryRun) return;
2964
3119
  if (ctx.input.prNumber) {
@@ -2971,8 +3126,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
2971
3126
  }
2972
3127
  if (!ctx.input.issueNumber) return;
2973
3128
  try {
2974
- const taskMdPath = path15.join(ctx.taskDir, "task.md");
2975
- 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;
2976
3131
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
2977
3132
  syncWithDefault(ctx.projectDir);
2978
3133
  } catch (err) {
@@ -2980,10 +3135,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
2980
3135
  }
2981
3136
  }
2982
3137
  function acquireLock(taskDir) {
2983
- const lockPath = path15.join(taskDir, ".lock");
2984
- if (fs15.existsSync(lockPath)) {
3138
+ const lockPath = path17.join(taskDir, ".lock");
3139
+ if (fs17.existsSync(lockPath)) {
2985
3140
  try {
2986
- const pid = parseInt(fs15.readFileSync(lockPath, "utf-8").trim(), 10);
3141
+ const pid = parseInt(fs17.readFileSync(lockPath, "utf-8").trim(), 10);
2987
3142
  try {
2988
3143
  process.kill(pid, 0);
2989
3144
  throw new Error(`Pipeline already running (PID ${pid})`);
@@ -2994,11 +3149,11 @@ function acquireLock(taskDir) {
2994
3149
  if (e instanceof Error && e.message.startsWith("Pipeline already")) throw e;
2995
3150
  }
2996
3151
  }
2997
- fs15.writeFileSync(lockPath, String(process.pid));
3152
+ fs17.writeFileSync(lockPath, String(process.pid));
2998
3153
  }
2999
3154
  function releaseLock(taskDir) {
3000
3155
  try {
3001
- fs15.unlinkSync(path15.join(taskDir, ".lock"));
3156
+ fs17.unlinkSync(path17.join(taskDir, ".lock"));
3002
3157
  } catch {
3003
3158
  }
3004
3159
  }
@@ -3166,8 +3321,8 @@ var init_pipeline = __esm({
3166
3321
  });
3167
3322
 
3168
3323
  // src/preflight.ts
3169
- import { execFileSync as execFileSync8 } from "child_process";
3170
- import * as fs16 from "fs";
3324
+ import { execFileSync as execFileSync9 } from "child_process";
3325
+ import * as fs18 from "fs";
3171
3326
  function check(name, fn) {
3172
3327
  try {
3173
3328
  const detail = fn() ?? void 0;
@@ -3179,7 +3334,7 @@ function check(name, fn) {
3179
3334
  function runPreflight() {
3180
3335
  const checks = [
3181
3336
  check("claude CLI", () => {
3182
- const v = execFileSync8("claude", ["--version"], {
3337
+ const v = execFileSync9("claude", ["--version"], {
3183
3338
  encoding: "utf-8",
3184
3339
  timeout: 1e4,
3185
3340
  stdio: ["pipe", "pipe", "pipe"]
@@ -3187,14 +3342,14 @@ function runPreflight() {
3187
3342
  return v;
3188
3343
  }),
3189
3344
  check("git repo", () => {
3190
- execFileSync8("git", ["rev-parse", "--is-inside-work-tree"], {
3345
+ execFileSync9("git", ["rev-parse", "--is-inside-work-tree"], {
3191
3346
  encoding: "utf-8",
3192
3347
  timeout: 5e3,
3193
3348
  stdio: ["pipe", "pipe", "pipe"]
3194
3349
  });
3195
3350
  }),
3196
3351
  check("pnpm", () => {
3197
- const v = execFileSync8("pnpm", ["--version"], {
3352
+ const v = execFileSync9("pnpm", ["--version"], {
3198
3353
  encoding: "utf-8",
3199
3354
  timeout: 5e3,
3200
3355
  stdio: ["pipe", "pipe", "pipe"]
@@ -3202,7 +3357,7 @@ function runPreflight() {
3202
3357
  return v;
3203
3358
  }),
3204
3359
  check("node >= 18", () => {
3205
- const v = execFileSync8("node", ["--version"], {
3360
+ const v = execFileSync9("node", ["--version"], {
3206
3361
  encoding: "utf-8",
3207
3362
  timeout: 5e3,
3208
3363
  stdio: ["pipe", "pipe", "pipe"]
@@ -3212,7 +3367,7 @@ function runPreflight() {
3212
3367
  return v;
3213
3368
  }),
3214
3369
  check("gh CLI", () => {
3215
- const v = execFileSync8("gh", ["--version"], {
3370
+ const v = execFileSync9("gh", ["--version"], {
3216
3371
  encoding: "utf-8",
3217
3372
  timeout: 5e3,
3218
3373
  stdio: ["pipe", "pipe", "pipe"]
@@ -3220,7 +3375,7 @@ function runPreflight() {
3220
3375
  return v;
3221
3376
  }),
3222
3377
  check("package.json", () => {
3223
- if (!fs16.existsSync("package.json")) throw new Error("not found");
3378
+ if (!fs18.existsSync("package.json")) throw new Error("not found");
3224
3379
  })
3225
3380
  ];
3226
3381
  const failed = checks.filter((c) => !c.ok);
@@ -3240,140 +3395,6 @@ var init_preflight = __esm({
3240
3395
  }
3241
3396
  });
3242
3397
 
3243
- // src/cli/task-resolution.ts
3244
- import * as fs17 from "fs";
3245
- import * as path16 from "path";
3246
- import { execFileSync as execFileSync9 } from "child_process";
3247
- function findLatestTaskForIssue(issueNumber, projectDir) {
3248
- const tasksDir = path16.join(projectDir, ".kody", "tasks");
3249
- if (!fs17.existsSync(tasksDir)) return null;
3250
- const allDirs = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
3251
- const prefix = `${issueNumber}-`;
3252
- const direct = allDirs.find((d) => d.startsWith(prefix));
3253
- if (direct) return direct;
3254
- try {
3255
- const branch = execFileSync9("git", ["branch", "--show-current"], {
3256
- encoding: "utf-8",
3257
- cwd: projectDir,
3258
- timeout: 5e3,
3259
- stdio: ["pipe", "pipe", "pipe"]
3260
- }).trim();
3261
- const branchIssueMatch = branch.match(/^(\d+)-/);
3262
- if (branchIssueMatch) {
3263
- const branchIssueNum = branchIssueMatch[1];
3264
- const branchPrefix = `${branchIssueNum}-`;
3265
- const fromBranch = allDirs.find((d) => d.startsWith(branchPrefix));
3266
- if (fromBranch) return fromBranch;
3267
- }
3268
- } catch {
3269
- }
3270
- return null;
3271
- }
3272
- function generateTaskId() {
3273
- const now = /* @__PURE__ */ new Date();
3274
- const pad = (n) => String(n).padStart(2, "0");
3275
- return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
3276
- }
3277
- var init_task_resolution = __esm({
3278
- "src/cli/task-resolution.ts"() {
3279
- "use strict";
3280
- }
3281
- });
3282
-
3283
- // src/review-standalone.ts
3284
- import * as fs18 from "fs";
3285
- import * as path17 from "path";
3286
- function resolveReviewTarget(input) {
3287
- if (input.prs.length === 0) {
3288
- return {
3289
- action: "none",
3290
- message: `Issue #${input.issueNumber} has no open PRs. Nothing to review.`
3291
- };
3292
- }
3293
- if (input.prs.length === 1) {
3294
- return { action: "review", prNumber: input.prs[0].number };
3295
- }
3296
- const prList = input.prs.map((pr) => ` - #${pr.number}: ${pr.title}`).join("\n");
3297
- return {
3298
- action: "pick",
3299
- prs: input.prs,
3300
- message: `\u26A0\uFE0F Issue #${input.issueNumber} has ${input.prs.length} open PRs:
3301
- ${prList}
3302
-
3303
- Run: \`pnpm kody review --pr-number <n>\`
3304
- Or comment on the specific PR: \`@kody review\``
3305
- };
3306
- }
3307
- async function runStandaloneReview(input) {
3308
- const taskId = input.taskId ?? `review-${generateTaskId()}`;
3309
- const taskDir = path17.join(input.projectDir, ".kody", "tasks", taskId);
3310
- fs18.mkdirSync(taskDir, { recursive: true });
3311
- const taskContent = `# ${input.prTitle}
3312
-
3313
- ${input.prBody ?? ""}`;
3314
- fs18.writeFileSync(path17.join(taskDir, "task.md"), taskContent);
3315
- const reviewDef = STAGES.find((s) => s.name === "review");
3316
- const ctx = {
3317
- taskId,
3318
- taskDir,
3319
- projectDir: input.projectDir,
3320
- runners: input.runners,
3321
- sessions: {},
3322
- input: {
3323
- mode: "full",
3324
- local: input.local
3325
- }
3326
- };
3327
- logger.info(`[review] standalone review for: ${input.prTitle}`);
3328
- const result = await executeAgentStage(ctx, reviewDef);
3329
- if (result.outcome !== "completed") {
3330
- return {
3331
- outcome: "failed",
3332
- taskDir,
3333
- error: result.error ?? "Review stage failed"
3334
- };
3335
- }
3336
- const reviewPath = path17.join(taskDir, "review.md");
3337
- let reviewContent;
3338
- if (fs18.existsSync(reviewPath)) {
3339
- reviewContent = fs18.readFileSync(reviewPath, "utf-8");
3340
- }
3341
- return {
3342
- outcome: "completed",
3343
- reviewContent,
3344
- taskDir
3345
- };
3346
- }
3347
- function detectReviewVerdict(reviewContent) {
3348
- const verdictMatch = reviewContent.match(/##\s*Verdict:\s*(PASS|FAIL)/i);
3349
- if (verdictMatch) {
3350
- return verdictMatch[1].toLowerCase();
3351
- }
3352
- const hasCritical = /###\s*Critical\s*\n(?!None\.)/i.test(reviewContent);
3353
- const hasMajor = /###\s*Major\s*\n(?!None\.)/i.test(reviewContent);
3354
- if (hasCritical || hasMajor) return "fail";
3355
- return "pass";
3356
- }
3357
- function formatReviewComment(reviewContent, taskId) {
3358
- const verdict = detectReviewVerdict(reviewContent);
3359
- const cta = verdict === "fail" ? "\n\n> To fix these issues, comment: `@kody fix`\n> The review findings will be used automatically as context." : "";
3360
- return `## \u{1F50D} Kody Review (\`${taskId}\`)
3361
-
3362
- ${reviewContent}${cta}
3363
-
3364
- ---
3365
- \u{1F916} Generated by Kody`;
3366
- }
3367
- var init_review_standalone = __esm({
3368
- "src/review-standalone.ts"() {
3369
- "use strict";
3370
- init_definitions();
3371
- init_agent();
3372
- init_task_resolution();
3373
- init_logger();
3374
- }
3375
- });
3376
-
3377
3398
  // src/cli/args.ts
3378
3399
  function getArg(args2, flag) {
3379
3400
  const idx = args2.indexOf(flag);
@@ -3938,7 +3959,6 @@ async function main() {
3938
3959
  console.log(result.reviewContent);
3939
3960
  if (!input.local && prNumber) {
3940
3961
  const comment = formatReviewComment(result.reviewContent, taskId);
3941
- postPRComment(prNumber, comment);
3942
3962
  const verdict = detectReviewVerdict(result.reviewContent);
3943
3963
  if (verdict === "fail") {
3944
3964
  submitPRReview(prNumber, comment, "request-changes");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.81",
3
+ "version": "0.1.82",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",