@kody-ade/kody-engine-lite 0.1.45 → 0.1.47
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.
- package/dist/bin/cli.js +173 -2
- package/package.json +1 -1
- package/dist/agent-runner.d.ts +0 -4
- package/dist/agent-runner.js +0 -122
- package/dist/ci/parse-inputs.d.ts +0 -6
- package/dist/ci/parse-inputs.js +0 -76
- package/dist/ci/parse-safety.d.ts +0 -6
- package/dist/ci/parse-safety.js +0 -22
- package/dist/cli/args.d.ts +0 -13
- package/dist/cli/args.js +0 -42
- package/dist/cli/litellm.d.ts +0 -2
- package/dist/cli/litellm.js +0 -85
- package/dist/cli/task-resolution.d.ts +0 -2
- package/dist/cli/task-resolution.js +0 -41
- package/dist/config.d.ts +0 -49
- package/dist/config.js +0 -72
- package/dist/context.d.ts +0 -4
- package/dist/context.js +0 -83
- package/dist/definitions.d.ts +0 -3
- package/dist/definitions.js +0 -59
- package/dist/entry.d.ts +0 -1
- package/dist/entry.js +0 -236
- package/dist/git-utils.d.ts +0 -13
- package/dist/git-utils.js +0 -174
- package/dist/github-api.d.ts +0 -14
- package/dist/github-api.js +0 -114
- package/dist/kody-utils.d.ts +0 -1
- package/dist/kody-utils.js +0 -9
- package/dist/learning/auto-learn.d.ts +0 -2
- package/dist/learning/auto-learn.js +0 -169
- package/dist/logger.d.ts +0 -14
- package/dist/logger.js +0 -51
- package/dist/memory.d.ts +0 -1
- package/dist/memory.js +0 -20
- package/dist/observer.d.ts +0 -9
- package/dist/observer.js +0 -80
- package/dist/pipeline/complexity.d.ts +0 -3
- package/dist/pipeline/complexity.js +0 -12
- package/dist/pipeline/executor-registry.d.ts +0 -3
- package/dist/pipeline/executor-registry.js +0 -20
- package/dist/pipeline/hooks.d.ts +0 -17
- package/dist/pipeline/hooks.js +0 -110
- package/dist/pipeline/questions.d.ts +0 -2
- package/dist/pipeline/questions.js +0 -44
- package/dist/pipeline/runner-selection.d.ts +0 -2
- package/dist/pipeline/runner-selection.js +0 -13
- package/dist/pipeline/state.d.ts +0 -4
- package/dist/pipeline/state.js +0 -37
- package/dist/pipeline.d.ts +0 -3
- package/dist/pipeline.js +0 -213
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.js +0 -69
- package/dist/retrospective.d.ts +0 -26
- package/dist/retrospective.js +0 -211
- package/dist/stages/agent.d.ts +0 -2
- package/dist/stages/agent.js +0 -94
- package/dist/stages/gate.d.ts +0 -2
- package/dist/stages/gate.js +0 -32
- package/dist/stages/review.d.ts +0 -2
- package/dist/stages/review.js +0 -32
- package/dist/stages/ship.d.ts +0 -3
- package/dist/stages/ship.js +0 -154
- package/dist/stages/verify.d.ts +0 -2
- package/dist/stages/verify.js +0 -94
- package/dist/types.d.ts +0 -61
- package/dist/types.js +0 -1
- package/dist/validators.d.ts +0 -8
- package/dist/validators.js +0 -42
- package/dist/verify-runner.d.ts +0 -11
- 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
package/dist/agent-runner.d.ts
DELETED
package/dist/agent-runner.js
DELETED
|
@@ -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
|
-
}
|
package/dist/ci/parse-inputs.js
DELETED
|
@@ -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");
|
package/dist/ci/parse-safety.js
DELETED
|
@@ -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", "");
|
package/dist/cli/args.d.ts
DELETED
|
@@ -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
|
-
}
|
package/dist/cli/litellm.d.ts
DELETED
package/dist/cli/litellm.js
DELETED
|
@@ -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,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
|
-
}
|