@kody-ade/kody-engine-lite 0.1.45 → 0.1.46

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 (70) hide show
  1. package/dist/bin/cli.js +173 -2
  2. package/package.json +1 -1
  3. package/dist/agent-runner.d.ts +0 -4
  4. package/dist/agent-runner.js +0 -122
  5. package/dist/ci/parse-inputs.d.ts +0 -6
  6. package/dist/ci/parse-inputs.js +0 -76
  7. package/dist/ci/parse-safety.d.ts +0 -6
  8. package/dist/ci/parse-safety.js +0 -22
  9. package/dist/cli/args.d.ts +0 -13
  10. package/dist/cli/args.js +0 -42
  11. package/dist/cli/litellm.d.ts +0 -2
  12. package/dist/cli/litellm.js +0 -85
  13. package/dist/cli/task-resolution.d.ts +0 -2
  14. package/dist/cli/task-resolution.js +0 -41
  15. package/dist/config.d.ts +0 -49
  16. package/dist/config.js +0 -72
  17. package/dist/context.d.ts +0 -4
  18. package/dist/context.js +0 -83
  19. package/dist/definitions.d.ts +0 -3
  20. package/dist/definitions.js +0 -59
  21. package/dist/entry.d.ts +0 -1
  22. package/dist/entry.js +0 -236
  23. package/dist/git-utils.d.ts +0 -13
  24. package/dist/git-utils.js +0 -174
  25. package/dist/github-api.d.ts +0 -14
  26. package/dist/github-api.js +0 -114
  27. package/dist/kody-utils.d.ts +0 -1
  28. package/dist/kody-utils.js +0 -9
  29. package/dist/learning/auto-learn.d.ts +0 -2
  30. package/dist/learning/auto-learn.js +0 -169
  31. package/dist/logger.d.ts +0 -14
  32. package/dist/logger.js +0 -51
  33. package/dist/memory.d.ts +0 -1
  34. package/dist/memory.js +0 -20
  35. package/dist/observer.d.ts +0 -9
  36. package/dist/observer.js +0 -80
  37. package/dist/pipeline/complexity.d.ts +0 -3
  38. package/dist/pipeline/complexity.js +0 -12
  39. package/dist/pipeline/executor-registry.d.ts +0 -3
  40. package/dist/pipeline/executor-registry.js +0 -20
  41. package/dist/pipeline/hooks.d.ts +0 -17
  42. package/dist/pipeline/hooks.js +0 -110
  43. package/dist/pipeline/questions.d.ts +0 -2
  44. package/dist/pipeline/questions.js +0 -44
  45. package/dist/pipeline/runner-selection.d.ts +0 -2
  46. package/dist/pipeline/runner-selection.js +0 -13
  47. package/dist/pipeline/state.d.ts +0 -4
  48. package/dist/pipeline/state.js +0 -37
  49. package/dist/pipeline.d.ts +0 -3
  50. package/dist/pipeline.js +0 -213
  51. package/dist/preflight.d.ts +0 -1
  52. package/dist/preflight.js +0 -69
  53. package/dist/retrospective.d.ts +0 -26
  54. package/dist/retrospective.js +0 -211
  55. package/dist/stages/agent.d.ts +0 -2
  56. package/dist/stages/agent.js +0 -94
  57. package/dist/stages/gate.d.ts +0 -2
  58. package/dist/stages/gate.js +0 -32
  59. package/dist/stages/review.d.ts +0 -2
  60. package/dist/stages/review.js +0 -32
  61. package/dist/stages/ship.d.ts +0 -3
  62. package/dist/stages/ship.js +0 -154
  63. package/dist/stages/verify.d.ts +0 -2
  64. package/dist/stages/verify.js +0 -94
  65. package/dist/types.d.ts +0 -61
  66. package/dist/types.js +0 -1
  67. package/dist/validators.d.ts +0 -8
  68. package/dist/validators.js +0 -42
  69. package/dist/verify-runner.d.ts +0 -11
  70. package/dist/verify-runner.js +0 -110
