@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.
- package/dist/bin/cli.js +158 -9
- package/package.json +1 -1
- package/templates/kody.yml +2 -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
|
@@ -3911,7 +3911,7 @@ function buildConfig(cwd, basic) {
|
|
|
3911
3911
|
}
|
|
3912
3912
|
return "";
|
|
3913
3913
|
};
|
|
3914
|
-
|
|
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,
|
|
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(
|
|
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,
|
|
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
package/templates/kody.yml
CHANGED
|
@@ -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:
|
|
302
|
+
retention-days: 3
|
|
302
303
|
|
|
303
304
|
# ─── Error Notifications ─────────────────────────────────────────────────────
|
|
304
305
|
notify-parse-error:
|
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
|
-
}
|