@kody-ade/kody-engine-lite 0.1.65 → 0.1.66

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 +112 -3
  2. package/package.json +1 -1
  3. package/templates/kody.yml +2 -1
  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
@@ -3911,7 +3911,7 @@ function buildConfig(cwd, basic) {
3911
3911
  }
3912
3912
  return "";
3913
3913
  };
3914
- return {
3914
+ const config = {
3915
3915
  "$schema": "https://raw.githubusercontent.com/aharonyaircohen/Kody-Engine-Lite/main/kody.config.schema.json",
3916
3916
  quality: {
3917
3917
  typecheck: find("typecheck", "type-check") || (pkg.devDependencies?.typescript ? `${basic.pm} tsc --noEmit` : ""),
@@ -3927,6 +3927,37 @@ function buildConfig(cwd, basic) {
3927
3927
  modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
3928
3928
  }
3929
3929
  };
3930
+ const mcp = detectMcpConfig(cwd, basic.pm, pkg);
3931
+ if (mcp) config.mcp = mcp;
3932
+ return config;
3933
+ }
3934
+ var FRONTEND_DEPS = ["next", "react", "vue", "svelte", "nuxt", "astro", "solid-js", "angular", "@angular/core"];
3935
+ function detectMcpConfig(cwd, pm, pkg) {
3936
+ const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
3937
+ const hasFrontend = FRONTEND_DEPS.some((dep) => dep in allDeps);
3938
+ if (!hasFrontend) return void 0;
3939
+ const scripts = pkg.scripts ?? {};
3940
+ const hasDevScript = !!scripts.dev;
3941
+ const isNext = "next" in allDeps || "nuxt" in allDeps;
3942
+ const isVite = "vite" in allDeps;
3943
+ const defaultPort = isNext ? 3e3 : isVite ? 5173 : 3e3;
3944
+ const mcp = {
3945
+ enabled: true,
3946
+ servers: {
3947
+ playwright: {
3948
+ command: "npx",
3949
+ args: ["@playwright/mcp@latest"]
3950
+ }
3951
+ },
3952
+ stages: ["build", "review"]
3953
+ };
3954
+ if (hasDevScript) {
3955
+ mcp.devServer = {
3956
+ command: `${pm} dev`,
3957
+ url: `http://localhost:${defaultPort}`
3958
+ };
3959
+ }
3960
+ return mcp;
3930
3961
  }
3931
3962
  function initCommand(opts) {
3932
3963
  const cwd = process.cwd();
@@ -4462,11 +4493,17 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
4462
4493
  } catch {
4463
4494
  console.log(" \u25CB Label creation skipped");
4464
4495
  }
4496
+ console.log("\n\u2500\u2500 Skills \u2500\u2500");
4497
+ const installedSkillPaths = installSkillsForProject(cwd);
4465
4498
  console.log("\n\u2500\u2500 Git \u2500\u2500");
4466
4499
  const filesToCommit = [
4467
4500
  ".kody/memory/architecture.md",
4468
- ".kody/memory/conventions.md"
4501
+ ".kody/memory/conventions.md",
4502
+ ...installedSkillPaths
4469
4503
  ].filter((f) => fs22.existsSync(path21.join(cwd, f)));
4504
+ if (fs22.existsSync(path21.join(cwd, "skills-lock.json"))) {
4505
+ filesToCommit.push("skills-lock.json");
4506
+ }
4470
4507
  for (const stage of STEP_STAGES) {
4471
4508
  const stepFile = `.kody/steps/${stage}.md`;
4472
4509
  if (fs22.existsSync(path21.join(cwd, stepFile))) {
@@ -4599,6 +4636,77 @@ function detectArchitectureBasic(cwd) {
4599
4636
  }
4600
4637
  return detected;
4601
4638
  }
4639
+ var SKILL_MAPPINGS = [
4640
+ {
4641
+ detect: (deps) => "next" in deps,
4642
+ skills: [
4643
+ { package: "vercel-labs/agent-skills@vercel-react-best-practices", label: "React best practices (Vercel)" }
4644
+ ]
4645
+ },
4646
+ {
4647
+ detect: (deps) => "react" in deps && !("next" in deps),
4648
+ skills: [
4649
+ { package: "vercel-labs/agent-skills@vercel-react-best-practices", label: "React best practices (Vercel)" }
4650
+ ]
4651
+ },
4652
+ {
4653
+ detect: (deps) => FRONTEND_DEPS.some((d) => d in deps),
4654
+ skills: [
4655
+ { package: "microsoft/playwright-cli@playwright-cli", label: "Playwright browser automation" }
4656
+ ]
4657
+ }
4658
+ ];
4659
+ function detectSkillsForProject(cwd) {
4660
+ const pkgPath = path21.join(cwd, "package.json");
4661
+ if (!fs22.existsSync(pkgPath)) return [];
4662
+ try {
4663
+ const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
4664
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
4665
+ const seen = /* @__PURE__ */ new Set();
4666
+ const skills = [];
4667
+ for (const mapping of SKILL_MAPPINGS) {
4668
+ if (mapping.detect(allDeps)) {
4669
+ for (const skill of mapping.skills) {
4670
+ if (!seen.has(skill.package)) {
4671
+ seen.add(skill.package);
4672
+ skills.push(skill);
4673
+ }
4674
+ }
4675
+ }
4676
+ }
4677
+ return skills;
4678
+ } catch {
4679
+ return [];
4680
+ }
4681
+ }
4682
+ function installSkillsForProject(cwd) {
4683
+ const skills = detectSkillsForProject(cwd);
4684
+ if (skills.length === 0) {
4685
+ console.log(" \u25CB No skills to install (no frontend framework detected)");
4686
+ return [];
4687
+ }
4688
+ const installedPaths = [];
4689
+ for (const skill of skills) {
4690
+ try {
4691
+ console.log(` Installing: ${skill.label} (${skill.package})`);
4692
+ execFileSync11("npx", ["skills", "add", skill.package, "--yes"], {
4693
+ cwd,
4694
+ encoding: "utf-8",
4695
+ timeout: 6e4,
4696
+ stdio: ["pipe", "pipe", "pipe"]
4697
+ });
4698
+ const skillName = skill.package.split("@").pop() ?? "";
4699
+ const agentPath = `.agents/skills/${skillName}`;
4700
+ const claudePath = `.claude/skills/${skillName}`;
4701
+ if (fs22.existsSync(path21.join(cwd, agentPath))) installedPaths.push(agentPath);
4702
+ if (fs22.existsSync(path21.join(cwd, claudePath))) installedPaths.push(claudePath);
4703
+ console.log(` \u2713 ${skill.label}`);
4704
+ } catch (err) {
4705
+ console.log(` \u2717 ${skill.label} \u2014 failed to install`);
4706
+ }
4707
+ }
4708
+ return installedPaths;
4709
+ }
4602
4710
  var args = process.argv.slice(2);
4603
4711
  var command = args[0];
4604
4712
  if (command === "init") {
@@ -4618,5 +4726,6 @@ export {
4618
4726
  checkGhRepoAccess,
4619
4727
  checkGhSecret,
4620
4728
  detectArchitectureBasic,
4621
- detectBasicConfig
4729
+ detectBasicConfig,
4730
+ detectSkillsForProject
4622
4731
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.65",
3
+ "version": "0.1.66",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -295,10 +295,11 @@ jobs:
295
295
  - name: Upload artifacts
296
296
  if: always()
297
297
  uses: actions/upload-artifact@v4
298
+ continue-on-error: true
298
299
  with:
299
300
  name: kody-tasks-${{ github.event.inputs.task_id || needs.parse.outputs.task_id }}
300
301
  path: .kody/tasks/
301
- retention-days: 7
302
+ retention-days: 3
302
303
 
303
304
  # ─── Error Notifications ─────────────────────────────────────────────────────
304
305
  notify-parse-error:
@@ -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;