package/dist/bin/cli.js CHANGED
@@ -752,7 +752,14 @@ var init_memory = __esm({
752
752
  // src/context.ts
753
753
  import * as fs4 from "fs";
754
754
  import * as path4 from "path";
755
- function readPromptFile(stageName) {
755
+ function readPromptFile(stageName, projectDir) {
756
+ if (projectDir) {
757
+ const stepFile = path4.join(projectDir, ".kody", "steps", `${stageName}.md`);
758
+ if (fs4.existsSync(stepFile)) {
759
+ return fs4.readFileSync(stepFile, "utf-8");
760
+ }
761
+ console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
762
+ }
756
763
  const scriptDir = new URL(".", import.meta.url).pathname;
757
764
  const candidates = [
758
765
  path4.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
@@ -834,7 +841,7 @@ ${feedback}
834
841
  }
835
842
  function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
836
843
  const memory = readProjectMemory(projectDir);
837
- const promptTemplate = readPromptFile(stageName);
844
+ const promptTemplate = readPromptFile(stageName, projectDir);
838
845
  const prompt = injectTaskContext(promptTemplate, taskId, taskDir, feedback);
839
846
  return memory ? `${memory}
840
847
  ---
@@ -3264,6 +3271,77 @@ function detectArchitecture(cwd) {
3264
3271
  if (configs.length > 0) detected.push(`- Config files: ${configs.join(", ")}`);
3265
3272
  return detected;
3266
3273
  }
3274
+ var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
3275
+ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3276
+ const srcDir = path20.join(cwd, "src");
3277
+ const baseDir = fs21.existsSync(srcDir) ? srcDir : cwd;
3278
+ const results = [];
3279
+ function walk(dir) {
3280
+ const entries = [];
3281
+ try {
3282
+ for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
3283
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3284
+ const full = path20.join(dir, entry.name);
3285
+ if (entry.isDirectory()) {
3286
+ entries.push(...walk(full));
3287
+ } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
3288
+ try {
3289
+ const stat = fs21.statSync(full);
3290
+ if (stat.size >= 200 && stat.size <= 5e3) {
3291
+ entries.push({ filePath: full, size: stat.size });
3292
+ }
3293
+ } catch {
3294
+ }
3295
+ }
3296
+ }
3297
+ } catch {
3298
+ }
3299
+ return entries;
3300
+ }
3301
+ const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
3302
+ for (const { filePath } of files) {
3303
+ const rel = path20.relative(cwd, filePath);
3304
+ const content = fs21.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
3305
+ results.push(`### File: ${rel}
3306
+ \`\`\`typescript
3307
+ ${content}
3308
+ \`\`\``);
3309
+ }
3310
+ return results.join("\n\n");
3311
+ }
3312
+ function buildStepCustomizationPrompt(stageName, defaultPrompt, repoContext, architecture, conventions) {
3313
+ return `You are customizing a Kody pipeline prompt for a specific repository.
3314
+
3315
+ ## Your Task
3316
+ Take the default prompt template below and produce a CUSTOMIZED version tailored to this specific repository.
3317
+
3318
+ ## Rules
3319
+ 1. KEEP the entire original prompt intact \u2014 its role definition, rules, output format, and {{TASK_CONTEXT}} placeholder. Do not remove or rephrase any existing content.
3320
+ 2. APPEND three new sections after the original content but BEFORE the {{TASK_CONTEXT}} line:
3321
+ - ## Repo Patterns \u2014 Real code examples from this repo that demonstrate the patterns to follow. Include specific file paths, function signatures, and brief code snippets. Show what GOOD looks like in this repo.
3322
+ - ## Improvement Areas \u2014 Gaps, anti-patterns, or inconsistencies found in the codebase that this stage should address when touching related code. Be specific with file paths and what to fix. Do NOT refactor unrelated code \u2014 only improve what the task touches.
3323
+ - ## Acceptance Criteria \u2014 A concrete checklist (using markdown checkboxes) that defines "done" for this stage in this specific repo.
3324
+ 3. Be SPECIFIC \u2014 reference actual file paths, function names, and conventions from the repo context provided below.
3325
+ 4. Keep each appended section concise (10-20 lines max).
3326
+ 5. Output ONLY the complete customized prompt markdown. No explanation before or after.
3327
+
3328
+ ## Stage Being Customized
3329
+ Stage: ${stageName}
3330
+
3331
+ ## Default Prompt Template
3332
+ ${defaultPrompt}
3333
+
3334
+ ## Repository Context
3335
+
3336
+ ### Architecture
3337
+ ${architecture}
3338
+
3339
+ ### Conventions
3340
+ ${conventions}
3341
+
3342
+ ### Project Details
3343
+ ${repoContext}`;
3344
+ }
3267
3345
  function detectBasicConfig(cwd) {
3268
3346
  let pm = "pnpm";
3269
3347
  if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) pm = "yarn";
@@ -3717,6 +3795,93 @@ ${archItems.join("\n")}
3717
3795
  fs21.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
3718
3796
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
3719
3797
  }
