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

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 +158 -9
  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();
@@ -4170,7 +4201,7 @@ function ghComment(issueNumber, body, cwd) {
4170
4201
  } catch {
4171
4202
  }
4172
4203
  }
4173
- function bootstrapCommand() {
4204
+ function bootstrapCommand(opts = { force: false }) {
4174
4205
  const cwd = process.cwd();
4175
4206
  const issueNumber = parseInt(process.env.ISSUE_NUMBER ?? "", 10) || 0;
4176
4207
  console.log(`
@@ -4244,6 +4275,23 @@ ${existingFiles.join(", ")}
4244
4275
  fs22.mkdirSync(memoryDir, { recursive: true });
4245
4276
  const archPath = path21.join(memoryDir, "architecture.md");
4246
4277
  const conventionsPath = path21.join(memoryDir, "conventions.md");
4278
+ const existingArch = fs22.existsSync(archPath) ? fs22.readFileSync(archPath, "utf-8") : "";
4279
+ const existingConv = fs22.existsSync(conventionsPath) ? fs22.readFileSync(conventionsPath, "utf-8") : "";
4280
+ const hasExisting = !!(existingArch || existingConv);
4281
+ const extendInstruction = hasExisting && !opts.force ? `
4282
+ ## Existing Documentation (EXTEND, do not replace)
4283
+ You are UPDATING existing documentation. Follow these rules strictly:
4284
+ - PRESERVE all existing sections and content that are still accurate
4285
+ - REMOVE only lines that reference files, patterns, or dependencies that no longer exist in the project
4286
+ - APPEND new sections or lines for newly discovered patterns, files, or conventions
4287
+ - Do NOT rewrite sections that are still correct \u2014 keep them verbatim
4288
+
4289
+ ### Existing architecture.md:
4290
+ ${existingArch}
4291
+
4292
+ ### Existing conventions.md:
4293
+ ${existingConv}
4294
+ ` : "";
4247
4295
  const memoryPrompt = `You are analyzing a project to generate documentation for an autonomous SDLC pipeline.
4248
4296
 
4249
4297
  Given this project context, output ONLY a JSON object with EXACTLY this structure:
@@ -4263,7 +4311,7 @@ Rules for conventions (markdown string):
4263
4311
  - Extract actual patterns from the project
4264
4312
  - If CLAUDE.md exists, reference it
4265
4313
  - Keep under 30 lines
4266
-
4314
+ ${extendInstruction}
4267
4315
  Output ONLY valid JSON. No markdown fences. No explanation.
4268
4316
 
4269
4317
  ${repoContext}`;
@@ -4321,11 +4369,16 @@ ${detected.join("\n")}
4321
4369
  console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
4322
4370
  continue;
4323
4371
  }
4372
+ const stepOutputPath = path21.join(stepsDir, `${stage}.md`);
4373
+ if (fs22.existsSync(stepOutputPath) && !opts.force) {
4374
+ console.log(` \u25CB ${stage}.md \u2014 already exists (use --force to regenerate)`);
4375
+ continue;
4376
+ }
4324
4377
  const defaultPrompt = fs22.readFileSync(templatePath, "utf-8");
4325
4378
  const contextPlaceholder = "{{TASK_CONTEXT}}";
4326
4379
  const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
4327
4380
  if (placeholderIdx === -1) {
4328
- fs22.copyFileSync(templatePath, path21.join(stepsDir, `${stage}.md`));
4381
+ fs22.copyFileSync(templatePath, stepOutputPath);
4329
4382
  stepCount++;
4330
4383
  console.log(` \u2713 ${stage}.md`);
4331
4384
  continue;
@@ -4382,12 +4435,12 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
4382
4435
  let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
4383
4436
  cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
4384
4437
  const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
4385
- fs22.writeFileSync(path21.join(stepsDir, `${stage}.md`), finalPrompt);
4438
+ fs22.writeFileSync(stepOutputPath, finalPrompt);
4386
4439
  stepCount++;
4387
4440
  console.log(` \u2713 ${stage}.md`);
4388
4441
  } catch {
4389
4442
  console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
4390
- fs22.copyFileSync(templatePath, path21.join(stepsDir, `${stage}.md`));
4443
+ fs22.copyFileSync(templatePath, stepOutputPath);
4391
4444
  stepCount++;
4392
4445
  }
4393
4446
  }
@@ -4462,11 +4515,17 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
4462
4515
  } catch {
4463
4516
  console.log(" \u25CB Label creation skipped");
4464
4517
  }
4518
+ console.log("\n\u2500\u2500 Skills \u2500\u2500");
4519
+ const installedSkillPaths = installSkillsForProject(cwd);
4465
4520
  console.log("\n\u2500\u2500 Git \u2500\u2500");
4466
4521
  const filesToCommit = [
4467
4522
  ".kody/memory/architecture.md",
4468
- ".kody/memory/conventions.md"
4523
+ ".kody/memory/conventions.md",
4524
+ ...installedSkillPaths
4469
4525
  ].filter((f) => fs22.existsSync(path21.join(cwd, f)));
4526
+ if (fs22.existsSync(path21.join(cwd, "skills-lock.json"))) {
4527
+ filesToCommit.push("skills-lock.json");
4528
+ }
4470
4529
  for (const stage of STEP_STAGES) {
4471
4530
  const stepFile = `.kody/steps/${stage}.md`;
4472
4531
  if (fs22.existsSync(path21.join(cwd, stepFile))) {
@@ -4599,12 +4658,101 @@ function detectArchitectureBasic(cwd) {
4599
4658
  }
4600
4659
  return detected;
4601
4660
  }
4661
+ var SKILL_MAPPINGS = [
4662
+ {
4663
+ detect: (deps) => "next" in deps,
4664
+ skills: [
4665
+ { package: "vercel-labs/agent-skills@vercel-react-best-practices", label: "React best practices (Vercel)" }
4666
+ ]
4667
+ },
4668
+ {
4669
+ detect: (deps) => "react" in deps && !("next" in deps),
4670
+ skills: [
4671
+ { package: "vercel-labs/agent-skills@vercel-react-best-practices", label: "React best practices (Vercel)" }
4672
+ ]
4673
+ },
4674
+ {
4675
+ detect: (deps) => FRONTEND_DEPS.some((d) => d in deps),
4676
+ skills: [
4677
+ { package: "microsoft/playwright-cli@playwright-cli", label: "Playwright browser automation" }
4678
+ ]
4679
+ }
4680
+ ];
4681
+ function detectSkillsForProject(cwd) {
4682
+ const pkgPath = path21.join(cwd, "package.json");
4683
+ if (!fs22.existsSync(pkgPath)) return [];
4684
+ try {
4685
+ const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
4686
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
4687
+ const seen = /* @__PURE__ */ new Set();
4688
+ const skills = [];
4689
+ for (const mapping of SKILL_MAPPINGS) {
4690
+ if (mapping.detect(allDeps)) {
4691
+ for (const skill of mapping.skills) {
4692
+ if (!seen.has(skill.package)) {
4693
+ seen.add(skill.package);
4694
+ skills.push(skill);
4695
+ }
4696
+ }
4697
+ }
4698
+ }
4699
+ return skills;
4700
+ } catch {
4701
+ return [];
4702
+ }
4703
+ }
4704
+ function installSkillsForProject(cwd) {
4705
+ const skills = detectSkillsForProject(cwd);
4706
+ if (skills.length === 0) {
4707
+ console.log(" \u25CB No skills to install (no frontend framework detected)");
4708
+ return [];
4709
+ }
4710
+ let installedSkills = {};
4711
+ const lockPath = path21.join(cwd, "skills-lock.json");
4712
+ if (fs22.existsSync(lockPath)) {
4713
+ try {
4714
+ const lock = JSON.parse(fs22.readFileSync(lockPath, "utf-8"));
4715
+ installedSkills = lock.skills ?? {};
4716
+ } catch {
4717
+ }
4718
+ }
4719
+ const installedPaths = [];
4720
+ for (const skill of skills) {
4721
+ const skillName = skill.package.split("@").pop() ?? "";
4722
+ if (skillName in installedSkills) {
4723
+ console.log(` \u25CB ${skill.label} \u2014 already installed`);
4724
+ const agentPath = `.agents/skills/${skillName}`;
4725
+ const claudePath = `.claude/skills/${skillName}`;
4726
+ if (fs22.existsSync(path21.join(cwd, agentPath))) installedPaths.push(agentPath);
4727
+ if (fs22.existsSync(path21.join(cwd, claudePath))) installedPaths.push(claudePath);
4728
+ continue;
4729
+ }
4730
+ try {
4731
+ console.log(` Installing: ${skill.label} (${skill.package})`);
4732
+ execFileSync11("npx", ["skills", "add", skill.package, "--yes"], {
4733
+ cwd,
4734
+ encoding: "utf-8",
4735
+ timeout: 6e4,
4736
+ stdio: ["pipe", "pipe", "pipe"]
4737
+ });
4738
+ const skillName2 = skill.package.split("@").pop() ?? "";
4739
+ const agentPath = `.agents/skills/${skillName2}`;
4740
+ const claudePath = `.claude/skills/${skillName2}`;
4741
+ if (fs22.existsSync(path21.join(cwd, agentPath))) installedPaths.push(agentPath);
4742
+ if (fs22.existsSync(path21.join(cwd, claudePath))) installedPaths.push(claudePath);
4743
+ console.log(` \u2713 ${skill.label}`);
4744
+ } catch (err) {
4745
+ console.log(` \u2717 ${skill.label} \u2014 failed to install`);
4746
+ }
4747
+ }
4748
+ return installedPaths;
4749
+ }
4602
4750
  var args = process.argv.slice(2);
4603
4751
  var command = args[0];
4604
4752
  if (command === "init") {
4605
4753
  initCommand({ force: args.includes("--force") });
4606
4754
  } else if (command === "bootstrap") {
4607
- bootstrapCommand();
4755
+ bootstrapCommand({ force: args.includes("--force") });
4608
4756
  } else if (command === "version" || command === "--version" || command === "-v") {
4609
4757
  console.log(getVersion());
4610
4758
  } else {
@@ -4618,5 +4766,6 @@ export {
4618
4766
  checkGhRepoAccess,
4619
4767
  checkGhSecret,
4620
4768
  detectArchitectureBasic,
4621
- detectBasicConfig
4769
+ detectBasicConfig,
4770
+ detectSkillsForProject
4622
4771
  };
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.67",
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;