@kody-ade/kody-engine-lite 0.1.104 → 0.1.106
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 +626 -171
- package/kody.config.schema.json +5 -0
- package/package.json +1 -1
- package/prompts/taskify.md +0 -5
- package/templates/kody.yml +30 -103
- 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
|
@@ -64,6 +64,193 @@ var init_architecture_detection = __esm({
|
|
|
64
64
|
}
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
// src/ci/parse-inputs.ts
|
|
68
|
+
var parse_inputs_exports = {};
|
|
69
|
+
__export(parse_inputs_exports, {
|
|
70
|
+
parseCommentInputs: () => parseCommentInputs,
|
|
71
|
+
runCiParse: () => runCiParse,
|
|
72
|
+
writeOutputs: () => writeOutputs
|
|
73
|
+
});
|
|
74
|
+
import * as fs8 from "fs";
|
|
75
|
+
function generateTimestamp() {
|
|
76
|
+
const now = /* @__PURE__ */ new Date();
|
|
77
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
78
|
+
const y = String(now.getFullYear()).slice(2);
|
|
79
|
+
const m = pad(now.getMonth() + 1);
|
|
80
|
+
const d = pad(now.getDate());
|
|
81
|
+
const H = pad(now.getHours());
|
|
82
|
+
const M = pad(now.getMinutes());
|
|
83
|
+
const S = pad(now.getSeconds());
|
|
84
|
+
return `${y}${m}${d}-${H}${M}${S}`;
|
|
85
|
+
}
|
|
86
|
+
function parseCommentInputs() {
|
|
87
|
+
const triggerType = process.env.TRIGGER_TYPE ?? "dispatch";
|
|
88
|
+
if (triggerType === "dispatch") {
|
|
89
|
+
const taskId2 = process.env.INPUT_TASK_ID ?? "";
|
|
90
|
+
return {
|
|
91
|
+
task_id: taskId2,
|
|
92
|
+
mode: process.env.INPUT_MODE ?? "full",
|
|
93
|
+
from_stage: process.env.INPUT_FROM_STAGE ?? "",
|
|
94
|
+
issue_number: process.env.INPUT_ISSUE_NUMBER ?? "",
|
|
95
|
+
pr_number: "",
|
|
96
|
+
feedback: process.env.INPUT_FEEDBACK ?? "",
|
|
97
|
+
complexity: "",
|
|
98
|
+
ci_run_id: "",
|
|
99
|
+
dry_run: false,
|
|
100
|
+
valid: !!taskId2,
|
|
101
|
+
trigger_type: "dispatch"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const commentBody = (process.env.COMMENT_BODY ?? "").replace(/\r/g, "");
|
|
105
|
+
const issueNumber = process.env.ISSUE_NUMBER ?? "";
|
|
106
|
+
const isPR = !!process.env.ISSUE_IS_PR;
|
|
107
|
+
const kodyMatch = commentBody.match(/(?:@kody|\/kody)\s*(.*)/i);
|
|
108
|
+
if (!kodyMatch) {
|
|
109
|
+
return {
|
|
110
|
+
task_id: "",
|
|
111
|
+
mode: "full",
|
|
112
|
+
from_stage: "",
|
|
113
|
+
issue_number: issueNumber,
|
|
114
|
+
pr_number: "",
|
|
115
|
+
feedback: "",
|
|
116
|
+
complexity: "",
|
|
117
|
+
ci_run_id: "",
|
|
118
|
+
dry_run: false,
|
|
119
|
+
valid: false,
|
|
120
|
+
trigger_type: "comment"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const argsLine = kodyMatch[1].trim();
|
|
124
|
+
let fromStage = "";
|
|
125
|
+
let feedback = "";
|
|
126
|
+
let complexity = "";
|
|
127
|
+
let dryRun = false;
|
|
128
|
+
let ciRunId = "";
|
|
129
|
+
const fromMatch = argsLine.match(/--from\s+(\S+)/);
|
|
130
|
+
if (fromMatch) fromStage = fromMatch[1];
|
|
131
|
+
const feedbackMatch = argsLine.match(/--feedback\s+"([^"]*)"/);
|
|
132
|
+
if (feedbackMatch) feedback = feedbackMatch[1];
|
|
133
|
+
const complexityMatch = argsLine.match(/--complexity\s+(\S+)/);
|
|
134
|
+
if (complexityMatch) complexity = complexityMatch[1];
|
|
135
|
+
if (/--dry-run/.test(argsLine)) dryRun = true;
|
|
136
|
+
const ciRunIdMatch = argsLine.match(/--ci-run-id\s+(\S+)/);
|
|
137
|
+
if (ciRunIdMatch) ciRunId = ciRunIdMatch[1];
|
|
138
|
+
const positional = argsLine.replace(/--from\s+\S+/g, "").replace(/--feedback\s+"[^"]*"/g, "").replace(/--complexity\s+\S+/g, "").replace(/--dry-run/g, "").replace(/--ci-run-id\s+\S+/g, "").replace(/\s+/g, " ").trim();
|
|
139
|
+
const parts = positional ? positional.split(/\s+/) : [];
|
|
140
|
+
let mode = "full";
|
|
141
|
+
let taskId = "";
|
|
142
|
+
let idx = 0;
|
|
143
|
+
if (parts[idx] && VALID_MODES.includes(parts[idx])) {
|
|
144
|
+
mode = parts[idx];
|
|
145
|
+
idx++;
|
|
146
|
+
}
|
|
147
|
+
if (parts[idx] && !parts[idx].startsWith("--")) {
|
|
148
|
+
taskId = parts[idx];
|
|
149
|
+
idx++;
|
|
150
|
+
} else if (parts[0] && !VALID_MODES.includes(parts[0]) && !parts[0].startsWith("--")) {
|
|
151
|
+
taskId = parts[0];
|
|
152
|
+
}
|
|
153
|
+
const kodyLineIdx = commentBody.search(/(?:@kody|\/kody)/i);
|
|
154
|
+
const afterKodyLine = commentBody.slice(kodyLineIdx);
|
|
155
|
+
const newlineIdx = afterKodyLine.indexOf("\n");
|
|
156
|
+
const bodyAfterCommand = newlineIdx !== -1 ? afterKodyLine.slice(newlineIdx + 1) : "";
|
|
157
|
+
if (mode === "approve") {
|
|
158
|
+
mode = "rerun";
|
|
159
|
+
if (bodyAfterCommand) {
|
|
160
|
+
feedback = bodyAfterCommand;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (mode === "fix") {
|
|
164
|
+
if (bodyAfterCommand) {
|
|
165
|
+
feedback = bodyAfterCommand;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (mode === "fix-ci") {
|
|
169
|
+
if (bodyAfterCommand) {
|
|
170
|
+
feedback = bodyAfterCommand;
|
|
171
|
+
const runIdFromBody = bodyAfterCommand.match(/Run ID:\s*(\d+)/);
|
|
172
|
+
if (runIdFromBody) {
|
|
173
|
+
ciRunId = runIdFromBody[1];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (mode === "bootstrap") {
|
|
178
|
+
taskId = `bootstrap-${generateTimestamp()}`;
|
|
179
|
+
}
|
|
180
|
+
const prNumber = isPR ? issueNumber : "";
|
|
181
|
+
if (mode === "review" && prNumber) {
|
|
182
|
+
taskId = `review-pr-${prNumber}-${generateTimestamp()}`;
|
|
183
|
+
}
|
|
184
|
+
if (!taskId && mode === "full") {
|
|
185
|
+
taskId = `${issueNumber}-${generateTimestamp()}`;
|
|
186
|
+
}
|
|
187
|
+
const modesWithoutTaskId = ["fix", "fix-ci", "status", "review", "resolve", "rerun"];
|
|
188
|
+
const valid = !!taskId || modesWithoutTaskId.includes(mode);
|
|
189
|
+
return {
|
|
190
|
+
task_id: taskId,
|
|
191
|
+
mode,
|
|
192
|
+
from_stage: fromStage,
|
|
193
|
+
issue_number: issueNumber,
|
|
194
|
+
pr_number: prNumber,
|
|
195
|
+
feedback,
|
|
196
|
+
complexity,
|
|
197
|
+
ci_run_id: ciRunId,
|
|
198
|
+
dry_run: dryRun,
|
|
199
|
+
valid,
|
|
200
|
+
trigger_type: "comment"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function writeOutputs(result) {
|
|
204
|
+
const outputFile = process.env.GITHUB_OUTPUT;
|
|
205
|
+
function output(key, value) {
|
|
206
|
+
if (outputFile) {
|
|
207
|
+
if (value.includes("\n")) {
|
|
208
|
+
fs8.appendFileSync(outputFile, `${key}<<KODY_EOF
|
|
209
|
+
${value}
|
|
210
|
+
KODY_EOF
|
|
211
|
+
`);
|
|
212
|
+
} else {
|
|
213
|
+
fs8.appendFileSync(outputFile, `${key}=${value}
|
|
214
|
+
`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const display = value.includes("\n") ? value.split("\n")[0] + "..." : value;
|
|
218
|
+
console.log(`${key}=${display}`);
|
|
219
|
+
}
|
|
220
|
+
output("task_id", result.task_id);
|
|
221
|
+
output("mode", result.mode);
|
|
222
|
+
output("from_stage", result.from_stage);
|
|
223
|
+
output("issue_number", result.issue_number);
|
|
224
|
+
output("pr_number", result.pr_number);
|
|
225
|
+
output("feedback", result.feedback);
|
|
226
|
+
output("complexity", result.complexity);
|
|
227
|
+
output("ci_run_id", result.ci_run_id);
|
|
228
|
+
output("dry_run", result.dry_run ? "true" : "false");
|
|
229
|
+
output("valid", result.valid ? "true" : "false");
|
|
230
|
+
output("trigger_type", result.trigger_type);
|
|
231
|
+
}
|
|
232
|
+
function runCiParse() {
|
|
233
|
+
const result = parseCommentInputs();
|
|
234
|
+
writeOutputs(result);
|
|
235
|
+
}
|
|
236
|
+
var VALID_MODES;
|
|
237
|
+
var init_parse_inputs = __esm({
|
|
238
|
+
"src/ci/parse-inputs.ts"() {
|
|
239
|
+
"use strict";
|
|
240
|
+
VALID_MODES = [
|
|
241
|
+
"full",
|
|
242
|
+
"rerun",
|
|
243
|
+
"fix",
|
|
244
|
+
"fix-ci",
|
|
245
|
+
"status",
|
|
246
|
+
"approve",
|
|
247
|
+
"review",
|
|
248
|
+
"resolve",
|
|
249
|
+
"bootstrap"
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
67
254
|
// src/agent-runner.ts
|
|
68
255
|
import { spawn, execFileSync as execFileSync6 } from "child_process";
|
|
69
256
|
function writeStdin(child, prompt) {
|
|
@@ -395,11 +582,36 @@ var init_validators = __esm({
|
|
|
395
582
|
});
|
|
396
583
|
|
|
397
584
|
// src/config.ts
|
|
398
|
-
import * as
|
|
585
|
+
import * as fs9 from "fs";
|
|
399
586
|
import * as path7 from "path";
|
|
587
|
+
function resolveStageConfig(config, stageName, modelTier) {
|
|
588
|
+
const stageOverride = config.agent.stages?.[stageName];
|
|
589
|
+
if (stageOverride) return stageOverride;
|
|
590
|
+
if (config.agent.default) return config.agent.default;
|
|
591
|
+
const model = config.agent.modelMap[modelTier];
|
|
592
|
+
if (!model) {
|
|
593
|
+
throw new Error(`No model configured for stage '${stageName}' (tier: ${modelTier}). Set agent.stages.${stageName} or agent.default in kody.config.json`);
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
provider: config.agent.provider ?? "claude",
|
|
597
|
+
model
|
|
598
|
+
};
|
|
599
|
+
}
|
|
400
600
|
function needsLitellmProxy(config) {
|
|
401
601
|
return !!(config.agent.provider && config.agent.provider !== "anthropic");
|
|
402
602
|
}
|
|
603
|
+
function stageNeedsProxy(stageConfig) {
|
|
604
|
+
return stageConfig.provider !== "claude" && stageConfig.provider !== "anthropic";
|
|
605
|
+
}
|
|
606
|
+
function anyStageNeedsProxy(config) {
|
|
607
|
+
if (config.agent.stages) {
|
|
608
|
+
for (const sc of Object.values(config.agent.stages)) {
|
|
609
|
+
if (stageNeedsProxy(sc)) return true;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (config.agent.default && stageNeedsProxy(config.agent.default)) return true;
|
|
613
|
+
return needsLitellmProxy(config);
|
|
614
|
+
}
|
|
403
615
|
function getLitellmUrl() {
|
|
404
616
|
return LITELLM_DEFAULT_URL;
|
|
405
617
|
}
|
|
@@ -414,9 +626,9 @@ function setConfigDir(dir) {
|
|
|
414
626
|
function getProjectConfig() {
|
|
415
627
|
if (_config) return _config;
|
|
416
628
|
const configPath = path7.join(_configDir ?? process.cwd(), "kody.config.json");
|
|
417
|
-
if (
|
|
629
|
+
if (fs9.existsSync(configPath)) {
|
|
418
630
|
try {
|
|
419
|
-
const result = parseJsonSafe(
|
|
631
|
+
const result = parseJsonSafe(fs9.readFileSync(configPath, "utf-8"));
|
|
420
632
|
if (!result.ok) {
|
|
421
633
|
logger.warn(`kody.config.json: ${result.error} \u2014 using defaults`);
|
|
422
634
|
_config = { ...DEFAULT_CONFIG };
|
|
@@ -652,6 +864,17 @@ function commitAll(message, cwd) {
|
|
|
652
864
|
logger.info(` Committed: ${hash} ${message}`);
|
|
653
865
|
return { success: true, hash, message };
|
|
654
866
|
}
|
|
867
|
+
function getDiffFiles(baseBranch, cwd) {
|
|
868
|
+
try {
|
|
869
|
+
const output = git(["diff", "--name-only", `origin/${baseBranch}...HEAD`], { cwd });
|
|
870
|
+
if (!output) return [];
|
|
871
|
+
return output.split("\n").filter((f) => f && !f.startsWith(".kody/"));
|
|
872
|
+
} catch (err) {
|
|
873
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
874
|
+
logger.warn(` Failed to get diff files: ${msg}`);
|
|
875
|
+
return [];
|
|
876
|
+
}
|
|
877
|
+
}
|
|
655
878
|
function pushBranch(cwd) {
|
|
656
879
|
try {
|
|
657
880
|
git(["push", "-u", "origin", "HEAD"], { cwd, timeout: 12e4 });
|
|
@@ -730,6 +953,19 @@ function getIssue(issueNumber) {
|
|
|
730
953
|
return null;
|
|
731
954
|
}
|
|
732
955
|
}
|
|
956
|
+
function getIssueComments(issueNumber) {
|
|
957
|
+
try {
|
|
958
|
+
const output = gh([
|
|
959
|
+
"api",
|
|
960
|
+
`repos/{owner}/{repo}/issues/${issueNumber}/comments`,
|
|
961
|
+
"--jq",
|
|
962
|
+
"[.[] | {body, created_at}]"
|
|
963
|
+
]);
|
|
964
|
+
return output ? JSON.parse(output) : [];
|
|
965
|
+
} catch {
|
|
966
|
+
return [];
|
|
967
|
+
}
|
|
968
|
+
}
|
|
733
969
|
function getIssueLabels(issueNumber) {
|
|
734
970
|
try {
|
|
735
971
|
const output = gh(["issue", "view", String(issueNumber), "--json", "labels", "--jq", ".labels[].name"]);
|
|
@@ -1059,14 +1295,14 @@ var init_github_api = __esm({
|
|
|
1059
1295
|
});
|
|
1060
1296
|
|
|
1061
1297
|
// src/pipeline/state.ts
|
|
1062
|
-
import * as
|
|
1298
|
+
import * as fs10 from "fs";
|
|
1063
1299
|
import * as path8 from "path";
|
|
1064
1300
|
function loadState(taskId, taskDir) {
|
|
1065
1301
|
const p = path8.join(taskDir, "status.json");
|
|
1066
|
-
if (!
|
|
1302
|
+
if (!fs10.existsSync(p)) return null;
|
|
1067
1303
|
try {
|
|
1068
1304
|
const result = parseJsonSafe(
|
|
1069
|
-
|
|
1305
|
+
fs10.readFileSync(p, "utf-8"),
|
|
1070
1306
|
["taskId", "state", "stages", "createdAt", "updatedAt"]
|
|
1071
1307
|
);
|
|
1072
1308
|
if (!result.ok) {
|
|
@@ -1086,8 +1322,8 @@ function writeState(state, taskDir) {
|
|
|
1086
1322
|
};
|
|
1087
1323
|
const target = path8.join(taskDir, "status.json");
|
|
1088
1324
|
const tmp = target + ".tmp";
|
|
1089
|
-
|
|
1090
|
-
|
|
1325
|
+
fs10.writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
1326
|
+
fs10.renameSync(tmp, target);
|
|
1091
1327
|
return updated;
|
|
1092
1328
|
}
|
|
1093
1329
|
function initState(taskId) {
|
|
@@ -1128,16 +1364,16 @@ var init_complexity = __esm({
|
|
|
1128
1364
|
});
|
|
1129
1365
|
|
|
1130
1366
|
// src/memory.ts
|
|
1131
|
-
import * as
|
|
1367
|
+
import * as fs11 from "fs";
|
|
1132
1368
|
import * as path9 from "path";
|
|
1133
1369
|
function readProjectMemory(projectDir) {
|
|
1134
1370
|
const memoryDir = path9.join(projectDir, ".kody", "memory");
|
|
1135
|
-
if (!
|
|
1136
|
-
const files =
|
|
1371
|
+
if (!fs11.existsSync(memoryDir)) return "";
|
|
1372
|
+
const files = fs11.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
1137
1373
|
if (files.length === 0) return "";
|
|
1138
1374
|
const sections = [];
|
|
1139
1375
|
for (const file of files) {
|
|
1140
|
-
const content =
|
|
1376
|
+
const content = fs11.readFileSync(path9.join(memoryDir, file), "utf-8").trim();
|
|
1141
1377
|
if (content) {
|
|
1142
1378
|
sections.push(`## ${file.replace(".md", "")}
|
|
1143
1379
|
${content}`);
|
|
@@ -1156,7 +1392,7 @@ var init_memory = __esm({
|
|
|
1156
1392
|
});
|
|
1157
1393
|
|
|
1158
1394
|
// src/context-tiers.ts
|
|
1159
|
-
import * as
|
|
1395
|
+
import * as fs12 from "fs";
|
|
1160
1396
|
import * as path10 from "path";
|
|
1161
1397
|
function estimateTokens(text) {
|
|
1162
1398
|
return Math.ceil(text.length / 4);
|
|
@@ -1261,14 +1497,14 @@ function selectTier(tiered, tier) {
|
|
|
1261
1497
|
}
|
|
1262
1498
|
function readProjectMemoryTiered(projectDir, tier) {
|
|
1263
1499
|
const memoryDir = path10.join(projectDir, ".kody", "memory");
|
|
1264
|
-
if (!
|
|
1265
|
-
const files =
|
|
1500
|
+
if (!fs12.existsSync(memoryDir)) return "";
|
|
1501
|
+
const files = fs12.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
1266
1502
|
if (files.length === 0) return "";
|
|
1267
1503
|
const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
|
|
1268
1504
|
const sections = [];
|
|
1269
1505
|
for (const file of files) {
|
|
1270
1506
|
const filePath = path10.join(memoryDir, file);
|
|
1271
|
-
const content =
|
|
1507
|
+
const content = fs12.readFileSync(filePath, "utf-8").trim();
|
|
1272
1508
|
if (!content) continue;
|
|
1273
1509
|
const tiered = getTieredContent(filePath, content);
|
|
1274
1510
|
const selected = selectTier(tiered, tier);
|
|
@@ -1292,8 +1528,8 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
1292
1528
|
context += `Task Directory: ${taskDir}
|
|
1293
1529
|
`;
|
|
1294
1530
|
const taskMdPath = path10.join(taskDir, "task.md");
|
|
1295
|
-
if (
|
|
1296
|
-
const content =
|
|
1531
|
+
if (fs12.existsSync(taskMdPath)) {
|
|
1532
|
+
const content = fs12.readFileSync(taskMdPath, "utf-8");
|
|
1297
1533
|
const selected = selectContent(taskMdPath, content, policy.taskDescription);
|
|
1298
1534
|
const label = tierLabel("Task Description", policy.taskDescription);
|
|
1299
1535
|
context += `
|
|
@@ -1302,8 +1538,8 @@ ${selected}
|
|
|
1302
1538
|
`;
|
|
1303
1539
|
}
|
|
1304
1540
|
const taskJsonPath = path10.join(taskDir, "task.json");
|
|
1305
|
-
if (
|
|
1306
|
-
const content =
|
|
1541
|
+
if (fs12.existsSync(taskJsonPath)) {
|
|
1542
|
+
const content = fs12.readFileSync(taskJsonPath, "utf-8");
|
|
1307
1543
|
if (policy.taskClassification === "L2") {
|
|
1308
1544
|
try {
|
|
1309
1545
|
const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
|
|
@@ -1330,8 +1566,8 @@ ${selected}
|
|
|
1330
1566
|
}
|
|
1331
1567
|
}
|
|
1332
1568
|
const specPath = path10.join(taskDir, "spec.md");
|
|
1333
|
-
if (
|
|
1334
|
-
const content =
|
|
1569
|
+
if (fs12.existsSync(specPath)) {
|
|
1570
|
+
const content = fs12.readFileSync(specPath, "utf-8");
|
|
1335
1571
|
const selected = selectContent(specPath, content, policy.spec);
|
|
1336
1572
|
const label = tierLabel("Spec", policy.spec);
|
|
1337
1573
|
context += `
|
|
@@ -1340,8 +1576,8 @@ ${selected}
|
|
|
1340
1576
|
`;
|
|
1341
1577
|
}
|
|
1342
1578
|
const planPath = path10.join(taskDir, "plan.md");
|
|
1343
|
-
if (
|
|
1344
|
-
const content =
|
|
1579
|
+
if (fs12.existsSync(planPath)) {
|
|
1580
|
+
const content = fs12.readFileSync(planPath, "utf-8");
|
|
1345
1581
|
const selected = selectContent(planPath, content, policy.plan);
|
|
1346
1582
|
const label = tierLabel("Plan", policy.plan);
|
|
1347
1583
|
context += `
|
|
@@ -1350,8 +1586,8 @@ ${selected}
|
|
|
1350
1586
|
`;
|
|
1351
1587
|
}
|
|
1352
1588
|
const contextMdPath = path10.join(taskDir, "context.md");
|
|
1353
|
-
if (
|
|
1354
|
-
const content =
|
|
1589
|
+
if (fs12.existsSync(contextMdPath)) {
|
|
1590
|
+
const content = fs12.readFileSync(contextMdPath, "utf-8");
|
|
1355
1591
|
const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
|
|
1356
1592
|
const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
|
|
1357
1593
|
context += `
|
|
@@ -1483,13 +1719,13 @@ var init_mcp_config = __esm({
|
|
|
1483
1719
|
});
|
|
1484
1720
|
|
|
1485
1721
|
// src/context.ts
|
|
1486
|
-
import * as
|
|
1722
|
+
import * as fs13 from "fs";
|
|
1487
1723
|
import * as path11 from "path";
|
|
1488
1724
|
function readPromptFile(stageName, projectDir) {
|
|
1489
1725
|
if (projectDir) {
|
|
1490
1726
|
const stepFile = path11.join(projectDir, ".kody", "steps", `${stageName}.md`);
|
|
1491
|
-
if (
|
|
1492
|
-
return
|
|
1727
|
+
if (fs13.existsSync(stepFile)) {
|
|
1728
|
+
return fs13.readFileSync(stepFile, "utf-8");
|
|
1493
1729
|
}
|
|
1494
1730
|
console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
|
|
1495
1731
|
}
|
|
@@ -1499,8 +1735,8 @@ function readPromptFile(stageName, projectDir) {
|
|
|
1499
1735
|
path11.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
|
|
1500
1736
|
];
|
|
1501
1737
|
for (const candidate of candidates) {
|
|
1502
|
-
if (
|
|
1503
|
-
return
|
|
1738
|
+
if (fs13.existsSync(candidate)) {
|
|
1739
|
+
return fs13.readFileSync(candidate, "utf-8");
|
|
1504
1740
|
}
|
|
1505
1741
|
}
|
|
1506
1742
|
throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
|
|
@@ -1513,17 +1749,17 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
|
|
|
1513
1749
|
context += `Task Directory: ${taskDir}
|
|
1514
1750
|
`;
|
|
1515
1751
|
const taskMdPath = path11.join(taskDir, "task.md");
|
|
1516
|
-
if (
|
|
1517
|
-
const taskMd =
|
|
1752
|
+
if (fs13.existsSync(taskMdPath)) {
|
|
1753
|
+
const taskMd = fs13.readFileSync(taskMdPath, "utf-8");
|
|
1518
1754
|
context += `
|
|
1519
1755
|
## Task Description
|
|
1520
1756
|
${taskMd}
|
|
1521
1757
|
`;
|
|
1522
1758
|
}
|
|
1523
1759
|
const taskJsonPath = path11.join(taskDir, "task.json");
|
|
1524
|
-
if (
|
|
1760
|
+
if (fs13.existsSync(taskJsonPath)) {
|
|
1525
1761
|
try {
|
|
1526
|
-
const taskDef = JSON.parse(
|
|
1762
|
+
const taskDef = JSON.parse(fs13.readFileSync(taskJsonPath, "utf-8"));
|
|
1527
1763
|
context += `
|
|
1528
1764
|
## Task Classification
|
|
1529
1765
|
`;
|
|
@@ -1537,8 +1773,8 @@ ${taskMd}
|
|
|
1537
1773
|
}
|
|
1538
1774
|
}
|
|
1539
1775
|
const specPath = path11.join(taskDir, "spec.md");
|
|
1540
|
-
if (
|
|
1541
|
-
const spec =
|
|
1776
|
+
if (fs13.existsSync(specPath)) {
|
|
1777
|
+
const spec = fs13.readFileSync(specPath, "utf-8");
|
|
1542
1778
|
const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
|
|
1543
1779
|
context += `
|
|
1544
1780
|
## Spec Summary
|
|
@@ -1546,8 +1782,8 @@ ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
|
|
|
1546
1782
|
`;
|
|
1547
1783
|
}
|
|
1548
1784
|
const planPath = path11.join(taskDir, "plan.md");
|
|
1549
|
-
if (
|
|
1550
|
-
const plan =
|
|
1785
|
+
if (fs13.existsSync(planPath)) {
|
|
1786
|
+
const plan = fs13.readFileSync(planPath, "utf-8");
|
|
1551
1787
|
const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
|
|
1552
1788
|
context += `
|
|
1553
1789
|
## Plan Summary
|
|
@@ -1555,8 +1791,8 @@ ${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
|
|
|
1555
1791
|
`;
|
|
1556
1792
|
}
|
|
1557
1793
|
const contextMdPath = path11.join(taskDir, "context.md");
|
|
1558
|
-
if (
|
|
1559
|
-
const accumulated =
|
|
1794
|
+
if (fs13.existsSync(contextMdPath)) {
|
|
1795
|
+
const accumulated = fs13.readFileSync(contextMdPath, "utf-8");
|
|
1560
1796
|
const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
|
|
1561
1797
|
const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
|
|
1562
1798
|
context += `
|
|
@@ -1572,12 +1808,22 @@ ${feedback}
|
|
|
1572
1808
|
}
|
|
1573
1809
|
return prompt.replace("{{TASK_CONTEXT}}", context);
|
|
1574
1810
|
}
|
|
1811
|
+
function inferHasUIFromScope(scope) {
|
|
1812
|
+
return scope.some((filePath) => {
|
|
1813
|
+
const ext = path11.extname(filePath).toLowerCase();
|
|
1814
|
+
if (UI_EXTENSIONS.has(ext)) return true;
|
|
1815
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
1816
|
+
return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1575
1819
|
function taskHasUI(taskDir) {
|
|
1576
1820
|
const taskJsonPath = path11.join(taskDir, "task.json");
|
|
1577
|
-
if (!
|
|
1821
|
+
if (!fs13.existsSync(taskJsonPath)) return true;
|
|
1578
1822
|
try {
|
|
1579
|
-
const taskDef = JSON.parse(
|
|
1580
|
-
|
|
1823
|
+
const taskDef = JSON.parse(fs13.readFileSync(taskJsonPath, "utf-8"));
|
|
1824
|
+
const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
|
|
1825
|
+
if (scope.length === 0) return true;
|
|
1826
|
+
return inferHasUIFromScope(scope);
|
|
1581
1827
|
} catch {
|
|
1582
1828
|
return true;
|
|
1583
1829
|
}
|
|
@@ -1697,8 +1943,8 @@ ${prompt}` : prompt;
|
|
|
1697
1943
|
if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
|
|
1698
1944
|
assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
|
|
1699
1945
|
const qaGuidePath = path11.join(projectDir, ".kody", "qa-guide.md");
|
|
1700
|
-
if (
|
|
1701
|
-
const qaGuide =
|
|
1946
|
+
if (fs13.existsSync(qaGuidePath)) {
|
|
1947
|
+
const qaGuide = fs13.readFileSync(qaGuidePath, "utf-8").trim();
|
|
1702
1948
|
assembled = assembled + "\n\n" + qaGuide;
|
|
1703
1949
|
}
|
|
1704
1950
|
}
|
|
@@ -1722,13 +1968,16 @@ ${prompt}` : prompt;
|
|
|
1722
1968
|
}
|
|
1723
1969
|
return assembled;
|
|
1724
1970
|
}
|
|
1971
|
+
function escalateModelTier(currentTier) {
|
|
1972
|
+
return TIER_ESCALATION[currentTier] ?? "strong";
|
|
1973
|
+
}
|
|
1725
1974
|
function resolveModel(modelTier, stageName) {
|
|
1726
1975
|
const config = getProjectConfig();
|
|
1727
1976
|
const mapped = config.agent.modelMap[modelTier];
|
|
1728
1977
|
if (mapped) return mapped;
|
|
1729
1978
|
return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
|
|
1730
1979
|
}
|
|
1731
|
-
var DEFAULT_MODEL_MAP, MAX_TASK_CONTEXT_PLAN, MAX_TASK_CONTEXT_SPEC, MAX_ACCUMULATED_CONTEXT;
|
|
1980
|
+
var DEFAULT_MODEL_MAP, MAX_TASK_CONTEXT_PLAN, MAX_TASK_CONTEXT_SPEC, MAX_ACCUMULATED_CONTEXT, UI_EXTENSIONS, UI_PATH_SEGMENTS, TIER_ESCALATION;
|
|
1732
1981
|
var init_context = __esm({
|
|
1733
1982
|
"src/context.ts"() {
|
|
1734
1983
|
"use strict";
|
|
@@ -1744,6 +1993,29 @@ var init_context = __esm({
|
|
|
1744
1993
|
MAX_TASK_CONTEXT_PLAN = 1500;
|
|
1745
1994
|
MAX_TASK_CONTEXT_SPEC = 2e3;
|
|
1746
1995
|
MAX_ACCUMULATED_CONTEXT = 4e3;
|
|
1996
|
+
UI_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1997
|
+
".tsx",
|
|
1998
|
+
".jsx",
|
|
1999
|
+
".vue",
|
|
2000
|
+
".svelte",
|
|
2001
|
+
".css",
|
|
2002
|
+
".scss",
|
|
2003
|
+
".sass",
|
|
2004
|
+
".less",
|
|
2005
|
+
".html"
|
|
2006
|
+
]);
|
|
2007
|
+
UI_PATH_SEGMENTS = [
|
|
2008
|
+
"/components/",
|
|
2009
|
+
"/pages/",
|
|
2010
|
+
"/layouts/",
|
|
2011
|
+
"/styles/",
|
|
2012
|
+
"/views/"
|
|
2013
|
+
];
|
|
2014
|
+
TIER_ESCALATION = {
|
|
2015
|
+
cheap: "mid",
|
|
2016
|
+
mid: "strong",
|
|
2017
|
+
strong: "strong"
|
|
2018
|
+
};
|
|
1747
2019
|
}
|
|
1748
2020
|
});
|
|
1749
2021
|
|
|
@@ -1767,7 +2039,7 @@ var init_runner_selection = __esm({
|
|
|
1767
2039
|
});
|
|
1768
2040
|
|
|
1769
2041
|
// src/stages/agent.ts
|
|
1770
|
-
import * as
|
|
2042
|
+
import * as fs14 from "fs";
|
|
1771
2043
|
import * as path12 from "path";
|
|
1772
2044
|
function getSessionInfo(stageName, sessions) {
|
|
1773
2045
|
const group = SESSION_GROUP[stageName];
|
|
@@ -1798,15 +2070,19 @@ async function executeAgentStage(ctx, def) {
|
|
|
1798
2070
|
return { outcome: "completed", retries: 0 };
|
|
1799
2071
|
}
|
|
1800
2072
|
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
|
|
1801
|
-
|
|
2073
|
+
let currentModelTier = def.modelTier;
|
|
1802
2074
|
if (ctx.input.feedback && def.name === "build") {
|
|
1803
2075
|
logger.info(` feedback: ${ctx.input.feedback.slice(0, 200)}${ctx.input.feedback.length > 200 ? "..." : ""}`);
|
|
1804
2076
|
}
|
|
1805
2077
|
const config = getProjectConfig();
|
|
2078
|
+
const sc = resolveStageConfig(config, def.name, def.modelTier);
|
|
2079
|
+
let model = sc.model;
|
|
2080
|
+
const useProxy = stageNeedsProxy(sc);
|
|
2081
|
+
const escalateEnabled = config.agent.escalateOnTimeout !== false;
|
|
1806
2082
|
const runnerName = config.agent.stageRunners?.[def.name] ?? config.agent.defaultRunner ?? Object.keys(ctx.runners)[0] ?? "claude";
|
|
1807
|
-
logger.info(` runner=${runnerName} model=${model} timeout=${def.timeout / 1e3}s`);
|
|
2083
|
+
logger.info(` runner=${runnerName} provider=${sc.provider} model=${model} timeout=${def.timeout / 1e3}s`);
|
|
1808
2084
|
const extraEnv = {};
|
|
1809
|
-
if (
|
|
2085
|
+
if (useProxy) {
|
|
1810
2086
|
extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
|
|
1811
2087
|
}
|
|
1812
2088
|
const sessions = ctx.sessions ?? {};
|
|
@@ -1820,37 +2096,58 @@ async function executeAgentStage(ctx, def) {
|
|
|
1820
2096
|
logger.info(` MCP servers enabled for ${def.name}`);
|
|
1821
2097
|
}
|
|
1822
2098
|
const runner = getRunnerForStage(ctx, def.name);
|
|
1823
|
-
const
|
|
2099
|
+
const maxRetries = def.maxRetries ?? 0;
|
|
2100
|
+
let lastResult = await runner.run(def.name, prompt, model, def.timeout, ctx.taskDir, {
|
|
1824
2101
|
cwd: ctx.projectDir,
|
|
1825
2102
|
env: extraEnv,
|
|
1826
2103
|
...sessionInfo,
|
|
1827
2104
|
mcpConfigJson
|
|
1828
2105
|
});
|
|
1829
|
-
|
|
1830
|
-
|
|
2106
|
+
let retries = 0;
|
|
2107
|
+
while (lastResult.outcome !== "completed" && retries < maxRetries) {
|
|
2108
|
+
retries++;
|
|
2109
|
+
const isTimeout = lastResult.outcome === "timed_out";
|
|
2110
|
+
if (isTimeout && escalateEnabled) {
|
|
2111
|
+
const nextTier = escalateModelTier(currentModelTier);
|
|
2112
|
+
if (nextTier !== currentModelTier) {
|
|
2113
|
+
logger.info(` Escalating model from ${currentModelTier} to ${nextTier} after timeout`);
|
|
2114
|
+
currentModelTier = nextTier;
|
|
2115
|
+
model = resolveModel(currentModelTier, def.name);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
logger.info(` retry ${retries}/${maxRetries} with model=${model}`);
|
|
2119
|
+
lastResult = await runner.run(def.name, prompt, model, def.timeout, ctx.taskDir, {
|
|
2120
|
+
cwd: ctx.projectDir,
|
|
2121
|
+
env: extraEnv,
|
|
2122
|
+
mcpConfigJson
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
if (lastResult.outcome !== "completed") {
|
|
2126
|
+
return { outcome: lastResult.outcome, error: lastResult.error, retries };
|
|
1831
2127
|
}
|
|
2128
|
+
const result = lastResult;
|
|
1832
2129
|
if (def.outputFile && result.output) {
|
|
1833
|
-
|
|
2130
|
+
fs14.writeFileSync(path12.join(ctx.taskDir, def.outputFile), result.output);
|
|
1834
2131
|
}
|
|
1835
2132
|
if (def.outputFile) {
|
|
1836
2133
|
const outputPath = path12.join(ctx.taskDir, def.outputFile);
|
|
1837
|
-
if (!
|
|
2134
|
+
if (!fs14.existsSync(outputPath)) {
|
|
1838
2135
|
const ext = path12.extname(def.outputFile);
|
|
1839
2136
|
const base = path12.basename(def.outputFile, ext);
|
|
1840
|
-
const files =
|
|
2137
|
+
const files = fs14.readdirSync(ctx.taskDir);
|
|
1841
2138
|
const variant = files.find(
|
|
1842
2139
|
(f) => f.startsWith(base + "-") && f.endsWith(ext)
|
|
1843
2140
|
);
|
|
1844
2141
|
if (variant) {
|
|
1845
|
-
|
|
2142
|
+
fs14.renameSync(path12.join(ctx.taskDir, variant), outputPath);
|
|
1846
2143
|
logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
|
|
1847
2144
|
}
|
|
1848
2145
|
}
|
|
1849
2146
|
}
|
|
1850
2147
|
if (def.outputFile) {
|
|
1851
2148
|
const outputPath = path12.join(ctx.taskDir, def.outputFile);
|
|
1852
|
-
if (
|
|
1853
|
-
const content =
|
|
2149
|
+
if (fs14.existsSync(outputPath)) {
|
|
2150
|
+
const content = fs14.readFileSync(outputPath, "utf-8");
|
|
1854
2151
|
const validation = validateStageOutput(def.name, content);
|
|
1855
2152
|
if (!validation.valid) {
|
|
1856
2153
|
if (def.name === "taskify") {
|
|
@@ -1864,7 +2161,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
1864
2161
|
const stripped = stripFences(retryResult.output);
|
|
1865
2162
|
const retryValidation = validateTaskJson(stripped);
|
|
1866
2163
|
if (retryValidation.valid) {
|
|
1867
|
-
|
|
2164
|
+
fs14.writeFileSync(outputPath, retryResult.output);
|
|
1868
2165
|
logger.info(` taskify retry produced valid JSON`);
|
|
1869
2166
|
} else {
|
|
1870
2167
|
logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
|
|
@@ -1875,10 +2172,9 @@ async function executeAgentStage(ctx, def) {
|
|
|
1875
2172
|
description: plainText.slice(0, 500),
|
|
1876
2173
|
scope: [],
|
|
1877
2174
|
risk_level: "low",
|
|
1878
|
-
hasUI: true,
|
|
1879
2175
|
questions: []
|
|
1880
2176
|
}, null, 2);
|
|
1881
|
-
|
|
2177
|
+
fs14.writeFileSync(outputPath, fallback);
|
|
1882
2178
|
logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
|
|
1883
2179
|
}
|
|
1884
2180
|
}
|
|
@@ -1889,7 +2185,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
1889
2185
|
}
|
|
1890
2186
|
}
|
|
1891
2187
|
appendStageContext(ctx.taskDir, def.name, result.output);
|
|
1892
|
-
return { outcome: "completed", outputFile: def.outputFile, retries
|
|
2188
|
+
return { outcome: "completed", outputFile: def.outputFile, retries };
|
|
1893
2189
|
}
|
|
1894
2190
|
function appendStageContext(taskDir, stageName, output) {
|
|
1895
2191
|
const contextPath = path12.join(taskDir, "context.md");
|
|
@@ -1905,7 +2201,7 @@ function appendStageContext(taskDir, stageName, output) {
|
|
|
1905
2201
|
### ${stageName} (${timestamp2})
|
|
1906
2202
|
${summary}
|
|
1907
2203
|
`;
|
|
1908
|
-
|
|
2204
|
+
fs14.appendFileSync(contextPath, entry);
|
|
1909
2205
|
}
|
|
1910
2206
|
var SESSION_GROUP;
|
|
1911
2207
|
var init_agent = __esm({
|
|
@@ -2144,7 +2440,7 @@ Error context:
|
|
|
2144
2440
|
});
|
|
2145
2441
|
|
|
2146
2442
|
// src/stages/gate.ts
|
|
2147
|
-
import * as
|
|
2443
|
+
import * as fs15 from "fs";
|
|
2148
2444
|
import * as path13 from "path";
|
|
2149
2445
|
function executeGateStage(ctx, def) {
|
|
2150
2446
|
if (ctx.input.dryRun) {
|
|
@@ -2188,7 +2484,7 @@ ${output}
|
|
|
2188
2484
|
`);
|
|
2189
2485
|
}
|
|
2190
2486
|
}
|
|
2191
|
-
|
|
2487
|
+
fs15.writeFileSync(path13.join(ctx.taskDir, "verify.md"), lines.join(""));
|
|
2192
2488
|
return {
|
|
2193
2489
|
outcome: verifyResult.pass ? "completed" : "failed",
|
|
2194
2490
|
retries: 0
|
|
@@ -2203,7 +2499,7 @@ var init_gate = __esm({
|
|
|
2203
2499
|
});
|
|
2204
2500
|
|
|
2205
2501
|
// src/stages/verify.ts
|
|
2206
|
-
import * as
|
|
2502
|
+
import * as fs16 from "fs";
|
|
2207
2503
|
import * as path14 from "path";
|
|
2208
2504
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
2209
2505
|
async function executeVerifyWithAutofix(ctx, def) {
|
|
@@ -2216,7 +2512,7 @@ async function executeVerifyWithAutofix(ctx, def) {
|
|
|
2216
2512
|
}
|
|
2217
2513
|
if (attempt < maxAttempts) {
|
|
2218
2514
|
const verifyPath = path14.join(ctx.taskDir, "verify.md");
|
|
2219
|
-
const errorOutput =
|
|
2515
|
+
const errorOutput = fs16.existsSync(verifyPath) ? fs16.readFileSync(verifyPath, "utf-8") : "Unknown error";
|
|
2220
2516
|
const modifiedFiles = getModifiedFiles(ctx.projectDir);
|
|
2221
2517
|
const defaultRunner = getRunnerForStage(ctx, "taskify");
|
|
2222
2518
|
const diagConfig = getProjectConfig();
|
|
@@ -2312,13 +2608,13 @@ var init_verify = __esm({
|
|
|
2312
2608
|
});
|
|
2313
2609
|
|
|
2314
2610
|
// src/cli/task-resolution.ts
|
|
2315
|
-
import * as
|
|
2611
|
+
import * as fs17 from "fs";
|
|
2316
2612
|
import * as path15 from "path";
|
|
2317
2613
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
2318
2614
|
function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
2319
2615
|
const tasksDir = path15.join(projectDir, ".kody", "tasks");
|
|
2320
|
-
if (!
|
|
2321
|
-
const allDirs =
|
|
2616
|
+
if (!fs17.existsSync(tasksDir)) return null;
|
|
2617
|
+
const allDirs = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
2322
2618
|
const prefix = `${issueNumber}-`;
|
|
2323
2619
|
const direct = allDirs.find((d) => d.startsWith(prefix));
|
|
2324
2620
|
if (direct) return direct;
|
|
@@ -2345,14 +2641,38 @@ function generateTaskId() {
|
|
|
2345
2641
|
const pad = (n) => String(n).padStart(2, "0");
|
|
2346
2642
|
return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
2347
2643
|
}
|
|
2644
|
+
function resolveTaskIdFromComments(issueNumber) {
|
|
2645
|
+
try {
|
|
2646
|
+
const comments = getIssueComments(issueNumber);
|
|
2647
|
+
const pattern = /pipeline started: `([^`]+)`/;
|
|
2648
|
+
let latestTaskId = null;
|
|
2649
|
+
for (const comment of comments) {
|
|
2650
|
+
const match = comment.body.match(pattern);
|
|
2651
|
+
if (match) {
|
|
2652
|
+
latestTaskId = match[1];
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
return latestTaskId;
|
|
2656
|
+
} catch {
|
|
2657
|
+
return null;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
function resolveTaskIdForCommand(issueNumber, projectDir) {
|
|
2661
|
+
const fromTasks = findLatestTaskForIssue(issueNumber, projectDir);
|
|
2662
|
+
if (fromTasks) return fromTasks;
|
|
2663
|
+
const fromComments = resolveTaskIdFromComments(issueNumber);
|
|
2664
|
+
if (fromComments) return fromComments;
|
|
2665
|
+
return null;
|
|
2666
|
+
}
|
|
2348
2667
|
var init_task_resolution = __esm({
|
|
2349
2668
|
"src/cli/task-resolution.ts"() {
|
|
2350
2669
|
"use strict";
|
|
2670
|
+
init_github_api();
|
|
2351
2671
|
}
|
|
2352
2672
|
});
|
|
2353
2673
|
|
|
2354
2674
|
// src/review-standalone.ts
|
|
2355
|
-
import * as
|
|
2675
|
+
import * as fs18 from "fs";
|
|
2356
2676
|
import * as path16 from "path";
|
|
2357
2677
|
function resolveReviewTarget(input) {
|
|
2358
2678
|
if (input.prs.length === 0) {
|
|
@@ -2378,16 +2698,34 @@ Or comment on the specific PR: \`@kody review\``
|
|
|
2378
2698
|
async function runStandaloneReview(input) {
|
|
2379
2699
|
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
2380
2700
|
const taskDir = path16.join(input.projectDir, ".kody", "tasks", taskId);
|
|
2381
|
-
|
|
2382
|
-
|
|
2701
|
+
fs18.mkdirSync(taskDir, { recursive: true });
|
|
2702
|
+
let diffInstruction = "";
|
|
2703
|
+
let filesChangedSection = "";
|
|
2704
|
+
if (input.baseBranch) {
|
|
2705
|
+
diffInstruction = `
|
|
2383
2706
|
|
|
2384
2707
|
## Diff Command
|
|
2385
2708
|
Run: \`git diff origin/${input.baseBranch}...HEAD\` to see the PR changes.
|
|
2386
|
-
Do NOT use bare \`git diff\` \u2014 it shows only uncommitted working tree changes, not the PR diff
|
|
2709
|
+
Do NOT use bare \`git diff\` \u2014 it shows only uncommitted working tree changes, not the PR diff.`;
|
|
2710
|
+
const diffFiles = getDiffFiles(input.baseBranch, input.projectDir);
|
|
2711
|
+
if (diffFiles.length > 0) {
|
|
2712
|
+
logger.info(`[review] Review scope: git diff origin/${input.baseBranch}...HEAD (${diffFiles.length} files)`);
|
|
2713
|
+
const fileList = diffFiles.map((f) => `- ${f}`).join("\n");
|
|
2714
|
+
filesChangedSection = `
|
|
2715
|
+
|
|
2716
|
+
## Files Changed
|
|
2717
|
+
Only review the following ${diffFiles.length} files (these are the files changed in this PR):
|
|
2718
|
+
${fileList}`;
|
|
2719
|
+
} else {
|
|
2720
|
+
logger.info(`[review] Review scope: git diff origin/${input.baseBranch}...HEAD (0 files)`);
|
|
2721
|
+
}
|
|
2722
|
+
} else {
|
|
2723
|
+
logger.warn(`[review] No baseBranch provided \u2014 reviewing all files (no diff scope)`);
|
|
2724
|
+
}
|
|
2387
2725
|
const taskContent = `# ${input.prTitle}
|
|
2388
2726
|
|
|
2389
|
-
${input.prBody ?? ""}${diffInstruction}`;
|
|
2390
|
-
|
|
2727
|
+
${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
2728
|
+
fs18.writeFileSync(path16.join(taskDir, "task.md"), taskContent);
|
|
2391
2729
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
2392
2730
|
const ctx = {
|
|
2393
2731
|
taskId,
|
|
@@ -2411,8 +2749,8 @@ ${input.prBody ?? ""}${diffInstruction}`;
|
|
|
2411
2749
|
}
|
|
2412
2750
|
const reviewPath = path16.join(taskDir, "review.md");
|
|
2413
2751
|
let reviewContent;
|
|
2414
|
-
if (
|
|
2415
|
-
reviewContent =
|
|
2752
|
+
if (fs18.existsSync(reviewPath)) {
|
|
2753
|
+
reviewContent = fs18.readFileSync(reviewPath, "utf-8");
|
|
2416
2754
|
}
|
|
2417
2755
|
return {
|
|
2418
2756
|
outcome: "completed",
|
|
@@ -2447,11 +2785,12 @@ var init_review_standalone = __esm({
|
|
|
2447
2785
|
init_agent();
|
|
2448
2786
|
init_task_resolution();
|
|
2449
2787
|
init_logger();
|
|
2788
|
+
init_git_utils();
|
|
2450
2789
|
}
|
|
2451
2790
|
});
|
|
2452
2791
|
|
|
2453
2792
|
// src/stages/review.ts
|
|
2454
|
-
import * as
|
|
2793
|
+
import * as fs19 from "fs";
|
|
2455
2794
|
import * as path17 from "path";
|
|
2456
2795
|
async function executeReviewWithFix(ctx, def) {
|
|
2457
2796
|
if (ctx.input.dryRun) {
|
|
@@ -2467,10 +2806,10 @@ async function executeReviewWithFix(ctx, def) {
|
|
|
2467
2806
|
return reviewResult;
|
|
2468
2807
|
}
|
|
2469
2808
|
const reviewFile = path17.join(ctx.taskDir, "review.md");
|
|
2470
|
-
if (!
|
|
2809
|
+
if (!fs19.existsSync(reviewFile)) {
|
|
2471
2810
|
return { outcome: "failed", retries: iteration, error: "review.md not found" };
|
|
2472
2811
|
}
|
|
2473
|
-
const content =
|
|
2812
|
+
const content = fs19.readFileSync(reviewFile, "utf-8");
|
|
2474
2813
|
if (detectReviewVerdict(content) !== "fail") {
|
|
2475
2814
|
return { ...reviewResult, retries: iteration };
|
|
2476
2815
|
}
|
|
@@ -2499,15 +2838,15 @@ var init_review = __esm({
|
|
|
2499
2838
|
});
|
|
2500
2839
|
|
|
2501
2840
|
// src/stages/ship.ts
|
|
2502
|
-
import * as
|
|
2841
|
+
import * as fs20 from "fs";
|
|
2503
2842
|
import * as path18 from "path";
|
|
2504
2843
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
2505
2844
|
function buildPrBody(ctx) {
|
|
2506
2845
|
const sections = [];
|
|
2507
2846
|
const taskJsonPath = path18.join(ctx.taskDir, "task.json");
|
|
2508
|
-
if (
|
|
2847
|
+
if (fs20.existsSync(taskJsonPath)) {
|
|
2509
2848
|
try {
|
|
2510
|
-
const raw =
|
|
2849
|
+
const raw = fs20.readFileSync(taskJsonPath, "utf-8");
|
|
2511
2850
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2512
2851
|
const task = JSON.parse(cleaned);
|
|
2513
2852
|
if (task.description) {
|
|
@@ -2527,8 +2866,8 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
2527
2866
|
}
|
|
2528
2867
|
}
|
|
2529
2868
|
const reviewPath = path18.join(ctx.taskDir, "review.md");
|
|
2530
|
-
if (
|
|
2531
|
-
const review =
|
|
2869
|
+
if (fs20.existsSync(reviewPath)) {
|
|
2870
|
+
const review = fs20.readFileSync(reviewPath, "utf-8");
|
|
2532
2871
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
2533
2872
|
if (summaryMatch) {
|
|
2534
2873
|
const summary = summaryMatch[1].trim();
|
|
@@ -2546,13 +2885,13 @@ ${summary}`);
|
|
|
2546
2885
|
}
|
|
2547
2886
|
}
|
|
2548
2887
|
const verifyPath = path18.join(ctx.taskDir, "verify.md");
|
|
2549
|
-
if (
|
|
2550
|
-
const verify =
|
|
2888
|
+
if (fs20.existsSync(verifyPath)) {
|
|
2889
|
+
const verify = fs20.readFileSync(verifyPath, "utf-8");
|
|
2551
2890
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
2552
2891
|
}
|
|
2553
2892
|
const planPath = path18.join(ctx.taskDir, "plan.md");
|
|
2554
|
-
if (
|
|
2555
|
-
const plan =
|
|
2893
|
+
if (fs20.existsSync(planPath)) {
|
|
2894
|
+
const plan = fs20.readFileSync(planPath, "utf-8").trim();
|
|
2556
2895
|
if (plan) {
|
|
2557
2896
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
2558
2897
|
sections.push(`
|
|
@@ -2574,11 +2913,11 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
2574
2913
|
function executeShipStage(ctx, _def) {
|
|
2575
2914
|
const shipPath = path18.join(ctx.taskDir, "ship.md");
|
|
2576
2915
|
if (ctx.input.dryRun) {
|
|
2577
|
-
|
|
2916
|
+
fs20.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
2578
2917
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2579
2918
|
}
|
|
2580
2919
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
2581
|
-
|
|
2920
|
+
fs20.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
2582
2921
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2583
2922
|
}
|
|
2584
2923
|
try {
|
|
@@ -2626,9 +2965,9 @@ function executeShipStage(ctx, _def) {
|
|
|
2626
2965
|
};
|
|
2627
2966
|
let prefix = "chore";
|
|
2628
2967
|
const taskJsonPath = path18.join(ctx.taskDir, "task.json");
|
|
2629
|
-
if (
|
|
2968
|
+
if (fs20.existsSync(taskJsonPath)) {
|
|
2630
2969
|
try {
|
|
2631
|
-
const raw =
|
|
2970
|
+
const raw = fs20.readFileSync(taskJsonPath, "utf-8");
|
|
2632
2971
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2633
2972
|
const task = JSON.parse(cleaned);
|
|
2634
2973
|
prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
@@ -2636,17 +2975,17 @@ function executeShipStage(ctx, _def) {
|
|
|
2636
2975
|
}
|
|
2637
2976
|
}
|
|
2638
2977
|
const taskMdPath = path18.join(ctx.taskDir, "task.md");
|
|
2639
|
-
if (
|
|
2640
|
-
const content =
|
|
2978
|
+
if (fs20.existsSync(taskMdPath)) {
|
|
2979
|
+
const content = fs20.readFileSync(taskMdPath, "utf-8");
|
|
2641
2980
|
const heading = content.split("\n").find((l) => l.startsWith("# "));
|
|
2642
2981
|
if (heading) {
|
|
2643
2982
|
title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
|
|
2644
2983
|
}
|
|
2645
2984
|
}
|
|
2646
2985
|
if (title === "Update") {
|
|
2647
|
-
if (
|
|
2986
|
+
if (fs20.existsSync(taskJsonPath)) {
|
|
2648
2987
|
try {
|
|
2649
|
-
const raw =
|
|
2988
|
+
const raw = fs20.readFileSync(taskJsonPath, "utf-8");
|
|
2650
2989
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2651
2990
|
const task = JSON.parse(cleaned);
|
|
2652
2991
|
if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
|
|
@@ -2669,7 +3008,7 @@ function executeShipStage(ctx, _def) {
|
|
|
2669
3008
|
} catch {
|
|
2670
3009
|
}
|
|
2671
3010
|
}
|
|
2672
|
-
|
|
3011
|
+
fs20.writeFileSync(shipPath, `# Ship
|
|
2673
3012
|
|
|
2674
3013
|
Updated existing PR: ${existingPr.url}
|
|
2675
3014
|
PR #${existingPr.number}
|
|
@@ -2690,20 +3029,20 @@ PR #${existingPr.number}
|
|
|
2690
3029
|
} catch {
|
|
2691
3030
|
}
|
|
2692
3031
|
}
|
|
2693
|
-
|
|
3032
|
+
fs20.writeFileSync(shipPath, `# Ship
|
|
2694
3033
|
|
|
2695
3034
|
PR created: ${pr.url}
|
|
2696
3035
|
PR #${pr.number}
|
|
2697
3036
|
`);
|
|
2698
3037
|
} else {
|
|
2699
|
-
|
|
3038
|
+
fs20.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
2700
3039
|
}
|
|
2701
3040
|
}
|
|
2702
3041
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2703
3042
|
} catch (err) {
|
|
2704
3043
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2705
3044
|
try {
|
|
2706
|
-
|
|
3045
|
+
fs20.writeFileSync(shipPath, `# Ship
|
|
2707
3046
|
|
|
2708
3047
|
Failed: ${msg}
|
|
2709
3048
|
`);
|
|
@@ -2752,15 +3091,15 @@ var init_executor_registry = __esm({
|
|
|
2752
3091
|
});
|
|
2753
3092
|
|
|
2754
3093
|
// src/pipeline/questions.ts
|
|
2755
|
-
import * as
|
|
3094
|
+
import * as fs21 from "fs";
|
|
2756
3095
|
import * as path19 from "path";
|
|
2757
3096
|
function checkForQuestions(ctx, stageName) {
|
|
2758
3097
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
2759
3098
|
try {
|
|
2760
3099
|
if (stageName === "taskify") {
|
|
2761
3100
|
const taskJsonPath = path19.join(ctx.taskDir, "task.json");
|
|
2762
|
-
if (!
|
|
2763
|
-
const raw =
|
|
3101
|
+
if (!fs21.existsSync(taskJsonPath)) return false;
|
|
3102
|
+
const raw = fs21.readFileSync(taskJsonPath, "utf-8");
|
|
2764
3103
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2765
3104
|
const taskJson = JSON.parse(cleaned);
|
|
2766
3105
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -2776,8 +3115,8 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
2776
3115
|
}
|
|
2777
3116
|
if (stageName === "plan") {
|
|
2778
3117
|
const planPath = path19.join(ctx.taskDir, "plan.md");
|
|
2779
|
-
if (!
|
|
2780
|
-
const plan =
|
|
3118
|
+
if (!fs21.existsSync(planPath)) return false;
|
|
3119
|
+
const plan = fs21.readFileSync(planPath, "utf-8");
|
|
2781
3120
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
2782
3121
|
if (questionsMatch) {
|
|
2783
3122
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -2806,7 +3145,7 @@ var init_questions = __esm({
|
|
|
2806
3145
|
});
|
|
2807
3146
|
|
|
2808
3147
|
// src/pipeline/hooks.ts
|
|
2809
|
-
import * as
|
|
3148
|
+
import * as fs22 from "fs";
|
|
2810
3149
|
import * as path20 from "path";
|
|
2811
3150
|
function applyPreStageLabel(ctx, def) {
|
|
2812
3151
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
@@ -2846,8 +3185,8 @@ function autoDetectComplexity(ctx, def) {
|
|
|
2846
3185
|
}
|
|
2847
3186
|
try {
|
|
2848
3187
|
const taskJsonPath = path20.join(ctx.taskDir, "task.json");
|
|
2849
|
-
if (!
|
|
2850
|
-
const raw =
|
|
3188
|
+
if (!fs22.existsSync(taskJsonPath)) return null;
|
|
3189
|
+
const raw = fs22.readFileSync(taskJsonPath, "utf-8");
|
|
2851
3190
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2852
3191
|
const taskJson = JSON.parse(cleaned);
|
|
2853
3192
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -2878,7 +3217,7 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
2878
3217
|
if (ctx.input.mode === "rerun") return null;
|
|
2879
3218
|
if (!ctx.input.issueNumber) return null;
|
|
2880
3219
|
const planPath = path20.join(ctx.taskDir, "plan.md");
|
|
2881
|
-
const plan =
|
|
3220
|
+
const plan = fs22.existsSync(planPath) ? fs22.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
|
|
2882
3221
|
try {
|
|
2883
3222
|
postComment(
|
|
2884
3223
|
ctx.input.issueNumber,
|
|
@@ -2945,7 +3284,7 @@ var init_hooks = __esm({
|
|
|
2945
3284
|
});
|
|
2946
3285
|
|
|
2947
3286
|
// src/learning/auto-learn.ts
|
|
2948
|
-
import * as
|
|
3287
|
+
import * as fs23 from "fs";
|
|
2949
3288
|
import * as path21 from "path";
|
|
2950
3289
|
function stripAnsi(str) {
|
|
2951
3290
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -2953,14 +3292,14 @@ function stripAnsi(str) {
|
|
|
2953
3292
|
function autoLearn(ctx) {
|
|
2954
3293
|
try {
|
|
2955
3294
|
const memoryDir = path21.join(ctx.projectDir, ".kody", "memory");
|
|
2956
|
-
if (!
|
|
2957
|
-
|
|
3295
|
+
if (!fs23.existsSync(memoryDir)) {
|
|
3296
|
+
fs23.mkdirSync(memoryDir, { recursive: true });
|
|
2958
3297
|
}
|
|
2959
3298
|
const learnings = [];
|
|
2960
3299
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2961
3300
|
const verifyPath = path21.join(ctx.taskDir, "verify.md");
|
|
2962
|
-
if (
|
|
2963
|
-
const verify = stripAnsi(
|
|
3301
|
+
if (fs23.existsSync(verifyPath)) {
|
|
3302
|
+
const verify = stripAnsi(fs23.readFileSync(verifyPath, "utf-8"));
|
|
2964
3303
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
2965
3304
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
2966
3305
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -2970,17 +3309,17 @@ function autoLearn(ctx) {
|
|
|
2970
3309
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
2971
3310
|
}
|
|
2972
3311
|
const reviewPath = path21.join(ctx.taskDir, "review.md");
|
|
2973
|
-
if (
|
|
2974
|
-
const review =
|
|
3312
|
+
if (fs23.existsSync(reviewPath)) {
|
|
3313
|
+
const review = fs23.readFileSync(reviewPath, "utf-8");
|
|
2975
3314
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
2976
3315
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
2977
3316
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
2978
3317
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
2979
3318
|
}
|
|
2980
3319
|
const taskJsonPath = path21.join(ctx.taskDir, "task.json");
|
|
2981
|
-
if (
|
|
3320
|
+
if (fs23.existsSync(taskJsonPath)) {
|
|
2982
3321
|
try {
|
|
2983
|
-
const raw = stripAnsi(
|
|
3322
|
+
const raw = stripAnsi(fs23.readFileSync(taskJsonPath, "utf-8"));
|
|
2984
3323
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2985
3324
|
const task = JSON.parse(cleaned);
|
|
2986
3325
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -2996,7 +3335,7 @@ function autoLearn(ctx) {
|
|
|
2996
3335
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
2997
3336
|
${learnings.join("\n")}
|
|
2998
3337
|
`;
|
|
2999
|
-
|
|
3338
|
+
fs23.appendFileSync(conventionsPath, entry);
|
|
3000
3339
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
3001
3340
|
}
|
|
3002
3341
|
autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
|
|
@@ -3005,7 +3344,7 @@ ${learnings.join("\n")}
|
|
|
3005
3344
|
}
|
|
3006
3345
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
3007
3346
|
const archPath = path21.join(memoryDir, "architecture.md");
|
|
3008
|
-
if (
|
|
3347
|
+
if (fs23.existsSync(archPath)) return;
|
|
3009
3348
|
const detected = detectArchitectureBasic(projectDir);
|
|
3010
3349
|
if (detected.length > 0) {
|
|
3011
3350
|
const content = `# Architecture (auto-detected ${timestamp2})
|
|
@@ -3013,7 +3352,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
3013
3352
|
## Overview
|
|
3014
3353
|
${detected.join("\n")}
|
|
3015
3354
|
`;
|
|
3016
|
-
|
|
3355
|
+
fs23.writeFileSync(archPath, content);
|
|
3017
3356
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
3018
3357
|
}
|
|
3019
3358
|
}
|
|
@@ -3026,13 +3365,13 @@ var init_auto_learn = __esm({
|
|
|
3026
3365
|
});
|
|
3027
3366
|
|
|
3028
3367
|
// src/retrospective.ts
|
|
3029
|
-
import * as
|
|
3368
|
+
import * as fs24 from "fs";
|
|
3030
3369
|
import * as path22 from "path";
|
|
3031
3370
|
function readArtifact(taskDir, filename, maxChars) {
|
|
3032
3371
|
const p = path22.join(taskDir, filename);
|
|
3033
|
-
if (!
|
|
3372
|
+
if (!fs24.existsSync(p)) return null;
|
|
3034
3373
|
try {
|
|
3035
|
-
const content =
|
|
3374
|
+
const content = fs24.readFileSync(p, "utf-8");
|
|
3036
3375
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
3037
3376
|
} catch {
|
|
3038
3377
|
return null;
|
|
@@ -3089,9 +3428,9 @@ function getLogPath(projectDir) {
|
|
|
3089
3428
|
}
|
|
3090
3429
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
3091
3430
|
const logPath = getLogPath(projectDir);
|
|
3092
|
-
if (!
|
|
3431
|
+
if (!fs24.existsSync(logPath)) return [];
|
|
3093
3432
|
try {
|
|
3094
|
-
const content =
|
|
3433
|
+
const content = fs24.readFileSync(logPath, "utf-8");
|
|
3095
3434
|
const lines = content.split("\n").filter(Boolean);
|
|
3096
3435
|
const entries = [];
|
|
3097
3436
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -3119,10 +3458,10 @@ function formatPreviousEntries(entries) {
|
|
|
3119
3458
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
3120
3459
|
const logPath = getLogPath(projectDir);
|
|
3121
3460
|
const dir = path22.dirname(logPath);
|
|
3122
|
-
if (!
|
|
3123
|
-
|
|
3461
|
+
if (!fs24.existsSync(dir)) {
|
|
3462
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
3124
3463
|
}
|
|
3125
|
-
|
|
3464
|
+
fs24.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
3126
3465
|
}
|
|
3127
3466
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
3128
3467
|
if (ctx.input.dryRun) return;
|
|
@@ -3232,8 +3571,65 @@ If no pipeline flaw is detected, set "pipelineFlaw" to null.
|
|
|
3232
3571
|
}
|
|
3233
3572
|
});
|
|
3234
3573
|
|
|
3574
|
+
// src/pipeline/summary.ts
|
|
3575
|
+
function formatDuration(ms) {
|
|
3576
|
+
const totalSec = Math.round(ms / 1e3);
|
|
3577
|
+
if (totalSec < 60) return `${totalSec}s`;
|
|
3578
|
+
const min = Math.floor(totalSec / 60);
|
|
3579
|
+
const sec = totalSec % 60;
|
|
3580
|
+
return `${min}m ${sec}s`;
|
|
3581
|
+
}
|
|
3582
|
+
function stageDuration(stage) {
|
|
3583
|
+
if (!stage.startedAt) return "-";
|
|
3584
|
+
const start = new Date(stage.startedAt).getTime();
|
|
3585
|
+
const end = stage.completedAt ? new Date(stage.completedAt).getTime() : Date.now();
|
|
3586
|
+
if (isNaN(start) || isNaN(end)) return "-";
|
|
3587
|
+
return formatDuration(end - start);
|
|
3588
|
+
}
|
|
3589
|
+
function formatPipelineSummary(state, options) {
|
|
3590
|
+
const lines = [];
|
|
3591
|
+
lines.push(`## Pipeline Summary: \`${state.taskId}\``);
|
|
3592
|
+
lines.push("");
|
|
3593
|
+
lines.push("| Stage | Status | Duration | Retries |");
|
|
3594
|
+
lines.push("|-------|--------|----------|---------|");
|
|
3595
|
+
for (const def of STAGES) {
|
|
3596
|
+
const s = state.stages[def.name];
|
|
3597
|
+
if (!s) continue;
|
|
3598
|
+
const status = STATUS_ICONS[s.state] ?? s.state;
|
|
3599
|
+
const duration = stageDuration(s);
|
|
3600
|
+
const retries = s.retries ?? 0;
|
|
3601
|
+
lines.push(`| ${def.name} | ${status} | ${duration} | ${retries} |`);
|
|
3602
|
+
}
|
|
3603
|
+
const totalMs = new Date(state.updatedAt).getTime() - new Date(state.createdAt).getTime();
|
|
3604
|
+
const totalStr = isNaN(totalMs) || totalMs < 0 ? "-" : formatDuration(totalMs);
|
|
3605
|
+
lines.push("");
|
|
3606
|
+
const footerParts = [`**Total:** ${totalStr}`];
|
|
3607
|
+
if (options?.complexity) {
|
|
3608
|
+
footerParts.push(`**Complexity:** ${options.complexity}`);
|
|
3609
|
+
}
|
|
3610
|
+
if (options?.model) {
|
|
3611
|
+
footerParts.push(`**Model:** ${options.model}`);
|
|
3612
|
+
}
|
|
3613
|
+
lines.push(footerParts.join(" | "));
|
|
3614
|
+
return lines.join("\n");
|
|
3615
|
+
}
|
|
3616
|
+
var STATUS_ICONS;
|
|
3617
|
+
var init_summary = __esm({
|
|
3618
|
+
"src/pipeline/summary.ts"() {
|
|
3619
|
+
"use strict";
|
|
3620
|
+
init_definitions();
|
|
3621
|
+
STATUS_ICONS = {
|
|
3622
|
+
completed: "completed",
|
|
3623
|
+
failed: "failed",
|
|
3624
|
+
timeout: "timeout",
|
|
3625
|
+
running: "running",
|
|
3626
|
+
pending: "pending"
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3629
|
+
});
|
|
3630
|
+
|
|
3235
3631
|
// src/pipeline.ts
|
|
3236
|
-
import * as
|
|
3632
|
+
import * as fs25 from "fs";
|
|
3237
3633
|
import * as path23 from "path";
|
|
3238
3634
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
3239
3635
|
if (ctx.input.dryRun) return;
|
|
@@ -3248,7 +3644,7 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
3248
3644
|
if (!ctx.input.issueNumber) return;
|
|
3249
3645
|
try {
|
|
3250
3646
|
const taskMdPath = path23.join(ctx.taskDir, "task.md");
|
|
3251
|
-
const title =
|
|
3647
|
+
const title = fs25.existsSync(taskMdPath) ? fs25.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
3252
3648
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
3253
3649
|
syncWithDefault(ctx.projectDir);
|
|
3254
3650
|
} catch (err) {
|
|
@@ -3263,9 +3659,9 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
3263
3659
|
}
|
|
3264
3660
|
function acquireLock(taskDir) {
|
|
3265
3661
|
const lockPath = path23.join(taskDir, ".lock");
|
|
3266
|
-
if (
|
|
3662
|
+
if (fs25.existsSync(lockPath)) {
|
|
3267
3663
|
try {
|
|
3268
|
-
const pid = parseInt(
|
|
3664
|
+
const pid = parseInt(fs25.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
3269
3665
|
if (!isNaN(pid)) {
|
|
3270
3666
|
try {
|
|
3271
3667
|
process.kill(pid, 0);
|
|
@@ -3282,14 +3678,14 @@ function acquireLock(taskDir) {
|
|
|
3282
3678
|
logger.warn(` Corrupt lock file \u2014 overwriting`);
|
|
3283
3679
|
}
|
|
3284
3680
|
try {
|
|
3285
|
-
|
|
3681
|
+
fs25.unlinkSync(lockPath);
|
|
3286
3682
|
} catch {
|
|
3287
3683
|
}
|
|
3288
3684
|
}
|
|
3289
3685
|
try {
|
|
3290
|
-
const fd =
|
|
3291
|
-
|
|
3292
|
-
|
|
3686
|
+
const fd = fs25.openSync(lockPath, fs25.constants.O_WRONLY | fs25.constants.O_CREAT | fs25.constants.O_EXCL);
|
|
3687
|
+
fs25.writeSync(fd, String(process.pid));
|
|
3688
|
+
fs25.closeSync(fd);
|
|
3293
3689
|
} catch (err) {
|
|
3294
3690
|
if (err.code === "EEXIST") {
|
|
3295
3691
|
throw new Error("Pipeline already running (lock acquired by another process)");
|
|
@@ -3299,7 +3695,7 @@ function acquireLock(taskDir) {
|
|
|
3299
3695
|
}
|
|
3300
3696
|
function releaseLock(taskDir) {
|
|
3301
3697
|
try {
|
|
3302
|
-
|
|
3698
|
+
fs25.unlinkSync(path23.join(taskDir, ".lock"));
|
|
3303
3699
|
} catch {
|
|
3304
3700
|
}
|
|
3305
3701
|
}
|
|
@@ -3449,6 +3845,24 @@ async function runPipelineInner(ctx) {
|
|
|
3449
3845
|
await runRetrospective(ctx, state, pipelineStartTime).catch((err) => {
|
|
3450
3846
|
logger.warn(` Retrospective failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3451
3847
|
});
|
|
3848
|
+
if (ctx.input.issueNumber && !ctx.input.dryRun) {
|
|
3849
|
+
const config = getProjectConfig();
|
|
3850
|
+
const isCI3 = !!process.env.GITHUB_ACTIONS;
|
|
3851
|
+
const shouldPost = config.github.postSummary ?? (isCI3 ? true : false);
|
|
3852
|
+
if (shouldPost) {
|
|
3853
|
+
try {
|
|
3854
|
+
const summaryOpts = {};
|
|
3855
|
+
if (complexity) summaryOpts.complexity = complexity;
|
|
3856
|
+
const modelMap = config.agent?.modelMap;
|
|
3857
|
+
if (modelMap?.mid) summaryOpts.model = modelMap.mid;
|
|
3858
|
+
const summary = formatPipelineSummary(state, summaryOpts);
|
|
3859
|
+
postComment(ctx.input.issueNumber, summary);
|
|
3860
|
+
logger.info("Pipeline summary posted on issue");
|
|
3861
|
+
} catch (err) {
|
|
3862
|
+
logger.warn(` Failed to post pipeline summary: ${err instanceof Error ? err.message : String(err)}`);
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3452
3866
|
return state;
|
|
3453
3867
|
}
|
|
3454
3868
|
function printStatus(taskId, taskDir) {
|
|
@@ -3483,12 +3897,14 @@ var init_pipeline = __esm({
|
|
|
3483
3897
|
init_hooks();
|
|
3484
3898
|
init_auto_learn();
|
|
3485
3899
|
init_retrospective();
|
|
3900
|
+
init_summary();
|
|
3901
|
+
init_config();
|
|
3486
3902
|
}
|
|
3487
3903
|
});
|
|
3488
3904
|
|
|
3489
3905
|
// src/preflight.ts
|
|
3490
3906
|
import { execFileSync as execFileSync14 } from "child_process";
|
|
3491
|
-
import * as
|
|
3907
|
+
import * as fs26 from "fs";
|
|
3492
3908
|
function check(name, fn) {
|
|
3493
3909
|
try {
|
|
3494
3910
|
const detail = fn() ?? void 0;
|
|
@@ -3541,7 +3957,7 @@ function runPreflight() {
|
|
|
3541
3957
|
return v;
|
|
3542
3958
|
}),
|
|
3543
3959
|
check("package.json", () => {
|
|
3544
|
-
if (!
|
|
3960
|
+
if (!fs26.existsSync("package.json")) throw new Error("not found");
|
|
3545
3961
|
})
|
|
3546
3962
|
];
|
|
3547
3963
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -3618,7 +4034,7 @@ var init_args = __esm({
|
|
|
3618
4034
|
});
|
|
3619
4035
|
|
|
3620
4036
|
// src/cli/litellm.ts
|
|
3621
|
-
import * as
|
|
4037
|
+
import * as fs27 from "fs";
|
|
3622
4038
|
import * as os from "os";
|
|
3623
4039
|
import * as path24 from "path";
|
|
3624
4040
|
import { execFileSync as execFileSync15 } from "child_process";
|
|
@@ -3676,13 +4092,40 @@ function generateLitellmConfig(provider, modelMap) {
|
|
|
3676
4092
|
}
|
|
3677
4093
|
return entries.join("\n") + "\n";
|
|
3678
4094
|
}
|
|
4095
|
+
function generateLitellmConfigFromStages(defaultConfig, stages) {
|
|
4096
|
+
const proxyModels = [];
|
|
4097
|
+
if (defaultConfig && defaultConfig.provider !== "claude" && defaultConfig.provider !== "anthropic") {
|
|
4098
|
+
proxyModels.push(defaultConfig);
|
|
4099
|
+
}
|
|
4100
|
+
if (stages) {
|
|
4101
|
+
for (const sc of Object.values(stages)) {
|
|
4102
|
+
if (sc.provider !== "claude" && sc.provider !== "anthropic") {
|
|
4103
|
+
proxyModels.push(sc);
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
if (proxyModels.length === 0) return void 0;
|
|
4108
|
+
const entries = ["model_list:"];
|
|
4109
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4110
|
+
for (const { provider, model } of proxyModels) {
|
|
4111
|
+
const key = `${provider}/${model}`;
|
|
4112
|
+
if (seen.has(key)) continue;
|
|
4113
|
+
seen.add(key);
|
|
4114
|
+
const apiKeyVar = providerApiKeyEnvVar(provider);
|
|
4115
|
+
entries.push(` - model_name: ${model}`);
|
|
4116
|
+
entries.push(` litellm_params:`);
|
|
4117
|
+
entries.push(` model: ${provider}/${model}`);
|
|
4118
|
+
entries.push(` api_key: os.environ/${apiKeyVar}`);
|
|
4119
|
+
}
|
|
4120
|
+
return entries.join("\n") + "\n";
|
|
4121
|
+
}
|
|
3679
4122
|
async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
3680
4123
|
if (!generatedConfig) {
|
|
3681
4124
|
logger.warn("No provider configured in kody.config.json \u2014 cannot start LiteLLM proxy");
|
|
3682
4125
|
return null;
|
|
3683
4126
|
}
|
|
3684
4127
|
const configPath = path24.join(os.tmpdir(), "kody-litellm-config.yaml");
|
|
3685
|
-
|
|
4128
|
+
fs27.writeFileSync(configPath, generatedConfig);
|
|
3686
4129
|
const portMatch = url.match(/:(\d+)/);
|
|
3687
4130
|
const port = portMatch ? portMatch[1] : "4000";
|
|
3688
4131
|
let litellmFound = false;
|
|
@@ -3706,15 +4149,15 @@ async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
|
3706
4149
|
try {
|
|
3707
4150
|
execFileSync15("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
3708
4151
|
cmd = "litellm";
|
|
3709
|
-
args2 = ["--config", configPath, "--port", port];
|
|
4152
|
+
args2 = ["--config", configPath, "--port", port, "--no_db"];
|
|
3710
4153
|
} catch {
|
|
3711
4154
|
cmd = "python3";
|
|
3712
|
-
args2 = ["-m", "litellm", "--config", configPath, "--port", port];
|
|
4155
|
+
args2 = ["-m", "litellm", "--config", configPath, "--port", port, "--no_db"];
|
|
3713
4156
|
}
|
|
3714
4157
|
const dotenvPath = path24.join(projectDir, ".env");
|
|
3715
4158
|
const dotenvVars = {};
|
|
3716
|
-
if (
|
|
3717
|
-
for (const rawLine of
|
|
4159
|
+
if (fs27.existsSync(dotenvPath)) {
|
|
4160
|
+
for (const rawLine of fs27.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
3718
4161
|
const line = rawLine.trim();
|
|
3719
4162
|
if (!line || line.startsWith("#")) continue;
|
|
3720
4163
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -3765,7 +4208,7 @@ var init_litellm = __esm({
|
|
|
3765
4208
|
});
|
|
3766
4209
|
|
|
3767
4210
|
// src/cli/task-state.ts
|
|
3768
|
-
import * as
|
|
4211
|
+
import * as fs28 from "fs";
|
|
3769
4212
|
import * as path25 from "path";
|
|
3770
4213
|
function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
3771
4214
|
if (!existingTaskId || !existingState) {
|
|
@@ -3800,9 +4243,9 @@ function resolveForIssue(issueNumber, projectDir) {
|
|
|
3800
4243
|
if (existingTaskId) {
|
|
3801
4244
|
const statusPath = path25.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
3802
4245
|
let existingState = null;
|
|
3803
|
-
if (
|
|
4246
|
+
if (fs28.existsSync(statusPath)) {
|
|
3804
4247
|
try {
|
|
3805
|
-
existingState = JSON.parse(
|
|
4248
|
+
existingState = JSON.parse(fs28.readFileSync(statusPath, "utf-8"));
|
|
3806
4249
|
} catch {
|
|
3807
4250
|
}
|
|
3808
4251
|
}
|
|
@@ -3959,10 +4402,10 @@ var init_resolve = __esm({
|
|
|
3959
4402
|
|
|
3960
4403
|
// src/entry.ts
|
|
3961
4404
|
var entry_exports = {};
|
|
3962
|
-
import * as
|
|
4405
|
+
import * as fs29 from "fs";
|
|
3963
4406
|
import * as path26 from "path";
|
|
3964
4407
|
async function ensureLitellmProxy(config, projectDir) {
|
|
3965
|
-
if (!
|
|
4408
|
+
if (!anyStageNeedsProxy(config)) return null;
|
|
3966
4409
|
const litellmUrl = getLitellmUrl();
|
|
3967
4410
|
const proxyRunning = await checkLitellmHealth(litellmUrl);
|
|
3968
4411
|
let litellmProcess = null;
|
|
@@ -3975,7 +4418,9 @@ async function ensureLitellmProxy(config, projectDir) {
|
|
|
3975
4418
|
}
|
|
3976
4419
|
}
|
|
3977
4420
|
let generatedConfig;
|
|
3978
|
-
if (config.agent.
|
|
4421
|
+
if (config.agent.stages || config.agent.default) {
|
|
4422
|
+
generatedConfig = generateLitellmConfigFromStages(config.agent.default, config.agent.stages);
|
|
4423
|
+
} else if (config.agent.provider && config.agent.provider !== "anthropic") {
|
|
3979
4424
|
generatedConfig = generateLitellmConfig(config.agent.provider, config.agent.modelMap);
|
|
3980
4425
|
}
|
|
3981
4426
|
litellmProcess = await tryStartLitellm(litellmUrl, projectDir, generatedConfig);
|
|
@@ -3986,9 +4431,8 @@ async function ensureLitellmProxy(config, projectDir) {
|
|
|
3986
4431
|
} else {
|
|
3987
4432
|
logger.info(`LiteLLM proxy already running at ${litellmUrl}`);
|
|
3988
4433
|
}
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
if (!process.env.ANTHROPIC_API_KEY || !process.env.ANTHROPIC_API_KEY.startsWith("sk-ant-")) {
|
|
4434
|
+
logger.info(`LiteLLM proxy available at ${litellmUrl}`);
|
|
4435
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
3992
4436
|
process.env.ANTHROPIC_API_KEY = `sk-ant-api03-${"0".repeat(64)}`;
|
|
3993
4437
|
}
|
|
3994
4438
|
return litellmProcess;
|
|
@@ -4016,7 +4460,7 @@ async function main() {
|
|
|
4016
4460
|
const input = parseArgs();
|
|
4017
4461
|
const projectDir = input.cwd ? path26.resolve(input.cwd) : process.cwd();
|
|
4018
4462
|
if (input.cwd) {
|
|
4019
|
-
if (!
|
|
4463
|
+
if (!fs29.existsSync(projectDir)) {
|
|
4020
4464
|
console.error(`--cwd path does not exist: ${projectDir}`);
|
|
4021
4465
|
process.exit(1);
|
|
4022
4466
|
}
|
|
@@ -4060,7 +4504,16 @@ async function main() {
|
|
|
4060
4504
|
}
|
|
4061
4505
|
let taskId = input.taskId;
|
|
4062
4506
|
if (!taskId) {
|
|
4063
|
-
if (
|
|
4507
|
+
if ((input.command === "rerun" || input.command === "status") && input.issueNumber) {
|
|
4508
|
+
const resolved = resolveTaskIdForCommand(input.issueNumber, projectDir);
|
|
4509
|
+
if (resolved) {
|
|
4510
|
+
taskId = resolved;
|
|
4511
|
+
logger.info(`Auto-resolved task-id: ${taskId} (from issue #${input.issueNumber})`);
|
|
4512
|
+
} else {
|
|
4513
|
+
console.error(`No task found for issue #${input.issueNumber}. Provide --task-id explicitly.`);
|
|
4514
|
+
process.exit(1);
|
|
4515
|
+
}
|
|
4516
|
+
} else if (isPRFix) {
|
|
4064
4517
|
taskId = `${input.command === "fix-ci" ? "fixci" : "fix"}-pr-${input.prNumber}-${generateTaskId()}`;
|
|
4065
4518
|
} else if (input.issueNumber) {
|
|
4066
4519
|
taskId = `${input.issueNumber}-${generateTaskId()}`;
|
|
@@ -4074,7 +4527,7 @@ async function main() {
|
|
|
4074
4527
|
}
|
|
4075
4528
|
}
|
|
4076
4529
|
const taskDir = path26.join(projectDir, ".kody", "tasks", taskId);
|
|
4077
|
-
|
|
4530
|
+
fs29.mkdirSync(taskDir, { recursive: true });
|
|
4078
4531
|
if (input.command === "status") {
|
|
4079
4532
|
printStatus(taskId, taskDir);
|
|
4080
4533
|
return;
|
|
@@ -4190,31 +4643,31 @@ async function main() {
|
|
|
4190
4643
|
logger.info("Preflight checks:");
|
|
4191
4644
|
runPreflight();
|
|
4192
4645
|
if (input.task) {
|
|
4193
|
-
|
|
4646
|
+
fs29.writeFileSync(path26.join(taskDir, "task.md"), input.task);
|
|
4194
4647
|
}
|
|
4195
4648
|
const taskMdPath = path26.join(taskDir, "task.md");
|
|
4196
|
-
if (!
|
|
4649
|
+
if (!fs29.existsSync(taskMdPath) && isPRFix && input.prNumber) {
|
|
4197
4650
|
logger.info(`Fetching PR #${input.prNumber} details as task context...`);
|
|
4198
4651
|
const prDetails = getPRDetails(input.prNumber);
|
|
4199
4652
|
if (prDetails) {
|
|
4200
4653
|
const taskContent = `# ${prDetails.title}
|
|
4201
4654
|
|
|
4202
4655
|
${prDetails.body ?? ""}`;
|
|
4203
|
-
|
|
4656
|
+
fs29.writeFileSync(taskMdPath, taskContent);
|
|
4204
4657
|
logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
|
|
4205
4658
|
}
|
|
4206
|
-
} else if (!
|
|
4659
|
+
} else if (!fs29.existsSync(taskMdPath) && input.issueNumber) {
|
|
4207
4660
|
logger.info(`Fetching issue #${input.issueNumber} body as task...`);
|
|
4208
4661
|
const issue = getIssue(input.issueNumber);
|
|
4209
4662
|
if (issue) {
|
|
4210
4663
|
const taskContent = `# ${issue.title}
|
|
4211
4664
|
|
|
4212
4665
|
${issue.body ?? ""}`;
|
|
4213
|
-
|
|
4666
|
+
fs29.writeFileSync(taskMdPath, taskContent);
|
|
4214
4667
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
4215
4668
|
}
|
|
4216
4669
|
}
|
|
4217
|
-
if (!
|
|
4670
|
+
if (!fs29.existsSync(taskMdPath)) {
|
|
4218
4671
|
console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
|
|
4219
4672
|
process.exit(1);
|
|
4220
4673
|
}
|
|
@@ -4352,7 +4805,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
|
|
|
4352
4805
|
}
|
|
4353
4806
|
}
|
|
4354
4807
|
const state = await runPipeline(ctx);
|
|
4355
|
-
const files =
|
|
4808
|
+
const files = fs29.readdirSync(taskDir);
|
|
4356
4809
|
console.log(`
|
|
4357
4810
|
Artifacts in ${taskDir}:`);
|
|
4358
4811
|
for (const f of files) {
|
|
@@ -4416,7 +4869,7 @@ var init_entry = __esm({
|
|
|
4416
4869
|
});
|
|
4417
4870
|
|
|
4418
4871
|
// src/bin/cli.ts
|
|
4419
|
-
import * as
|
|
4872
|
+
import * as fs30 from "fs";
|
|
4420
4873
|
import * as path27 from "path";
|
|
4421
4874
|
import { fileURLToPath } from "url";
|
|
4422
4875
|
|
|
@@ -5592,7 +6045,7 @@ var __dirname = path27.dirname(fileURLToPath(import.meta.url));
|
|
|
5592
6045
|
var PKG_ROOT = path27.resolve(__dirname, "..", "..");
|
|
5593
6046
|
function getVersion() {
|
|
5594
6047
|
const pkgPath = path27.join(PKG_ROOT, "package.json");
|
|
5595
|
-
const pkg = JSON.parse(
|
|
6048
|
+
const pkg = JSON.parse(fs30.readFileSync(pkgPath, "utf-8"));
|
|
5596
6049
|
return pkg.version;
|
|
5597
6050
|
}
|
|
5598
6051
|
var args = process.argv.slice(2);
|
|
@@ -5601,6 +6054,8 @@ if (command === "init") {
|
|
|
5601
6054
|
initCommand({ force: args.includes("--force") }, PKG_ROOT);
|
|
5602
6055
|
} else if (command === "bootstrap") {
|
|
5603
6056
|
bootstrapCommand({ force: args.includes("--force") }, PKG_ROOT);
|
|
6057
|
+
} else if (command === "ci-parse") {
|
|
6058
|
+
Promise.resolve().then(() => (init_parse_inputs(), parse_inputs_exports)).then(({ runCiParse: runCiParse2 }) => runCiParse2());
|
|
5604
6059
|
} else if (command === "version" || command === "--version" || command === "-v") {
|
|
5605
6060
|
console.log(getVersion());
|
|
5606
6061
|
} else {
|