3798
+ console.log("\n\u2500\u2500 Step Files \u2500\u2500");
3799
+ const stepsDir = path20.join(cwd, ".kody", "steps");
3800
+ const stepsExist = fs21.existsSync(stepsDir) && fs21.readdirSync(stepsDir).some((f) => f.endsWith(".md"));
3801
+ if (stepsExist && !opts.force) {
3802
+ console.log(" \u25CB .kody/steps/ (exists, use --force to regenerate)");
3803
+ } else {
3804
+ fs21.mkdirSync(stepsDir, { recursive: true });
3805
+ const readIfExistsForSteps = (rel, maxChars = 3e3) => {
3806
+ const p = path20.join(cwd, rel);
3807
+ if (fs21.existsSync(p)) return fs21.readFileSync(p, "utf-8").slice(0, maxChars);
3808
+ return null;
3809
+ };
3810
+ let repoContext = "";
3811
+ const pkgForSteps = readIfExistsForSteps("package.json");
3812
+ if (pkgForSteps) repoContext += `## package.json
3813
+ ${pkgForSteps}
3814
+
3815
+ `;
3816
+ const readmeForSteps = readIfExistsForSteps("README.md", 2e3);
3817
+ if (readmeForSteps) repoContext += `## README.md
3818
+ ${readmeForSteps}
3819
+
3820
+ `;
3821
+ const claudeMdForSteps = readIfExistsForSteps("CLAUDE.md", 3e3);
3822
+ if (claudeMdForSteps) repoContext += `## CLAUDE.md
3823
+ ${claudeMdForSteps}
3824
+
3825
+ `;
3826
+ const agentsMdForSteps = readIfExistsForSteps("AGENTS.md", 3e3);
3827
+ if (agentsMdForSteps) repoContext += `## AGENTS.md
3828
+ ${agentsMdForSteps}
3829
+
3830
+ `;
3831
+ const sampleFiles = gatherSampleSourceFiles(cwd);
3832
+ if (sampleFiles) repoContext += `## Sample Source Files
3833
+ ${sampleFiles}
3834
+
3835
+ `;
3836
+ try {
3837
+ const srcEntries = fs21.readdirSync(path20.join(cwd, "src"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3838
+ if (srcEntries.length > 0) repoContext += `## src/ structure
3839
+ ${srcEntries.join(", ")}
3840
+
3841
+ `;
3842
+ } catch {
3843
+ }
3844
+ const arch = fs21.existsSync(archPath) ? fs21.readFileSync(archPath, "utf-8") : "";
3845
+ const conv = fs21.existsSync(conventionsPath) ? fs21.readFileSync(conventionsPath, "utf-8") : "";
3846
+ console.log(" \u23F3 Customizing step files with Claude (sonnet)...");
3847
+ let stepCount = 0;
3848
+ for (const stage of STEP_STAGES) {
3849
+ const templatePath = path20.join(PKG_ROOT, "prompts", `${stage}.md`);
3850
+ if (!fs21.existsSync(templatePath)) {
3851
+ console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
3852
+ continue;
3853
+ }
3854
+ const defaultPrompt = fs21.readFileSync(templatePath, "utf-8");
3855
+ const customizationPrompt = buildStepCustomizationPrompt(stage, defaultPrompt, repoContext, arch, conv);
3856
+ try {
3857
+ const output = execFileSync11("claude", [
3858
+ "--print",
3859
+ "--model",
3860
+ "sonnet",
3861
+ "--dangerously-skip-permissions",
3862
+ customizationPrompt
3863
+ ], {
3864
+ encoding: "utf-8",
3865
+ timeout: 12e4,
3866
+ cwd,
3867
+ stdio: ["pipe", "pipe", "pipe"]
3868
+ }).trim();
3869
+ if (!output.includes("{{TASK_CONTEXT}}")) {
3870
+ console.log(` \u26A0 ${stage}.md \u2014 AI dropped {{TASK_CONTEXT}}, using default template`);
3871
+ fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), defaultPrompt);
3872
+ } else {
3873
+ fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), output);
3874
+ }
3875
+ stepCount++;
3876
+ console.log(` \u2713 ${stage}.md`);
3877
+ } catch (err) {
3878
+ console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
3879
+ fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
3880
+ stepCount++;
3881
+ }
3882
+ }
3883
+ console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
3884
+ }
3720
3885
  console.log("\n\u2500\u2500 Git \u2500\u2500");
3721
3886
  const filesToCommit = [
3722
3887
  ".github/workflows/kody.yml",
@@ -3724,6 +3889,12 @@ ${archItems.join("\n")}
3724
3889
  ".kody/memory/architecture.md",
3725
3890
  ".kody/memory/conventions.md"
3726
3891
  ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
3892
+ for (const stage of STEP_STAGES) {
3893
+ const stepFile = `.kody/steps/${stage}.md`;
3894
+ if (fs21.existsSync(path20.join(cwd, stepFile))) {
3895
+ filesToCommit.push(stepFile);
3896
+ }
3897
+ }
3727
3898
  if (filesToCommit.length > 0) {
3728
3899
  try {
3729
3900
  execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
- }