@kody-ade/kody-engine-lite 0.1.116 → 0.1.118
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 +46 -27
- package/package.json +2 -1
- package/prompts/taskify-ticket.md +10 -0
- 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
|
@@ -1257,8 +1257,8 @@ async function runTaskifyCommand() {
|
|
|
1257
1257
|
const local = hasFlag(args2, "--local") || !process.env.CI;
|
|
1258
1258
|
const taskIdArg = getArg(args2, "--task-id") ?? process.env.TASK_ID;
|
|
1259
1259
|
const taskId = taskIdArg ?? (issueNumber ? `taskify-${issueNumber}-${generateTaskId()}` : `taskify-${generateTaskId()}`);
|
|
1260
|
-
if (!ticketId && !prdFile) {
|
|
1261
|
-
logger.error("Usage: kody taskify --ticket <ticket-id> OR kody taskify --file <prd.md>");
|
|
1260
|
+
if (!ticketId && !prdFile && !issueNumber) {
|
|
1261
|
+
logger.error("Usage: kody taskify --ticket <ticket-id> OR kody taskify --file <prd.md> OR kody taskify --issue-number <n>");
|
|
1262
1262
|
process.exit(1);
|
|
1263
1263
|
}
|
|
1264
1264
|
if (prdFile && !fs11.existsSync(prdFile)) {
|
|
@@ -1304,8 +1304,8 @@ async function taskifyCommand(opts) {
|
|
|
1304
1304
|
const config = getProjectConfig();
|
|
1305
1305
|
const taskDir = path10.join(projectDir, ".kody", "tasks", taskId);
|
|
1306
1306
|
fs11.mkdirSync(taskDir, { recursive: true });
|
|
1307
|
-
const mode = prdFile ? "file" : "ticket";
|
|
1308
|
-
logger.info(`[taskify] mode=${mode} source=${ticketId ?? prdFile} issue=${issueNumber ?? "none"} task=${taskId}`);
|
|
1307
|
+
const mode = prdFile ? "file" : ticketId ? "ticket" : "issue";
|
|
1308
|
+
logger.info(`[taskify] mode=${mode} source=${ticketId ?? prdFile ?? `issue#${issueNumber}`} issue=${issueNumber ?? "none"} task=${taskId}`);
|
|
1309
1309
|
let mcpConfigJson;
|
|
1310
1310
|
if (mode === "ticket") {
|
|
1311
1311
|
try {
|
|
@@ -1328,6 +1328,18 @@ Add the required MCP server config to \`kody.config.json\` and try again.`
|
|
|
1328
1328
|
const sc = resolveStageConfig(config, "taskify", "strong");
|
|
1329
1329
|
const model = sc.model;
|
|
1330
1330
|
const fileContent = prdFile ? fs11.readFileSync(prdFile, "utf-8") : void 0;
|
|
1331
|
+
let issueBody;
|
|
1332
|
+
if (mode === "issue" && issueNumber) {
|
|
1333
|
+
const issue = getIssue(issueNumber);
|
|
1334
|
+
if (issue) {
|
|
1335
|
+
issueBody = `# ${issue.title}
|
|
1336
|
+
|
|
1337
|
+
${issue.body}`;
|
|
1338
|
+
logger.info(` Fetched issue #${issueNumber} body (${issueBody.length} chars)`);
|
|
1339
|
+
} else {
|
|
1340
|
+
throw new TaskifyError(`Could not fetch issue #${issueNumber}`);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1331
1343
|
let projectContext;
|
|
1332
1344
|
{
|
|
1333
1345
|
const parts = [];
|
|
@@ -1351,9 +1363,9 @@ ${lines.join("\n")}
|
|
|
1351
1363
|
}
|
|
1352
1364
|
if (parts.length > 0) projectContext = parts.join("\n\n");
|
|
1353
1365
|
}
|
|
1354
|
-
const prompt = buildPrompt({ ticketId, fileContent, taskDir, feedback, projectContext });
|
|
1366
|
+
const prompt = buildPrompt({ ticketId, fileContent, issueBody, taskDir, feedback, projectContext });
|
|
1355
1367
|
if (issueNumber && !local) {
|
|
1356
|
-
const src = mode === "file" ? `file \`${path10.basename(prdFile)}\`` : `ticket **${ticketId}
|
|
1368
|
+
const src = mode === "file" ? `file \`${path10.basename(prdFile)}\`` : mode === "ticket" ? `ticket **${ticketId}**` : `issue #${issueNumber} description`;
|
|
1357
1369
|
const runUrl = process.env.RUN_URL ? ` ([logs](${process.env.RUN_URL}))` : "";
|
|
1358
1370
|
postComment(issueNumber, `\u{1F680} Kody pipeline started: \`${taskId}\`${runUrl}
|
|
1359
1371
|
|
|
@@ -1402,7 +1414,7 @@ ${errMsg}`);
|
|
|
1402
1414
|
}
|
|
1403
1415
|
throw new TaskifyError(errMsg);
|
|
1404
1416
|
}
|
|
1405
|
-
const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : "spec");
|
|
1417
|
+
const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : issueNumber ? `issue #${issueNumber}` : "spec");
|
|
1406
1418
|
if (parsed.status === "questions") {
|
|
1407
1419
|
handleQuestions(parsed, sourceLabel, issueNumber, local ?? false);
|
|
1408
1420
|
} else if (parsed.status === "ready") {
|
|
@@ -1499,7 +1511,7 @@ ${links}${triggerNote}`
|
|
|
1499
1511
|
}
|
|
1500
1512
|
}
|
|
1501
1513
|
function buildPrompt(opts) {
|
|
1502
|
-
const { ticketId, fileContent, taskDir, feedback, projectContext } = opts;
|
|
1514
|
+
const { ticketId, fileContent, issueBody, taskDir, feedback, projectContext } = opts;
|
|
1503
1515
|
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
1504
1516
|
const candidates = [
|
|
1505
1517
|
path10.resolve(scriptDir, "..", "prompts", "taskify-ticket.md"),
|
|
@@ -1528,6 +1540,7 @@ function buildPrompt(opts) {
|
|
|
1528
1540
|
resolveBlock("PROJECT_CONTEXT", projectContext);
|
|
1529
1541
|
resolveBlock("TICKET_ID", ticketId);
|
|
1530
1542
|
resolveBlock("FILE_CONTENT", fileContent);
|
|
1543
|
+
resolveBlock("ISSUE_BODY", issueBody);
|
|
1531
1544
|
resolveBlock("FEEDBACK", feedback);
|
|
1532
1545
|
template = template.replace(/\{\{TASK_DIR\}\}/g, taskDir);
|
|
1533
1546
|
return template;
|
|
@@ -2860,23 +2873,6 @@ function parseCommentInputs() {
|
|
|
2860
2873
|
}
|
|
2861
2874
|
const modesWithoutTaskId = ["fix", "fix-ci", "status", "review", "resolve", "rerun"];
|
|
2862
2875
|
const valid = !!taskId || modesWithoutTaskId.includes(mode);
|
|
2863
|
-
if (mode === "taskify" && !ticketId && !prdFile) {
|
|
2864
|
-
return {
|
|
2865
|
-
task_id: taskId,
|
|
2866
|
-
mode,
|
|
2867
|
-
from_stage: fromStage,
|
|
2868
|
-
issue_number: issueNumber,
|
|
2869
|
-
pr_number: "",
|
|
2870
|
-
feedback,
|
|
2871
|
-
complexity,
|
|
2872
|
-
ci_run_id: ciRunId,
|
|
2873
|
-
ticket_id: "",
|
|
2874
|
-
prd_file: "",
|
|
2875
|
-
dry_run: dryRun,
|
|
2876
|
-
valid: false,
|
|
2877
|
-
trigger_type: "comment"
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
2876
|
return {
|
|
2881
2877
|
task_id: taskId,
|
|
2882
2878
|
mode,
|
|
@@ -4227,8 +4223,8 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
|
|
|
4227
4223
|
"diagnosis",
|
|
4228
4224
|
prompt,
|
|
4229
4225
|
model,
|
|
4230
|
-
|
|
4231
|
-
//
|
|
4226
|
+
9e4,
|
|
4227
|
+
// 90s timeout — MiniMax can be slow to respond
|
|
4232
4228
|
"",
|
|
4233
4229
|
options
|
|
4234
4230
|
);
|
|
@@ -4260,6 +4256,29 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
|
|
|
4260
4256
|
} catch (err) {
|
|
4261
4257
|
logger.warn(` Diagnosis error: ${err instanceof Error ? err.message : err}`);
|
|
4262
4258
|
}
|
|
4259
|
+
if (modifiedFiles.length > 0) {
|
|
4260
|
+
const errorLines = errorOutput.split("\n").filter(
|
|
4261
|
+
(l) => /error|Error|ERROR|failed|Failed|FAIL/i.test(l)
|
|
4262
|
+
);
|
|
4263
|
+
const errorFilePaths = errorLines.flatMap((line) => {
|
|
4264
|
+
const matches = line.match(/src\/[^\s(:]+\.[a-z]+/g);
|
|
4265
|
+
return matches ?? [];
|
|
4266
|
+
});
|
|
4267
|
+
if (errorFilePaths.length > 0) {
|
|
4268
|
+
const modifiedSet = new Set(modifiedFiles);
|
|
4269
|
+
const allPreExisting = errorFilePaths.every(
|
|
4270
|
+
(f) => !modifiedSet.has(f) && !modifiedFiles.some((m) => m.endsWith(f))
|
|
4271
|
+
);
|
|
4272
|
+
if (allPreExisting) {
|
|
4273
|
+
logger.warn(" Diagnosis fallback: all errors in unmodified files \u2192 pre-existing");
|
|
4274
|
+
return {
|
|
4275
|
+
classification: "pre-existing",
|
|
4276
|
+
reason: "All errors are in files not modified by the build stage",
|
|
4277
|
+
resolution: `The following files have pre-existing errors not introduced by this task: ${[...new Set(errorFilePaths)].join(", ")}`
|
|
4278
|
+
};
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4263
4282
|
logger.warn(" Diagnosis failed \u2014 defaulting to retry");
|
|
4264
4283
|
return {
|
|
4265
4284
|
classification: "retry",
|
package/package.json
CHANGED
|
@@ -19,6 +19,16 @@ The product spec is provided below:
|
|
|
19
19
|
```
|
|
20
20
|
{{/if}}
|
|
21
21
|
|
|
22
|
+
{{#if ISSUE_BODY}}
|
|
23
|
+
**Mode: issue**
|
|
24
|
+
|
|
25
|
+
The task description from the GitHub issue is provided below. Decompose it into scoped, independently implementable sub-tasks.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
{{ISSUE_BODY}}
|
|
29
|
+
```
|
|
30
|
+
{{/if}}
|
|
31
|
+
|
|
22
32
|
{{#if PROJECT_CONTEXT}}
|
|
23
33
|
## Existing codebase
|
|
24
34
|
|
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
|
-
}
|
package/dist/config.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export interface RunnerConfig {
|
|
2
|
-
type: "claude-code";
|
|
3
|
-
}
|
|
4
|
-
export interface KodyConfig {
|
|
5
|
-
quality: {
|
|
6
|
-
typecheck: string;
|
|
7
|
-
lint: string;
|
|
8
|
-
lintFix: string;
|
|
9
|
-
format: string;
|
|
10
|
-
formatFix: string;
|
|
11
|
-
testUnit: string;
|
|
12
|
-
};
|
|
13
|
-
git: {
|
|
14
|
-
defaultBranch: string;
|
|
15
|
-
userEmail?: string;
|
|
16
|
-
userName?: string;
|
|
17
|
-
};
|
|
18
|
-
github: {
|
|
19
|
-
owner: string;
|
|
20
|
-
repo: string;
|
|
21
|
-
};
|
|
22
|
-
paths: {
|
|
23
|
-
taskDir: string;
|
|
24
|
-
};
|
|
25
|
-
agent: {
|
|
26
|
-
runner?: string;
|
|
27
|
-
modelMap: {
|
|
28
|
-
cheap: string;
|
|
29
|
-
mid: string;
|
|
30
|
-
strong: string;
|
|
31
|
-
};
|
|
32
|
-
litellmUrl?: string;
|
|
33
|
-
usePerStageRouting?: boolean;
|
|
34
|
-
defaultRunner?: string;
|
|
35
|
-
runners?: Record<string, RunnerConfig>;
|
|
36
|
-
stageRunners?: Record<string, string>;
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
export declare const SIGKILL_GRACE_MS = 5000;
|
|
40
|
-
export declare const MAX_PR_TITLE_LENGTH = 72;
|
|
41
|
-
export declare const STDERR_TAIL_CHARS = 500;
|
|
42
|
-
export declare const API_TIMEOUT_MS = 30000;
|
|
43
|
-
export declare const DEFAULT_MAX_FIX_ATTEMPTS = 2;
|
|
44
|
-
export declare const AGENT_RETRY_DELAY_MS = 2000;
|
|
45
|
-
export declare const VERIFY_COMMAND_TIMEOUT_MS: number;
|
|
46
|
-
export declare const FIX_COMMAND_TIMEOUT_MS: number;
|
|
47
|
-
export declare function setConfigDir(dir: string): void;
|
|
48
|
-
export declare function getProjectConfig(): KodyConfig;
|
|
49
|
-
export declare function resetProjectConfig(): void;
|