@kody-ade/kody-engine-lite 0.1.116 → 0.1.118

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 +46 -27
  2. package/package.json +2 -1
  3. package/prompts/taskify-ticket.md +10 -0
  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
@@ -1257,8 +1257,8 @@ async function runTaskifyCommand() {
1257
1257
  const local = hasFlag(args2, "--local") || !process.env.CI;
1258
1258
  const taskIdArg = getArg(args2, "--task-id") ?? process.env.TASK_ID;
1259
1259
  const taskId = taskIdArg ?? (issueNumber ? `taskify-${issueNumber}-${generateTaskId()}` : `taskify-${generateTaskId()}`);
1260
- if (!ticketId && !prdFile) {
1261
- logger.error("Usage: kody taskify --ticket <ticket-id> OR kody taskify --file <prd.md>");
1260
+ if (!ticketId && !prdFile && !issueNumber) {
1261
+ logger.error("Usage: kody taskify --ticket <ticket-id> OR kody taskify --file <prd.md> OR kody taskify --issue-number <n>");
1262
1262
  process.exit(1);
1263
1263
  }
1264
1264
  if (prdFile && !fs11.existsSync(prdFile)) {
@@ -1304,8 +1304,8 @@ async function taskifyCommand(opts) {
1304
1304
  const config = getProjectConfig();
1305
1305
  const taskDir = path10.join(projectDir, ".kody", "tasks", taskId);
1306
1306
  fs11.mkdirSync(taskDir, { recursive: true });
1307
- const mode = prdFile ? "file" : "ticket";
1308
- logger.info(`[taskify] mode=${mode} source=${ticketId ?? prdFile} issue=${issueNumber ?? "none"} task=${taskId}`);
1307
+ const mode = prdFile ? "file" : ticketId ? "ticket" : "issue";
1308
+ logger.info(`[taskify] mode=${mode} source=${ticketId ?? prdFile ?? `issue#${issueNumber}`} issue=${issueNumber ?? "none"} task=${taskId}`);
1309
1309
  let mcpConfigJson;
1310
1310
  if (mode === "ticket") {
1311
1311
  try {
@@ -1328,6 +1328,18 @@ Add the required MCP server config to \`kody.config.json\` and try again.`
1328
1328
  const sc = resolveStageConfig(config, "taskify", "strong");
1329
1329
  const model = sc.model;
1330
1330
  const fileContent = prdFile ? fs11.readFileSync(prdFile, "utf-8") : void 0;
1331
+ let issueBody;
1332
+ if (mode === "issue" && issueNumber) {
1333
+ const issue = getIssue(issueNumber);
1334
+ if (issue) {
1335
+ issueBody = `# ${issue.title}
1336
+
1337
+ ${issue.body}`;
1338
+ logger.info(` Fetched issue #${issueNumber} body (${issueBody.length} chars)`);
1339
+ } else {
1340
+ throw new TaskifyError(`Could not fetch issue #${issueNumber}`);
1341
+ }
1342
+ }
1331
1343
  let projectContext;
1332
1344
  {
1333
1345
  const parts = [];
@@ -1351,9 +1363,9 @@ ${lines.join("\n")}
1351
1363
  }
1352
1364
  if (parts.length > 0) projectContext = parts.join("\n\n");
1353
1365
  }
1354
- const prompt = buildPrompt({ ticketId, fileContent, taskDir, feedback, projectContext });
1366
+ const prompt = buildPrompt({ ticketId, fileContent, issueBody, taskDir, feedback, projectContext });
1355
1367
  if (issueNumber && !local) {
1356
- const src = mode === "file" ? `file \`${path10.basename(prdFile)}\`` : `ticket **${ticketId}**`;
1368
+ const src = mode === "file" ? `file \`${path10.basename(prdFile)}\`` : mode === "ticket" ? `ticket **${ticketId}**` : `issue #${issueNumber} description`;
1357
1369
  const runUrl = process.env.RUN_URL ? ` ([logs](${process.env.RUN_URL}))` : "";
1358
1370
  postComment(issueNumber, `\u{1F680} Kody pipeline started: \`${taskId}\`${runUrl}
1359
1371
 
@@ -1402,7 +1414,7 @@ ${errMsg}`);
1402
1414
  }
1403
1415
  throw new TaskifyError(errMsg);
1404
1416
  }
1405
- const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : "spec");
1417
+ const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : issueNumber ? `issue #${issueNumber}` : "spec");
1406
1418
  if (parsed.status === "questions") {
1407
1419
  handleQuestions(parsed, sourceLabel, issueNumber, local ?? false);
1408
1420
  } else if (parsed.status === "ready") {
@@ -1499,7 +1511,7 @@ ${links}${triggerNote}`
1499
1511
  }
1500
1512
  }
1501
1513
  function buildPrompt(opts) {
1502
- const { ticketId, fileContent, taskDir, feedback, projectContext } = opts;
1514
+ const { ticketId, fileContent, issueBody, taskDir, feedback, projectContext } = opts;
1503
1515
  const scriptDir = new URL(".", import.meta.url).pathname;
1504
1516
  const candidates = [
1505
1517
  path10.resolve(scriptDir, "..", "prompts", "taskify-ticket.md"),
@@ -1528,6 +1540,7 @@ function buildPrompt(opts) {
1528
1540
  resolveBlock("PROJECT_CONTEXT", projectContext);
1529
1541
  resolveBlock("TICKET_ID", ticketId);
1530
1542
  resolveBlock("FILE_CONTENT", fileContent);
1543
+ resolveBlock("ISSUE_BODY", issueBody);
1531
1544
  resolveBlock("FEEDBACK", feedback);
1532
1545
  template = template.replace(/\{\{TASK_DIR\}\}/g, taskDir);
1533
1546
  return template;
@@ -2860,23 +2873,6 @@ function parseCommentInputs() {
2860
2873
  }
2861
2874
  const modesWithoutTaskId = ["fix", "fix-ci", "status", "review", "resolve", "rerun"];
2862
2875
  const valid = !!taskId || modesWithoutTaskId.includes(mode);
2863
- if (mode === "taskify" && !ticketId && !prdFile) {
2864
- return {
2865
- task_id: taskId,
2866
- mode,
2867
- from_stage: fromStage,
2868
- issue_number: issueNumber,
2869
- pr_number: "",
2870
- feedback,
2871
- complexity,
2872
- ci_run_id: ciRunId,
2873
- ticket_id: "",
2874
- prd_file: "",
2875
- dry_run: dryRun,
2876
- valid: false,
2877
- trigger_type: "comment"
2878
- };
2879
- }
2880
2876
  return {
2881
2877
  task_id: taskId,
2882
2878
  mode,
@@ -4227,8 +4223,8 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
4227
4223
  "diagnosis",
4228
4224
  prompt,
4229
4225
  model,
4230
- 3e4,
4231
- // 30s timeout — this should be fast
4226
+ 9e4,
4227
+ // 90s timeout — MiniMax can be slow to respond
4232
4228
  "",
4233
4229
  options
4234
4230
  );
@@ -4260,6 +4256,29 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
4260
4256
  } catch (err) {
4261
4257
  logger.warn(` Diagnosis error: ${err instanceof Error ? err.message : err}`);
4262
4258
  }
4259
+ if (modifiedFiles.length > 0) {
4260
+ const errorLines = errorOutput.split("\n").filter(
4261
+ (l) => /error|Error|ERROR|failed|Failed|FAIL/i.test(l)
4262
+ );
4263
+ const errorFilePaths = errorLines.flatMap((line) => {
4264
+ const matches = line.match(/src\/[^\s(:]+\.[a-z]+/g);
4265
+ return matches ?? [];
4266
+ });
4267
+ if (errorFilePaths.length > 0) {
4268
+ const modifiedSet = new Set(modifiedFiles);
4269
+ const allPreExisting = errorFilePaths.every(
4270
+ (f) => !modifiedSet.has(f) && !modifiedFiles.some((m) => m.endsWith(f))
4271
+ );
4272
+ if (allPreExisting) {
4273
+ logger.warn(" Diagnosis fallback: all errors in unmodified files \u2192 pre-existing");
4274
+ return {
4275
+ classification: "pre-existing",
4276
+ reason: "All errors are in files not modified by the build stage",
4277
+ resolution: `The following files have pre-existing errors not introduced by this task: ${[...new Set(errorFilePaths)].join(", ")}`
4278
+ };
4279
+ }
4280
+ }
4281
+ }
4263
4282
  logger.warn(" Diagnosis failed \u2014 defaulting to retry");
4264
4283
  return {
4265
4284
  classification: "retry",
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.116",
3
+
4
+ "version": "0.1.118",
4
5
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
6
  "license": "MIT",
6
7
  "type": "module",
@@ -19,6 +19,16 @@ The product spec is provided below:
19
19
  ```
20
20
  {{/if}}
21
21
 
22
+ {{#if ISSUE_BODY}}
23
+ **Mode: issue**
24
+
25
+ The task description from the GitHub issue is provided below. Decompose it into scoped, independently implementable sub-tasks.
26
+
27
+ ```
28
+ {{ISSUE_BODY}}
29
+ ```
30
+ {{/if}}
31
+
22
32
  {{#if PROJECT_CONTEXT}}
23
33
  ## Existing codebase
24
34
 
@@ -1,4 +0,0 @@
1
- import type { AgentRunner } from "./types.js";
2
- import type { KodyConfig } from "./config.js";
3
- export declare function createClaudeCodeRunner(): AgentRunner;
4
- export declare function createRunners(config: KodyConfig): Record<string, AgentRunner>;
@@ -1,122 +0,0 @@
1
- import { spawn, execFileSync } from "child_process";
2
- const SIGKILL_GRACE_MS = 5000;
3
- const STDERR_TAIL_CHARS = 500;
4
- function writeStdin(child, prompt) {
5
- return new Promise((resolve, reject) => {
6
- if (!child.stdin) {
7
- resolve();
8
- return;
9
- }
10
- child.stdin.write(prompt, (err) => {
11
- if (err)
12
- reject(err);
13
- else {
14
- child.stdin.end();
15
- resolve();
16
- }
17
- });
18
- });
19
- }
20
- function waitForProcess(child, timeout) {
21
- return new Promise((resolve) => {
22
- const stdoutChunks = [];
23
- const stderrChunks = [];
24
- child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
25
- child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
26
- const timer = setTimeout(() => {
27
- child.kill("SIGTERM");
28
- setTimeout(() => {
29
- if (!child.killed)
30
- child.kill("SIGKILL");
31
- }, SIGKILL_GRACE_MS);
32
- }, timeout);
33
- child.on("exit", (code) => {
34
- clearTimeout(timer);
35
- resolve({
36
- code,
37
- stdout: Buffer.concat(stdoutChunks).toString(),
38
- stderr: Buffer.concat(stderrChunks).toString(),
39
- });
40
- });
41
- child.on("error", (err) => {
42
- clearTimeout(timer);
43
- resolve({ code: -1, stdout: "", stderr: err.message });
44
- });
45
- });
46
- }
47
- async function runSubprocess(command, args, prompt, timeout, options) {
48
- const child = spawn(command, args, {
49
- cwd: options?.cwd ?? process.cwd(),
50
- env: {
51
- ...process.env,
52
- SKIP_BUILD: "1",
53
- SKIP_HOOKS: "1",
54
- ...options?.env,
55
- },
56
- stdio: ["pipe", "pipe", "pipe"],
57
- });
58
- try {
59
- await writeStdin(child, prompt);
60
- }
61
- catch (err) {
62
- return {
63
- outcome: "failed",
64
- error: `Failed to send prompt: ${err instanceof Error ? err.message : String(err)}`,
65
- };
66
- }
67
- const { code, stdout, stderr } = await waitForProcess(child, timeout);
68
- if (code === 0) {
69
- return { outcome: "completed", output: stdout };
70
- }
71
- return {
72
- outcome: code === null ? "timed_out" : "failed",
73
- error: `Exit code ${code}\n${stderr.slice(-STDERR_TAIL_CHARS)}`,
74
- };
75
- }
76
- function checkCommand(command, args) {
77
- try {
78
- execFileSync(command, args, { timeout: 10_000, stdio: "pipe" });
79
- return true;
80
- }
81
- catch {
82
- return false;
83
- }
84
- }
85
- // ─── Claude Code Runner ──────────────────────────────────────────────────────
86
- export function createClaudeCodeRunner() {
87
- return {
88
- async run(_stageName, prompt, model, timeout, _taskDir, options) {
89
- return runSubprocess("claude", [
90
- "--print",
91
- "--model", model,
92
- "--dangerously-skip-permissions",
93
- "--allowedTools", "Bash,Edit,Read,Write,Glob,Grep",
94
- ], prompt, timeout, options);
95
- },
96
- async healthCheck() {
97
- return checkCommand("claude", ["--version"]);
98
- },
99
- };
100
- }
101
- // ─── Runner Factory ──────────────────────────────────────────────────────────
102
- const RUNNER_FACTORIES = {
103
- "claude-code": createClaudeCodeRunner,
104
- };
105
- export function createRunners(config) {
106
- // New multi-runner config
107
- if (config.agent.runners && Object.keys(config.agent.runners).length > 0) {
108
- const runners = {};
109
- for (const [name, runnerConfig] of Object.entries(config.agent.runners)) {
110
- const factory = RUNNER_FACTORIES[runnerConfig.type];
111
- if (factory) {
112
- runners[name] = factory();
113
- }
114
- }
115
- return runners;
116
- }
117
- // Legacy single-runner fallback
118
- const runnerType = config.agent.runner ?? "claude-code";
119
- const factory = RUNNER_FACTORIES[runnerType];
120
- const defaultName = config.agent.defaultRunner ?? "claude";
121
- return { [defaultName]: factory ? factory() : createClaudeCodeRunner() };
122
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Parses @kody / /kody comment body into structured inputs.
3
- * Run by the parse job in GitHub Actions.
4
- * Reads from env, writes to $GITHUB_OUTPUT.
5
- */
6
- export {};
@@ -1,76 +0,0 @@
1
- /**
2
- * Parses @kody / /kody comment body into structured inputs.
3
- * Run by the parse job in GitHub Actions.
4
- * Reads from env, writes to $GITHUB_OUTPUT.
5
- */
6
- import * as fs from "fs";
7
- const outputFile = process.env.GITHUB_OUTPUT;
8
- const triggerType = process.env.TRIGGER_TYPE ?? "dispatch";
9
- function output(key, value) {
10
- if (outputFile) {
11
- fs.appendFileSync(outputFile, `${key}=${value}\n`);
12
- }
13
- console.log(`${key}=${value}`);
14
- }
15
- // For workflow_dispatch, pass through inputs
16
- if (triggerType === "dispatch") {
17
- output("task_id", process.env.INPUT_TASK_ID ?? "");
18
- output("mode", process.env.INPUT_MODE ?? "full");
19
- output("from_stage", process.env.INPUT_FROM_STAGE ?? "");
20
- output("issue_number", process.env.INPUT_ISSUE_NUMBER ?? "");
21
- output("feedback", process.env.INPUT_FEEDBACK ?? "");
22
- output("valid", process.env.INPUT_TASK_ID ? "true" : "false");
23
- output("trigger_type", "dispatch");
24
- process.exit(0);
25
- }
26
- // For issue_comment, parse the comment body
27
- const commentBody = process.env.COMMENT_BODY ?? "";
28
- const issueNumber = process.env.ISSUE_NUMBER ?? "";
29
- // Match: @kody [mode] [task-id] [--from stage] [--feedback "text"]
30
- const kodyMatch = commentBody.match(/(?:@kody|\/kody)\s*(.*)/i);
31
- if (!kodyMatch) {
32
- output("valid", "false");
33
- output("trigger_type", "comment");
34
- process.exit(0);
35
- }
36
- const parts = kodyMatch[1].trim().split(/\s+/);
37
- const validModes = ["full", "rerun", "status"];
38
- let mode = "full";
39
- let taskId = "";
40
- let fromStage = "";
41
- let feedback = "";
42
- let i = 0;
43
- // First arg: mode or task-id
44
- if (parts[i] && validModes.includes(parts[i])) {
45
- mode = parts[i];
46
- i++;
47
- }
48
- // Second arg: task-id
49
- if (parts[i] && !parts[i].startsWith("--")) {
50
- taskId = parts[i];
51
- i++;
52
- }
53
- // Named args
54
- while (i < parts.length) {
55
- if (parts[i] === "--from" && parts[i + 1]) {
56
- fromStage = parts[i + 1];
57
- i += 2;
58
- }
59
- else if (parts[i] === "--feedback" && parts[i + 1]) {
60
- // Collect quoted feedback
61
- const rest = parts.slice(i + 1).join(" ");
62
- const quoted = rest.match(/^"([^"]*)"/);
63
- feedback = quoted ? quoted[1] : parts[i + 1];
64
- break;
65
- }
66
- else {
67
- i++;
68
- }
69
- }
70
- output("task_id", taskId);
71
- output("mode", mode);
72
- output("from_stage", fromStage);
73
- output("issue_number", issueNumber);
74
- output("feedback", feedback);
75
- output("valid", taskId ? "true" : "false");
76
- output("trigger_type", "comment");
@@ -1,6 +0,0 @@
1
- /**
2
- * Validates that a comment trigger is safe to execute.
3
- * Run by the parse job in GitHub Actions.
4
- * Reads from env, writes to $GITHUB_OUTPUT.
5
- */
6
- export {};
@@ -1,22 +0,0 @@
1
- /**
2
- * Validates that a comment trigger is safe to execute.
3
- * Run by the parse job in GitHub Actions.
4
- * Reads from env, writes to $GITHUB_OUTPUT.
5
- */
6
- import * as fs from "fs";
7
- const ALLOWED_ASSOCIATIONS = ["COLLABORATOR", "MEMBER", "OWNER"];
8
- const association = process.env.COMMENT_AUTHOR_ASSOCIATION ?? "";
9
- const outputFile = process.env.GITHUB_OUTPUT;
10
- function output(key, value) {
11
- if (outputFile) {
12
- fs.appendFileSync(outputFile, `${key}=${value}\n`);
13
- }
14
- console.log(`${key}=${value}`);
15
- }
16
- if (!ALLOWED_ASSOCIATIONS.includes(association)) {
17
- output("valid", "false");
18
- output("reason", `Author association '${association}' not in allowlist: ${ALLOWED_ASSOCIATIONS.join(", ")}`);
19
- process.exit(0);
20
- }
21
- output("valid", "true");
22
- output("reason", "");
@@ -1,13 +0,0 @@
1
- export interface CliInput {
2
- command: "run" | "rerun" | "fix" | "status";
3
- taskId?: string;
4
- task?: string;
5
- fromStage?: string;
6
- dryRun?: boolean;
7
- cwd?: string;
8
- issueNumber?: number;
9
- feedback?: string;
10
- local?: boolean;
11
- complexity?: "low" | "medium" | "high";
12
- }
13
- export declare function parseArgs(): CliInput;
package/dist/cli/args.js DELETED
@@ -1,42 +0,0 @@
1
- const isCI = !!process.env.GITHUB_ACTIONS;
2
- function getArg(args, flag) {
3
- const idx = args.indexOf(flag);
4
- if (idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith("--")) {
5
- return args[idx + 1];
6
- }
7
- return undefined;
8
- }
9
- function hasFlag(args, flag) {
10
- return args.includes(flag);
11
- }
12
- export function parseArgs() {
13
- const args = process.argv.slice(2);
14
- if (hasFlag(args, "--help") || hasFlag(args, "-h") || args.length === 0) {
15
- console.log(`Usage:
16
- kody run --task-id <id> [--task "<desc>"] [--cwd <path>] [--issue-number <n>] [--complexity low|medium|high] [--feedback "<text>"] [--local] [--dry-run]
17
- kody rerun --task-id <id> --from <stage> [--cwd <path>] [--issue-number <n>]
18
- kody fix --task-id <id> [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
19
- kody status --task-id <id> [--cwd <path>]
20
- kody --help`);
21
- process.exit(0);
22
- }
23
- const command = args[0];
24
- if (!["run", "rerun", "fix", "status"].includes(command)) {
25
- console.error(`Unknown command: ${command}`);
26
- process.exit(1);
27
- }
28
- const issueStr = getArg(args, "--issue-number") ?? process.env.ISSUE_NUMBER;
29
- const localFlag = hasFlag(args, "--local");
30
- return {
31
- command,
32
- taskId: getArg(args, "--task-id") ?? process.env.TASK_ID,
33
- task: getArg(args, "--task"),
34
- fromStage: getArg(args, "--from") ?? process.env.FROM_STAGE,
35
- dryRun: hasFlag(args, "--dry-run") || process.env.DRY_RUN === "true",
36
- cwd: getArg(args, "--cwd"),
37
- issueNumber: issueStr ? parseInt(issueStr, 10) : undefined,
38
- feedback: getArg(args, "--feedback") ?? process.env.FEEDBACK,
39
- local: localFlag || (!isCI && !hasFlag(args, "--no-local")),
40
- complexity: (getArg(args, "--complexity") ?? process.env.COMPLEXITY),
41
- };
42
- }
@@ -1,2 +0,0 @@
1
- export declare function checkLitellmHealth(url: string): Promise<boolean>;
2
- export declare function tryStartLitellm(url: string, projectDir: string): Promise<ReturnType<typeof import("child_process").spawn> | null>;
@@ -1,85 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { execFileSync } from "child_process";
4
- import { logger } from "../logger.js";
5
- export async function checkLitellmHealth(url) {
6
- try {
7
- const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3000) });
8
- return response.ok;
9
- }
10
- catch {
11
- return false;
12
- }
13
- }
14
- export async function tryStartLitellm(url, projectDir) {
15
- const configPath = path.join(projectDir, "litellm-config.yaml");
16
- if (!fs.existsSync(configPath)) {
17
- logger.warn("litellm-config.yaml not found — cannot start proxy");
18
- return null;
19
- }
20
- // Extract port from URL
21
- const portMatch = url.match(/:(\d+)/);
22
- const port = portMatch ? portMatch[1] : "4000";
23
- // Check if litellm is installed
24
- try {
25
- execFileSync("litellm", ["--version"], { timeout: 5000, stdio: "pipe" });
26
- }
27
- catch {
28
- try {
29
- execFileSync("python3", ["-m", "litellm", "--version"], { timeout: 5000, stdio: "pipe" });
30
- }
31
- catch {
32
- logger.warn("litellm not installed (pip install 'litellm[proxy]')");
33
- return null;
34
- }
35
- }
36
- logger.info(`Starting LiteLLM proxy on port ${port}...`);
37
- // Determine command
38
- let cmd;
39
- let args;
40
- try {
41
- execFileSync("litellm", ["--version"], { timeout: 5000, stdio: "pipe" });
42
- cmd = "litellm";
43
- args = ["--config", configPath, "--port", port];
44
- }
45
- catch {
46
- cmd = "python3";
47
- args = ["-m", "litellm", "--config", configPath, "--port", port];
48
- }
49
- // Load API key env vars from project .env (only *_API_KEY patterns)
50
- const dotenvPath = path.join(projectDir, ".env");
51
- const dotenvVars = {};
52
- if (fs.existsSync(dotenvPath)) {
53
- for (const line of fs.readFileSync(dotenvPath, "utf-8").split("\n")) {
54
- const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
55
- if (match)
56
- dotenvVars[match[1]] = match[2];
57
- }
58
- if (Object.keys(dotenvVars).length > 0) {
59
- logger.info(` Loaded API keys: ${Object.keys(dotenvVars).join(", ")}`);
60
- }
61
- }
62
- const { spawn } = await import("child_process");
63
- const child = spawn(cmd, args, {
64
- stdio: ["ignore", "pipe", "pipe"],
65
- detached: true,
66
- env: { ...process.env, ...dotenvVars },
67
- });
68
- // Capture stderr for debugging
69
- let proxyStderr = "";
70
- child.stderr?.on("data", (chunk) => { proxyStderr += chunk.toString(); });
71
- // Wait for health
72
- for (let i = 0; i < 30; i++) {
73
- await new Promise((r) => setTimeout(r, 2000));
74
- if (await checkLitellmHealth(url)) {
75
- logger.info(`LiteLLM proxy ready at ${url}`);
76
- return child;
77
- }
78
- }
79
- if (proxyStderr) {
80
- logger.warn(`LiteLLM stderr: ${proxyStderr.slice(-1000)}`);
81
- }
82
- logger.warn("LiteLLM proxy failed to start within 60s");
83
- child.kill();
84
- return null;
85
- }
@@ -1,2 +0,0 @@
1
- export declare function findLatestTaskForIssue(issueNumber: number, projectDir: string): string | null;
2
- export declare function generateTaskId(): string;
@@ -1,41 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { execFileSync } from "child_process";
4
- export function findLatestTaskForIssue(issueNumber, projectDir) {
5
- const tasksDir = path.join(projectDir, ".tasks");
6
- if (!fs.existsSync(tasksDir))
7
- return null;
8
- // Only consider directories (not files)
9
- const allDirs = fs.readdirSync(tasksDir, { withFileTypes: true })
10
- .filter((d) => d.isDirectory())
11
- .map((d) => d.name)
12
- .sort()
13
- .reverse();
14
- // Direct match: tasks starting with issue number
15
- const prefix = `${issueNumber}-`;
16
- const direct = allDirs.find((d) => d.startsWith(prefix));
17
- if (direct)
18
- return direct;
19
- // Fallback for PR comments: extract issue number from current git branch
20
- // Branch format: <issueNum>--<slug> (e.g., 1031--security-8x-route)
21
- try {
22
- const branch = execFileSync("git", ["branch", "--show-current"], {
23
- encoding: "utf-8", cwd: projectDir, timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
24
- }).trim();
25
- const branchIssueMatch = branch.match(/^(\d+)-/);
26
- if (branchIssueMatch) {
27
- const branchIssueNum = branchIssueMatch[1];
28
- const branchPrefix = `${branchIssueNum}-`;
29
- const fromBranch = allDirs.find((d) => d.startsWith(branchPrefix));
30
- if (fromBranch)
31
- return fromBranch;
32
- }
33
- }
34
- catch { /* ignore */ }
35
- return null;
36
- }
37
- export function generateTaskId() {
38
- const now = new Date();
39
- const pad = (n) => String(n).padStart(2, "0");
40
- return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
41
- }
package/dist/config.d.ts DELETED
@@ -1,49 +0,0 @@
1
- export interface RunnerConfig {
2
- type: "claude-code";
3
- }
4
- export interface KodyConfig {
5
- quality: {
6
- typecheck: string;
7
- lint: string;
8
- lintFix: string;
9
- format: string;
10
- formatFix: string;
11
- testUnit: string;
12
- };
13
- git: {
14
- defaultBranch: string;
15
- userEmail?: string;
16
- userName?: string;
17
- };
18
- github: {
19
- owner: string;
20
- repo: string;
21
- };
22
- paths: {
23
- taskDir: string;
24
- };
25
- agent: {
26
- runner?: string;
27
- modelMap: {
28
- cheap: string;
29
- mid: string;
30
- strong: string;
31
- };
32
- litellmUrl?: string;
33
- usePerStageRouting?: boolean;
34
- defaultRunner?: string;
35
- runners?: Record<string, RunnerConfig>;
36
- stageRunners?: Record<string, string>;
37
- };
38
- }
39
- export declare const SIGKILL_GRACE_MS = 5000;
40
- export declare const MAX_PR_TITLE_LENGTH = 72;
41
- export declare const STDERR_TAIL_CHARS = 500;
42
- export declare const API_TIMEOUT_MS = 30000;
43
- export declare const DEFAULT_MAX_FIX_ATTEMPTS = 2;
44
- export declare const AGENT_RETRY_DELAY_MS = 2000;
45
- export declare const VERIFY_COMMAND_TIMEOUT_MS: number;
46
- export declare const FIX_COMMAND_TIMEOUT_MS: number;
47
- export declare function setConfigDir(dir: string): void;
48
- export declare function getProjectConfig(): KodyConfig;
49
- export declare function resetProjectConfig(): void;