@kody-ade/kody-engine 0.2.63 → 0.3.1
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/README.md +24 -24
- package/dist/bin/{kody2.js → kody.js} +270 -164
- package/dist/executables/bug/profile.json +6 -6
- package/dist/executables/chore/profile.json +6 -6
- package/dist/executables/classify/profile.json +1 -1
- package/dist/executables/feature/profile.json +6 -6
- package/dist/executables/fix/profile.json +20 -3
- package/dist/executables/fix/prompt.md +1 -1
- package/dist/executables/fix-ci/profile.json +1 -1
- package/dist/executables/init/profile.json +1 -1
- package/dist/executables/plan/profile.json +23 -3
- package/dist/executables/plan/prompt.md +117 -9
- package/dist/executables/plan-verify/profile.json +2 -2
- package/dist/executables/plan-verify/prompt.md +1 -1
- package/dist/executables/research/profile.json +25 -3
- package/dist/executables/research/prompt.md +4 -0
- package/dist/executables/resolve/profile.json +1 -1
- package/dist/executables/review/profile.json +20 -3
- package/dist/executables/run/profile.json +1 -1
- package/dist/executables/run/prompt.md +2 -2
- package/dist/executables/spec/profile.json +4 -4
- package/dist/executables/sync/profile.json +1 -1
- package/dist/executables/types.ts +3 -3
- package/dist/executables/ui-review/profile.json +10 -4
- package/dist/executables/ui-review/prompt.md +8 -8
- package/dist/plugins/commands/kody-live-probe.md +2 -2
- package/dist/plugins/skills/kody-live-marker/SKILL.md +3 -3
- package/dist/plugins/test-plugin/.claude-plugin/plugin.json +2 -2
- package/dist/plugins/test-plugin/skills/kody-plugin-marker/SKILL.md +2 -2
- package/kody.config.schema.json +3 -3
- package/package.json +4 -4
- package/templates/{kody2.yml → kody.yml} +16 -16
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.
|
|
7
|
-
description: "
|
|
6
|
+
version: "0.3.1",
|
|
7
|
+
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
10
10
|
bin: {
|
|
11
|
-
|
|
11
|
+
kody: "dist/bin/kody.js"
|
|
12
12
|
},
|
|
13
13
|
files: [
|
|
14
14
|
"dist",
|
|
@@ -16,7 +16,7 @@ var package_default = {
|
|
|
16
16
|
"kody.config.schema.json"
|
|
17
17
|
],
|
|
18
18
|
scripts: {
|
|
19
|
-
|
|
19
|
+
kody: "tsx bin/kody.ts",
|
|
20
20
|
build: "tsup && node scripts/copy-assets.cjs",
|
|
21
21
|
test: "vitest run tests/unit tests/int --no-coverage",
|
|
22
22
|
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
@@ -73,7 +73,7 @@ var FileSink = class {
|
|
|
73
73
|
};
|
|
74
74
|
var HttpSink = class {
|
|
75
75
|
constructor(baseUrl, sessionId, logger = {
|
|
76
|
-
warn: (m) => process.stderr.write(`[
|
|
76
|
+
warn: (m) => process.stderr.write(`[kody:chat] ${m}
|
|
77
77
|
`)
|
|
78
78
|
}) {
|
|
79
79
|
this.baseUrl = baseUrl;
|
|
@@ -340,7 +340,7 @@ function formatBytes(bytes) {
|
|
|
340
340
|
// src/agent.ts
|
|
341
341
|
var DEFAULT_ALLOWED_TOOLS = ["Bash", "Edit", "Read", "Write", "Glob", "Grep"];
|
|
342
342
|
async function runAgent(opts) {
|
|
343
|
-
const ndjsonDir = opts.ndjsonDir ?? path3.join(opts.cwd, ".
|
|
343
|
+
const ndjsonDir = opts.ndjsonDir ?? path3.join(opts.cwd, ".kody");
|
|
344
344
|
fs3.mkdirSync(ndjsonDir, { recursive: true });
|
|
345
345
|
const ndjsonPath = path3.join(ndjsonDir, "last-run.jsonl");
|
|
346
346
|
const fullLog = fs3.createWriteStream(ndjsonPath, { flags: "w" });
|
|
@@ -366,7 +366,14 @@ async function runAgent(opts) {
|
|
|
366
366
|
env
|
|
367
367
|
};
|
|
368
368
|
if (opts.mcpServers && opts.mcpServers.length > 0) {
|
|
369
|
-
queryOptions.mcpServers =
|
|
369
|
+
queryOptions.mcpServers = Object.fromEntries(
|
|
370
|
+
opts.mcpServers.map((s) => {
|
|
371
|
+
const cfg = { command: s.command };
|
|
372
|
+
if (s.args) cfg.args = s.args;
|
|
373
|
+
if (s.env) cfg.env = s.env;
|
|
374
|
+
return [s.name, cfg];
|
|
375
|
+
})
|
|
376
|
+
);
|
|
370
377
|
}
|
|
371
378
|
if (opts.pluginPaths && opts.pluginPaths.length > 0) {
|
|
372
379
|
queryOptions.plugins = opts.pluginPaths.map((p) => ({ type: "local", path: p }));
|
|
@@ -544,7 +551,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
544
551
|
});
|
|
545
552
|
}
|
|
546
553
|
|
|
547
|
-
// src/
|
|
554
|
+
// src/kody-cli.ts
|
|
548
555
|
import { execFileSync as execFileSync20 } from "child_process";
|
|
549
556
|
import * as fs21 from "fs";
|
|
550
557
|
import * as path18 from "path";
|
|
@@ -623,9 +630,9 @@ function asDispatch(executable, target) {
|
|
|
623
630
|
return { executable, cliArgs: { issue: target }, target };
|
|
624
631
|
}
|
|
625
632
|
function extractAfterTag(body) {
|
|
626
|
-
const idx = body.indexOf("@
|
|
633
|
+
const idx = body.indexOf("@kody");
|
|
627
634
|
if (idx === -1) return body;
|
|
628
|
-
return body.slice(idx + "@
|
|
635
|
+
return body.slice(idx + "@kody".length).trim();
|
|
629
636
|
}
|
|
630
637
|
function extractSubcommand(afterTag) {
|
|
631
638
|
const match = afterTag.match(/^([a-z][a-z0-9-]{1,40})\b/);
|
|
@@ -685,13 +692,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
685
692
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
686
693
|
}
|
|
687
694
|
}
|
|
688
|
-
const configPath = path5.join(os.tmpdir(), `
|
|
695
|
+
const configPath = path5.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
689
696
|
fs6.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
690
697
|
const portMatch = url.match(/:(\d+)/);
|
|
691
698
|
const port = portMatch ? portMatch[1] : "4000";
|
|
692
699
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
693
700
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
694
|
-
const logPath = path5.join(os.tmpdir(), `
|
|
701
|
+
const logPath = path5.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
695
702
|
const outFd = fs6.openSync(logPath, "w");
|
|
696
703
|
const child = spawn(cmd, args, {
|
|
697
704
|
stdio: ["ignore", outFd, outFd],
|
|
@@ -1000,8 +1007,8 @@ import { execFileSync as execFileSync3 } from "child_process";
|
|
|
1000
1007
|
|
|
1001
1008
|
// src/state.ts
|
|
1002
1009
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1003
|
-
var STATE_BEGIN = "<!--
|
|
1004
|
-
var STATE_END = "<!--
|
|
1010
|
+
var STATE_BEGIN = "<!-- kody:state:v1:begin -->";
|
|
1011
|
+
var STATE_END = "<!-- kody:state:v1:end -->";
|
|
1005
1012
|
var HISTORY_MAX_ENTRIES = 20;
|
|
1006
1013
|
var API_TIMEOUT_MS = 3e4;
|
|
1007
1014
|
function emptyState() {
|
|
@@ -1117,7 +1124,7 @@ function noteFromAction(action) {
|
|
|
1117
1124
|
}
|
|
1118
1125
|
function renderStateComment(state) {
|
|
1119
1126
|
const lines = [];
|
|
1120
|
-
lines.push("## \u{1F4CB}
|
|
1127
|
+
lines.push("## \u{1F4CB} kody task state");
|
|
1121
1128
|
lines.push("");
|
|
1122
1129
|
if (state.flow) {
|
|
1123
1130
|
lines.push(`- **Flow:** \`${state.flow.name}\` (step: \`${state.flow.step}\`)`);
|
|
@@ -1197,7 +1204,7 @@ function writeTaskState(target, number, state, cwd) {
|
|
|
1197
1204
|
}
|
|
1198
1205
|
} catch (err) {
|
|
1199
1206
|
process.stderr.write(
|
|
1200
|
-
`[
|
|
1207
|
+
`[kody state] failed to write state on ${target} #${number}: ${err instanceof Error ? err.message : String(err)}
|
|
1201
1208
|
`
|
|
1202
1209
|
);
|
|
1203
1210
|
}
|
|
@@ -1221,12 +1228,12 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
1221
1228
|
writeTaskState("issue", flow.issueNumber, next, ctx.cwd);
|
|
1222
1229
|
} catch (err) {
|
|
1223
1230
|
process.stderr.write(
|
|
1224
|
-
`[
|
|
1231
|
+
`[kody advanceFlow] failed to mirror action to issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1225
1232
|
`
|
|
1226
1233
|
);
|
|
1227
1234
|
}
|
|
1228
1235
|
}
|
|
1229
|
-
const body = `@
|
|
1236
|
+
const body = `@kody ${flow.name}`;
|
|
1230
1237
|
try {
|
|
1231
1238
|
execFileSync3("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
|
|
1232
1239
|
timeout: API_TIMEOUT_MS2,
|
|
@@ -1235,7 +1242,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
1235
1242
|
});
|
|
1236
1243
|
} catch (err) {
|
|
1237
1244
|
process.stderr.write(
|
|
1238
|
-
`[
|
|
1245
|
+
`[kody advanceFlow] failed to re-trigger orchestrator on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1239
1246
|
`
|
|
1240
1247
|
);
|
|
1241
1248
|
}
|
|
@@ -1266,7 +1273,7 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1266
1273
|
if (!needsSynthetic) return;
|
|
1267
1274
|
const catalog = getPluginsCatalogRoot();
|
|
1268
1275
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1269
|
-
const root = path7.join(os2.tmpdir(), `
|
|
1276
|
+
const root = path7.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
1270
1277
|
fs8.mkdirSync(path7.join(root, ".claude-plugin"), { recursive: true });
|
|
1271
1278
|
if (cc.skills.length > 0) {
|
|
1272
1279
|
const dst = path7.join(root, "skills");
|
|
@@ -1313,9 +1320,9 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1313
1320
|
`);
|
|
1314
1321
|
}
|
|
1315
1322
|
const manifest = {
|
|
1316
|
-
name: `
|
|
1323
|
+
name: `kody-synth-${profile.name}`,
|
|
1317
1324
|
version: "1.0.0",
|
|
1318
|
-
description: `Synthetic plugin assembled by
|
|
1325
|
+
description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
|
|
1319
1326
|
};
|
|
1320
1327
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
1321
1328
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
@@ -1428,6 +1435,7 @@ function parseAgentResult(finalText) {
|
|
|
1428
1435
|
prSummary: "",
|
|
1429
1436
|
feedbackActions: "",
|
|
1430
1437
|
planDeviations: "",
|
|
1438
|
+
priorArt: "",
|
|
1431
1439
|
failureReason: "agent produced no final message"
|
|
1432
1440
|
};
|
|
1433
1441
|
const failedMatch = text.match(/(?:^|\n)\s*FAILED\s*:\s*(.+?)\s*$/s);
|
|
@@ -1438,6 +1446,7 @@ function parseAgentResult(finalText) {
|
|
|
1438
1446
|
prSummary: "",
|
|
1439
1447
|
feedbackActions: "",
|
|
1440
1448
|
planDeviations: "",
|
|
1449
|
+
priorArt: "",
|
|
1441
1450
|
failureReason: failedMatch[1].trim()
|
|
1442
1451
|
};
|
|
1443
1452
|
}
|
|
@@ -1450,6 +1459,7 @@ function parseAgentResult(finalText) {
|
|
|
1450
1459
|
prSummary: "",
|
|
1451
1460
|
feedbackActions: "",
|
|
1452
1461
|
planDeviations: "",
|
|
1462
|
+
priorArt: "",
|
|
1453
1463
|
failureReason: "no DONE or FAILED marker in agent output"
|
|
1454
1464
|
};
|
|
1455
1465
|
}
|
|
@@ -1458,24 +1468,27 @@ function parseAgentResult(finalText) {
|
|
|
1458
1468
|
const feedbackActions = extractBlock(
|
|
1459
1469
|
text,
|
|
1460
1470
|
/(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
|
|
1461
|
-
/(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY)\s*:/i
|
|
1471
|
+
/(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
|
|
1462
1472
|
);
|
|
1463
1473
|
let planDeviations = extractBlock(
|
|
1464
1474
|
text,
|
|
1465
1475
|
/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
|
|
1466
|
-
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS)\s*:/i
|
|
1476
|
+
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
|
|
1467
1477
|
);
|
|
1468
1478
|
if (!planDeviations) {
|
|
1469
1479
|
const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
1470
1480
|
if (inline) planDeviations = inline[1].trim();
|
|
1471
1481
|
}
|
|
1482
|
+
let priorArt = "";
|
|
1483
|
+
const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
1484
|
+
if (priorArtInline) priorArt = priorArtInline[1].trim();
|
|
1472
1485
|
const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
|
|
1473
1486
|
let prSummary = "";
|
|
1474
1487
|
if (summaryStart !== -1) {
|
|
1475
1488
|
const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
|
|
1476
1489
|
prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
|
|
1477
1490
|
}
|
|
1478
|
-
return { done: true, commitMessage, prSummary, feedbackActions, planDeviations, failureReason: "" };
|
|
1491
|
+
return { done: true, commitMessage, prSummary, feedbackActions, planDeviations, priorArt, failureReason: "" };
|
|
1479
1492
|
}
|
|
1480
1493
|
function extractBlock(text, startMarker, endMarker) {
|
|
1481
1494
|
const startIdx = text.search(startMarker);
|
|
@@ -1508,7 +1521,7 @@ var checkCoverageWithRetry = async (ctx) => {
|
|
|
1508
1521
|
ctx.data.coverageMisses = misses;
|
|
1509
1522
|
return;
|
|
1510
1523
|
}
|
|
1511
|
-
process.stderr.write(`[
|
|
1524
|
+
process.stderr.write(`[kody] coverage check found ${misses.length} missing test(s); retrying agent once
|
|
1512
1525
|
`);
|
|
1513
1526
|
const retryPrompt = `${basePrompt}
|
|
1514
1527
|
|
|
@@ -1570,14 +1583,14 @@ import * as path9 from "path";
|
|
|
1570
1583
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
1571
1584
|
".kody/",
|
|
1572
1585
|
".kody-engine/",
|
|
1573
|
-
".
|
|
1586
|
+
".kody/",
|
|
1574
1587
|
".kody-lean/",
|
|
1575
1588
|
// back-compat: stale runtime dir from kody-lean v0.5.x
|
|
1576
1589
|
"node_modules/",
|
|
1577
1590
|
"dist/",
|
|
1578
1591
|
"build/"
|
|
1579
1592
|
];
|
|
1580
|
-
var FORBIDDEN_PATH_EXACT = /* @__PURE__ */ new Set([".env", ".
|
|
1593
|
+
var FORBIDDEN_PATH_EXACT = /* @__PURE__ */ new Set([".env", ".kody-pip-requirements.txt"]);
|
|
1581
1594
|
var FORBIDDEN_PATH_SUFFIXES = [".log"];
|
|
1582
1595
|
var CONVENTIONAL_PREFIXES = [
|
|
1583
1596
|
"feat:",
|
|
@@ -1677,7 +1690,7 @@ function listFilesInCommit(ref = "HEAD", cwd) {
|
|
|
1677
1690
|
}
|
|
1678
1691
|
function normalizeCommitMessage(raw) {
|
|
1679
1692
|
const trimmed = raw.trim().replace(/^['"]|['"]$/g, "").trim();
|
|
1680
|
-
if (!trimmed) return "chore:
|
|
1693
|
+
if (!trimmed) return "chore: kody update";
|
|
1681
1694
|
const firstLine2 = trimmed.split("\n")[0];
|
|
1682
1695
|
for (const prefix of CONVENTIONAL_PREFIXES) {
|
|
1683
1696
|
if (firstLine2.toLowerCase().startsWith(prefix)) return trimmed;
|
|
@@ -1750,7 +1763,7 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
1750
1763
|
} else {
|
|
1751
1764
|
const aborted = abortUnfinishedGitOps(ctx.cwd);
|
|
1752
1765
|
if (aborted.length > 0) {
|
|
1753
|
-
process.stderr.write(`[
|
|
1766
|
+
process.stderr.write(`[kody] cleaned up unfinished git ops: ${aborted.join(", ")}
|
|
1754
1767
|
`);
|
|
1755
1768
|
}
|
|
1756
1769
|
}
|
|
@@ -1770,15 +1783,15 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
1770
1783
|
function defaultCommitMessage(mode, data) {
|
|
1771
1784
|
switch (mode) {
|
|
1772
1785
|
case "run":
|
|
1773
|
-
return `chore:
|
|
1786
|
+
return `chore: kody changes for #${data.commentTargetNumber}`;
|
|
1774
1787
|
case "fix":
|
|
1775
|
-
return `chore(fix):
|
|
1788
|
+
return `chore(fix): kody fix for PR #${data.commentTargetNumber}`;
|
|
1776
1789
|
case "fix-ci":
|
|
1777
|
-
return `fix(ci):
|
|
1790
|
+
return `fix(ci): kody fix-ci for PR #${data.commentTargetNumber}`;
|
|
1778
1791
|
case "resolve":
|
|
1779
1792
|
return `fix: resolve merge conflicts with ${data.baseBranch}`;
|
|
1780
1793
|
default:
|
|
1781
|
-
return `chore:
|
|
1794
|
+
return `chore: kody changes`;
|
|
1782
1795
|
}
|
|
1783
1796
|
}
|
|
1784
1797
|
|
|
@@ -2301,7 +2314,7 @@ function generateQaGuideTemplate(d) {
|
|
|
2301
2314
|
const lines = [];
|
|
2302
2315
|
lines.push("# QA guide");
|
|
2303
2316
|
lines.push("");
|
|
2304
|
-
lines.push("This file is read by `
|
|
2317
|
+
lines.push("This file is read by `kody ui-review`. Fill in the credential placeholders");
|
|
2305
2318
|
lines.push("below and commit \u2014 the agent uses them to log in to your preview deployment.");
|
|
2306
2319
|
lines.push("");
|
|
2307
2320
|
lines.push("## Test accounts");
|
|
@@ -2371,13 +2384,13 @@ var API_TIMEOUT_MS3 = 3e4;
|
|
|
2371
2384
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
2372
2385
|
const next = args?.next;
|
|
2373
2386
|
if (!next) {
|
|
2374
|
-
process.stderr.write("[
|
|
2387
|
+
process.stderr.write("[kody dispatch] missing `with.next` \u2014 skipping\n");
|
|
2375
2388
|
return;
|
|
2376
2389
|
}
|
|
2377
2390
|
const target = args?.target ?? "issue";
|
|
2378
2391
|
const issueNumber = ctx.args.issue;
|
|
2379
2392
|
if (!issueNumber) {
|
|
2380
|
-
process.stderr.write("[
|
|
2393
|
+
process.stderr.write("[kody dispatch] no --issue arg \u2014 skipping\n");
|
|
2381
2394
|
return;
|
|
2382
2395
|
}
|
|
2383
2396
|
const state = ctx.data.taskState;
|
|
@@ -2387,7 +2400,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
2387
2400
|
const usePr = target === "pr" && state?.core.prUrl;
|
|
2388
2401
|
const targetNumber = usePr ? parsePr(state.core.prUrl) ?? issueNumber : issueNumber;
|
|
2389
2402
|
const sub = usePr ? "pr" : "issue";
|
|
2390
|
-
const body = `@
|
|
2403
|
+
const body = `@kody ${next}`;
|
|
2391
2404
|
try {
|
|
2392
2405
|
execFileSync7("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
2393
2406
|
timeout: API_TIMEOUT_MS3,
|
|
@@ -2396,7 +2409,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
2396
2409
|
});
|
|
2397
2410
|
} catch (err) {
|
|
2398
2411
|
process.stderr.write(
|
|
2399
|
-
`[
|
|
2412
|
+
`[kody dispatch] failed to post @kody ${next} on ${sub} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2400
2413
|
`
|
|
2401
2414
|
);
|
|
2402
2415
|
}
|
|
@@ -2444,15 +2457,15 @@ function getIssue(issueNumber, cwd) {
|
|
|
2444
2457
|
labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
|
|
2445
2458
|
};
|
|
2446
2459
|
}
|
|
2447
|
-
function
|
|
2448
|
-
return body.replace(/(@)(
|
|
2460
|
+
function stripKodyMentions(body) {
|
|
2461
|
+
return body.replace(/(@)(kody)/gi, "$1\u200B$2");
|
|
2449
2462
|
}
|
|
2450
2463
|
function postIssueComment(issueNumber, body, cwd) {
|
|
2451
2464
|
try {
|
|
2452
|
-
gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input:
|
|
2465
|
+
gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKodyMentions(body), cwd });
|
|
2453
2466
|
} catch (err) {
|
|
2454
2467
|
process.stderr.write(
|
|
2455
|
-
`[
|
|
2468
|
+
`[kody] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2456
2469
|
`
|
|
2457
2470
|
);
|
|
2458
2471
|
}
|
|
@@ -2483,7 +2496,7 @@ function getPrDiff(prNumber, cwd) {
|
|
|
2483
2496
|
return gh2(["pr", "diff", String(prNumber)], { cwd });
|
|
2484
2497
|
} catch (err) {
|
|
2485
2498
|
process.stderr.write(
|
|
2486
|
-
`[
|
|
2499
|
+
`[kody] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2487
2500
|
`
|
|
2488
2501
|
);
|
|
2489
2502
|
return "";
|
|
@@ -2534,10 +2547,10 @@ function getPrLatestReviewBody(prNumber, cwd) {
|
|
|
2534
2547
|
}
|
|
2535
2548
|
function postPrReviewComment(prNumber, body, cwd) {
|
|
2536
2549
|
try {
|
|
2537
|
-
gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input:
|
|
2550
|
+
gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKodyMentions(body), cwd });
|
|
2538
2551
|
} catch (err) {
|
|
2539
2552
|
process.stderr.write(
|
|
2540
|
-
`[
|
|
2553
|
+
`[kody] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2541
2554
|
`
|
|
2542
2555
|
);
|
|
2543
2556
|
}
|
|
@@ -2600,7 +2613,7 @@ function buildPrBody(opts) {
|
|
|
2600
2613
|
lines.push("");
|
|
2601
2614
|
}
|
|
2602
2615
|
lines.push("---");
|
|
2603
|
-
lines.push("_Opened by
|
|
2616
|
+
lines.push("_Opened by kody (single-session autonomous run)._ ");
|
|
2604
2617
|
return lines.join("\n");
|
|
2605
2618
|
}
|
|
2606
2619
|
function firstLine(s) {
|
|
@@ -2671,7 +2684,7 @@ var ensurePr2 = async (ctx) => {
|
|
|
2671
2684
|
const issue = ctx.data.issue;
|
|
2672
2685
|
const pr = ctx.data.pr;
|
|
2673
2686
|
const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
|
|
2674
|
-
const title = issue?.title ?? pr?.title ?? `
|
|
2687
|
+
const title = issue?.title ?? pr?.title ?? `kody changes`;
|
|
2675
2688
|
try {
|
|
2676
2689
|
const result = ensurePr({
|
|
2677
2690
|
branch,
|
|
@@ -2843,7 +2856,7 @@ function setKodyLabel(issueNumber, spec, cwd) {
|
|
|
2843
2856
|
const target = spec.label;
|
|
2844
2857
|
if (!target.startsWith(KODY_NAMESPACE)) {
|
|
2845
2858
|
process.stderr.write(
|
|
2846
|
-
`[
|
|
2859
|
+
`[kody] setKodyLabel: refusing to set non-kody label "${target}"
|
|
2847
2860
|
`
|
|
2848
2861
|
);
|
|
2849
2862
|
return;
|
|
@@ -2865,14 +2878,14 @@ function setKodyLabel(issueNumber, spec, cwd) {
|
|
|
2865
2878
|
return;
|
|
2866
2879
|
} catch (retryErr) {
|
|
2867
2880
|
process.stderr.write(
|
|
2868
|
-
`[
|
|
2881
|
+
`[kody] setKodyLabel: create+retry failed for ${target} on #${issueNumber}: ${errMsg(retryErr)}
|
|
2869
2882
|
`
|
|
2870
2883
|
);
|
|
2871
2884
|
return;
|
|
2872
2885
|
}
|
|
2873
2886
|
}
|
|
2874
2887
|
process.stderr.write(
|
|
2875
|
-
`[
|
|
2888
|
+
`[kody] setKodyLabel: failed to add ${target} on #${issueNumber}: ${errMsg(err)}
|
|
2876
2889
|
`
|
|
2877
2890
|
);
|
|
2878
2891
|
}
|
|
@@ -2923,7 +2936,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
2923
2936
|
const prSuffix = state?.core.prUrl ? `
|
|
2924
2937
|
|
|
2925
2938
|
**PR:** ${state.core.prUrl}` : "";
|
|
2926
|
-
const body = `${icon}
|
|
2939
|
+
const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
2927
2940
|
try {
|
|
2928
2941
|
execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
2929
2942
|
timeout: API_TIMEOUT_MS5,
|
|
@@ -2932,7 +2945,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
2932
2945
|
});
|
|
2933
2946
|
} catch (err) {
|
|
2934
2947
|
process.stderr.write(
|
|
2935
|
-
`[
|
|
2948
|
+
`[kody finishFlow] failed to post final summary on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2936
2949
|
`
|
|
2937
2950
|
);
|
|
2938
2951
|
}
|
|
@@ -3091,7 +3104,7 @@ function reactToTriggerComment(cwd) {
|
|
|
3091
3104
|
}
|
|
3092
3105
|
}
|
|
3093
3106
|
process.stderr.write(
|
|
3094
|
-
`[
|
|
3107
|
+
`[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
|
|
3095
3108
|
`
|
|
3096
3109
|
);
|
|
3097
3110
|
}
|
|
@@ -3167,13 +3180,13 @@ function getFailedRunLogTail(runId, maxBytes, cwd) {
|
|
|
3167
3180
|
return "";
|
|
3168
3181
|
}
|
|
3169
3182
|
}
|
|
3170
|
-
function
|
|
3171
|
-
return workflowName.trim().toLowerCase() === "
|
|
3183
|
+
function isKodyDispatchWorkflow(workflowName) {
|
|
3184
|
+
return workflowName.trim().toLowerCase() === "kody";
|
|
3172
3185
|
}
|
|
3173
3186
|
function pickFailedRunForFixCi(prNumber, maxBytes, limit, cwd) {
|
|
3174
3187
|
const runs = getRecentFailedRunsForPr(prNumber, limit, cwd);
|
|
3175
3188
|
for (const run of runs) {
|
|
3176
|
-
if (
|
|
3189
|
+
if (isKodyDispatchWorkflow(run.workflowName)) continue;
|
|
3177
3190
|
const logTail = getFailedRunLogTail(run.id, maxBytes, cwd);
|
|
3178
3191
|
if (logTail) return { run, logTail };
|
|
3179
3192
|
}
|
|
@@ -3217,7 +3230,7 @@ var fixCiFlow = async (ctx) => {
|
|
|
3217
3230
|
bail(
|
|
3218
3231
|
ctx,
|
|
3219
3232
|
prNumber,
|
|
3220
|
-
`no actionable failed workflow run found on PR #${prNumber}'s branch (looked at last ${RUN_LOOKBACK} failed runs \u2014 all were either
|
|
3233
|
+
`no actionable failed workflow run found on PR #${prNumber}'s branch (looked at last ${RUN_LOOKBACK} failed runs \u2014 all were either kody's own dispatch workflow or had no fetchable logs; pass --run-id to target a specific run)`
|
|
3221
3234
|
);
|
|
3222
3235
|
return;
|
|
3223
3236
|
}
|
|
@@ -3232,10 +3245,10 @@ var fixCiFlow = async (ctx) => {
|
|
|
3232
3245
|
ctx.data.failedLogTail = logTail;
|
|
3233
3246
|
ctx.data.prDiff = getPrDiff(prNumber, ctx.cwd);
|
|
3234
3247
|
const runUrl = getRunUrl();
|
|
3235
|
-
const runSuffix = runUrl ? `,
|
|
3248
|
+
const runSuffix = runUrl ? `, kody run ${runUrl}` : "";
|
|
3236
3249
|
tryPostPr(
|
|
3237
3250
|
prNumber,
|
|
3238
|
-
`\u2699\uFE0F
|
|
3251
|
+
`\u2699\uFE0F kody fix-ci started on \`${ctx.data.branch}\`${runSuffix} \u2014 analyzing workflow run ${runId}`,
|
|
3239
3252
|
ctx.cwd
|
|
3240
3253
|
);
|
|
3241
3254
|
};
|
|
@@ -3245,7 +3258,7 @@ function bail(ctx, prNumber, reason) {
|
|
|
3245
3258
|
ctx.skipAgent = true;
|
|
3246
3259
|
const runUrl = getRunUrl();
|
|
3247
3260
|
const runSuffix = runUrl ? ` ([logs](${runUrl}))` : "";
|
|
3248
|
-
tryPostPr(prNumber, `\u274C
|
|
3261
|
+
tryPostPr(prNumber, `\u274C kody fix-ci could not run${runSuffix}: ${reason}`, ctx.cwd);
|
|
3249
3262
|
}
|
|
3250
3263
|
function tryPostPr(prNumber, body, cwd) {
|
|
3251
3264
|
try {
|
|
@@ -3283,7 +3296,7 @@ var fixFlow = async (ctx) => {
|
|
|
3283
3296
|
const runSuffix = runUrl ? `, run ${runUrl}` : "";
|
|
3284
3297
|
tryPostPr2(
|
|
3285
3298
|
prNumber,
|
|
3286
|
-
`\u2699\uFE0F
|
|
3299
|
+
`\u2699\uFE0F kody fix started on \`${ctx.data.branch}\`${runSuffix} \u2014 applying feedback (${truncate2(feedback.replace(/\n/g, " "), 200)})`,
|
|
3287
3300
|
ctx.cwd
|
|
3288
3301
|
);
|
|
3289
3302
|
};
|
|
@@ -3302,7 +3315,7 @@ import * as path15 from "path";
|
|
|
3302
3315
|
// src/scripts/loadQaGuide.ts
|
|
3303
3316
|
import * as fs16 from "fs";
|
|
3304
3317
|
import * as path14 from "path";
|
|
3305
|
-
var QA_GUIDE_REL_PATH = ".
|
|
3318
|
+
var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
|
|
3306
3319
|
var loadQaGuide = async (ctx) => {
|
|
3307
3320
|
const full = path14.join(ctx.cwd, QA_GUIDE_REL_PATH);
|
|
3308
3321
|
if (!fs16.existsSync(full)) {
|
|
@@ -3362,20 +3375,20 @@ function makeConfig(pm, ownerRepo, defaultBranch) {
|
|
|
3362
3375
|
}
|
|
3363
3376
|
};
|
|
3364
3377
|
}
|
|
3365
|
-
var WORKFLOW_TEMPLATE = `# Drop this file at .github/workflows/
|
|
3378
|
+
var WORKFLOW_TEMPLATE = `# Drop this file at .github/workflows/kody.yml in your repo.
|
|
3366
3379
|
#
|
|
3367
|
-
# Triggers: @
|
|
3380
|
+
# Triggers: @kody comment on an issue or PR, or manual workflow_dispatch.
|
|
3368
3381
|
# Everything else (install deps, set up LiteLLM, run the agent, open the PR)
|
|
3369
3382
|
# is handled inside the @kody-ade/kody-engine package.
|
|
3370
3383
|
#
|
|
3371
3384
|
# Required repo secrets: at least one model provider key (e.g. MINIMAX_API_KEY,
|
|
3372
|
-
# ANTHROPIC_API_KEY).
|
|
3385
|
+
# ANTHROPIC_API_KEY). kody reads any *_API_KEY secret automatically via
|
|
3373
3386
|
# toJSON(secrets) \u2014 no need to list them here.
|
|
3374
3387
|
#
|
|
3375
3388
|
# Recommended: KODY_TOKEN secret \u2014 a PAT or GitHub App token with repo
|
|
3376
|
-
# scope so
|
|
3389
|
+
# scope so kody's pushes trigger downstream CI and PR-body edits succeed.
|
|
3377
3390
|
|
|
3378
|
-
name:
|
|
3391
|
+
name: kody
|
|
3379
3392
|
|
|
3380
3393
|
on:
|
|
3381
3394
|
workflow_dispatch:
|
|
@@ -3393,7 +3406,7 @@ jobs:
|
|
|
3393
3406
|
\${{ github.event_name == 'workflow_dispatch' ||
|
|
3394
3407
|
(github.event_name == 'issue_comment' &&
|
|
3395
3408
|
!github.event.issue.pull_request &&
|
|
3396
|
-
contains(github.event.comment.body, '@
|
|
3409
|
+
contains(github.event.comment.body, '@kody')) }}
|
|
3397
3410
|
runs-on: ubuntu-latest
|
|
3398
3411
|
timeout-minutes: 60
|
|
3399
3412
|
permissions:
|
|
@@ -3416,7 +3429,7 @@ jobs:
|
|
|
3416
3429
|
|
|
3417
3430
|
- env:
|
|
3418
3431
|
ALL_SECRETS: \${{ toJSON(secrets) }}
|
|
3419
|
-
run: npx -y -p @kody-ade/kody-engine@latest
|
|
3432
|
+
run: npx -y -p @kody-ade/kody-engine@latest kody ci --issue \${{ github.event.inputs.issue_number || github.event.issue.number }}
|
|
3420
3433
|
`;
|
|
3421
3434
|
function defaultBranchFromGit(cwd) {
|
|
3422
3435
|
try {
|
|
@@ -3454,13 +3467,13 @@ function performInit(cwd, force) {
|
|
|
3454
3467
|
wrote.push("kody.config.json");
|
|
3455
3468
|
}
|
|
3456
3469
|
const workflowDir = path15.join(cwd, ".github", "workflows");
|
|
3457
|
-
const workflowPath = path15.join(workflowDir, "
|
|
3470
|
+
const workflowPath = path15.join(workflowDir, "kody.yml");
|
|
3458
3471
|
if (fs17.existsSync(workflowPath) && !force) {
|
|
3459
|
-
skipped.push(".github/workflows/
|
|
3472
|
+
skipped.push(".github/workflows/kody.yml");
|
|
3460
3473
|
} else {
|
|
3461
3474
|
fs17.mkdirSync(workflowDir, { recursive: true });
|
|
3462
3475
|
fs17.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
3463
|
-
wrote.push(".github/workflows/
|
|
3476
|
+
wrote.push(".github/workflows/kody.yml");
|
|
3464
3477
|
}
|
|
3465
3478
|
const hasUi = fs17.existsSync(path15.join(cwd, "src/app")) || fs17.existsSync(path15.join(cwd, "app")) || fs17.existsSync(path15.join(cwd, "pages"));
|
|
3466
3479
|
if (hasUi) {
|
|
@@ -3482,13 +3495,13 @@ function performInit(cwd, force) {
|
|
|
3482
3495
|
continue;
|
|
3483
3496
|
}
|
|
3484
3497
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
3485
|
-
const target = path15.join(workflowDir, `
|
|
3498
|
+
const target = path15.join(workflowDir, `kody-${exe.name}.yml`);
|
|
3486
3499
|
if (fs17.existsSync(target) && !force) {
|
|
3487
|
-
skipped.push(`.github/workflows/
|
|
3500
|
+
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
3488
3501
|
continue;
|
|
3489
3502
|
}
|
|
3490
3503
|
fs17.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
3491
|
-
wrote.push(`.github/workflows/
|
|
3504
|
+
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
3492
3505
|
}
|
|
3493
3506
|
let labels;
|
|
3494
3507
|
try {
|
|
@@ -3499,11 +3512,11 @@ function performInit(cwd, force) {
|
|
|
3499
3512
|
return { wrote, skipped, labels };
|
|
3500
3513
|
}
|
|
3501
3514
|
function renderScheduledWorkflow(name, cron) {
|
|
3502
|
-
return `# Scheduled
|
|
3503
|
-
# Generated by \`
|
|
3515
|
+
return `# Scheduled kody executable: ${name}
|
|
3516
|
+
# Generated by \`kody init\`. Regenerate with \`kody init --force\`.
|
|
3504
3517
|
# Edit the cron below or the executable's profile.json#schedule.
|
|
3505
3518
|
|
|
3506
|
-
name:
|
|
3519
|
+
name: kody ${name}
|
|
3507
3520
|
|
|
3508
3521
|
on:
|
|
3509
3522
|
schedule:
|
|
@@ -3527,14 +3540,14 @@ jobs:
|
|
|
3527
3540
|
node-version: 22
|
|
3528
3541
|
- env:
|
|
3529
3542
|
GH_TOKEN: \${{ secrets.KODY_TOKEN || github.token }}
|
|
3530
|
-
run: npx -y -p @kody-ade/kody-engine@latest
|
|
3543
|
+
run: npx -y -p @kody-ade/kody-engine@latest kody ${name}
|
|
3531
3544
|
`;
|
|
3532
3545
|
}
|
|
3533
3546
|
var initFlow = async (ctx) => {
|
|
3534
3547
|
const force = ctx.args.force === true;
|
|
3535
3548
|
const cwd = ctx.cwd;
|
|
3536
3549
|
const { wrote, skipped, labels } = performInit(cwd, force);
|
|
3537
|
-
process.stdout.write("\u2192
|
|
3550
|
+
process.stdout.write("\u2192 kody init\n");
|
|
3538
3551
|
for (const f of wrote) process.stdout.write(` wrote ${f}
|
|
3539
3552
|
`);
|
|
3540
3553
|
for (const f of skipped) process.stdout.write(` skipped ${f} (already exists; pass --force to overwrite)
|
|
@@ -3567,7 +3580,7 @@ var loadConventions = async (ctx) => {
|
|
|
3567
3580
|
const conventions = loadProjectConventions(ctx.cwd);
|
|
3568
3581
|
ctx.data.conventions = conventions;
|
|
3569
3582
|
if (conventions.length > 0) {
|
|
3570
|
-
process.stderr.write(`[
|
|
3583
|
+
process.stderr.write(`[kody] loaded conventions: ${conventions.map((c) => c.path).join(", ")}
|
|
3571
3584
|
`);
|
|
3572
3585
|
}
|
|
3573
3586
|
};
|
|
@@ -3600,6 +3613,97 @@ var loadIssueContext = async (ctx) => {
|
|
|
3600
3613
|
ctx.data.commentTargetNumber = issueNumber;
|
|
3601
3614
|
};
|
|
3602
3615
|
|
|
3616
|
+
// src/scripts/loadPriorArt.ts
|
|
3617
|
+
var PER_PR_DIFF_MAX_BYTES = 8e3;
|
|
3618
|
+
var TOTAL_MAX_BYTES = 3e4;
|
|
3619
|
+
var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
3620
|
+
var loadPriorArt = async (ctx, _profile, args) => {
|
|
3621
|
+
const artifactName = typeof args?.artifactName === "string" ? args.artifactName : "priorArt";
|
|
3622
|
+
const state = ctx.data.taskState;
|
|
3623
|
+
const artifact = state?.artifacts?.[artifactName];
|
|
3624
|
+
const prNumbers = parsePrNumbers(artifact?.content);
|
|
3625
|
+
if (prNumbers.length === 0) {
|
|
3626
|
+
ctx.data.priorArt = "";
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
const blocks = [];
|
|
3630
|
+
for (const n of prNumbers) {
|
|
3631
|
+
const block = fetchPrBlock(n, ctx.cwd);
|
|
3632
|
+
if (block) blocks.push(block);
|
|
3633
|
+
}
|
|
3634
|
+
const joined = blocks.join("\n\n---\n\n");
|
|
3635
|
+
ctx.data.priorArt = joined.length > TOTAL_MAX_BYTES ? joined.slice(0, TOTAL_MAX_BYTES) + TRUNCATED_SUFFIX : joined;
|
|
3636
|
+
};
|
|
3637
|
+
function parsePrNumbers(raw) {
|
|
3638
|
+
if (!raw) return [];
|
|
3639
|
+
const trimmed = raw.trim();
|
|
3640
|
+
if (!trimmed) return [];
|
|
3641
|
+
try {
|
|
3642
|
+
const parsed = JSON.parse(trimmed);
|
|
3643
|
+
if (!Array.isArray(parsed)) return [];
|
|
3644
|
+
return parsed.filter((n) => typeof n === "number" && Number.isInteger(n) && n > 0);
|
|
3645
|
+
} catch {
|
|
3646
|
+
return [];
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
function fetchPrBlock(prNumber, cwd) {
|
|
3650
|
+
try {
|
|
3651
|
+
const metaRaw = gh2(["pr", "view", String(prNumber), "--json", "title,state,url,mergedAt,closedAt"], { cwd });
|
|
3652
|
+
const meta = JSON.parse(metaRaw);
|
|
3653
|
+
const diff = truncate3(safeGh(["pr", "diff", String(prNumber)], cwd), PER_PR_DIFF_MAX_BYTES);
|
|
3654
|
+
const commentsRaw = safeGh(["pr", "view", String(prNumber), "--json", "comments,reviews"], cwd);
|
|
3655
|
+
const commentsBlock = formatReviewComments(commentsRaw);
|
|
3656
|
+
const lines = [
|
|
3657
|
+
`## Prior art: PR #${prNumber} \u2014 ${meta.title ?? "(no title)"} [${meta.state ?? "unknown"}]`,
|
|
3658
|
+
meta.url ? meta.url : "",
|
|
3659
|
+
"",
|
|
3660
|
+
"### Diff",
|
|
3661
|
+
"```diff",
|
|
3662
|
+
diff || "(empty)",
|
|
3663
|
+
"```"
|
|
3664
|
+
];
|
|
3665
|
+
if (commentsBlock) {
|
|
3666
|
+
lines.push("");
|
|
3667
|
+
lines.push("### Review comments");
|
|
3668
|
+
lines.push(commentsBlock);
|
|
3669
|
+
}
|
|
3670
|
+
return lines.filter((l) => l !== "").join("\n");
|
|
3671
|
+
} catch (err) {
|
|
3672
|
+
return `## Prior art: PR #${prNumber}
|
|
3673
|
+
_Could not fetch \u2014 ${err instanceof Error ? err.message : String(err)}_`;
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
function safeGh(args, cwd) {
|
|
3677
|
+
try {
|
|
3678
|
+
return gh2(args, { cwd });
|
|
3679
|
+
} catch {
|
|
3680
|
+
return "";
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
function truncate3(s, max) {
|
|
3684
|
+
return s.length <= max ? s : s.slice(0, max) + TRUNCATED_SUFFIX;
|
|
3685
|
+
}
|
|
3686
|
+
function formatReviewComments(raw) {
|
|
3687
|
+
if (!raw) return "";
|
|
3688
|
+
try {
|
|
3689
|
+
const parsed = JSON.parse(raw);
|
|
3690
|
+
const out = [];
|
|
3691
|
+
for (const c of parsed.comments ?? []) {
|
|
3692
|
+
if (!c.body) continue;
|
|
3693
|
+
out.push(`- **${c.author?.login ?? "unknown"}**: ${c.body.replace(/\n/g, " ").slice(0, 500)}`);
|
|
3694
|
+
}
|
|
3695
|
+
for (const r of parsed.reviews ?? []) {
|
|
3696
|
+
if (!r.body && !r.state) continue;
|
|
3697
|
+
const state = r.state ? ` (${r.state})` : "";
|
|
3698
|
+
const body = r.body ? `: ${r.body.replace(/\n/g, " ").slice(0, 500)}` : "";
|
|
3699
|
+
out.push(`- **${r.author?.login ?? "unknown"}**${state}${body}`);
|
|
3700
|
+
}
|
|
3701
|
+
return out.join("\n");
|
|
3702
|
+
} catch {
|
|
3703
|
+
return "";
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3603
3707
|
// src/scripts/loadTaskState.ts
|
|
3604
3708
|
var loadTaskState = async (ctx) => {
|
|
3605
3709
|
const target = ctx.data.commentTargetType;
|
|
@@ -3631,7 +3735,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
3631
3735
|
writeTaskState("pr", prNumber, state, ctx.cwd);
|
|
3632
3736
|
} catch (err) {
|
|
3633
3737
|
process.stderr.write(
|
|
3634
|
-
`[
|
|
3738
|
+
`[kody mirrorStateToPr] failed to mirror state to PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
3635
3739
|
`
|
|
3636
3740
|
);
|
|
3637
3741
|
}
|
|
@@ -3656,6 +3760,7 @@ var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
|
3656
3760
|
ctx.data.prSummary = parsed.prSummary;
|
|
3657
3761
|
ctx.data.feedbackActions = parsed.feedbackActions;
|
|
3658
3762
|
ctx.data.planDeviations = parsed.planDeviations;
|
|
3763
|
+
ctx.data.priorArt = parsed.priorArt;
|
|
3659
3764
|
ctx.data.agentFailureReason = parsed.failureReason;
|
|
3660
3765
|
ctx.data.agentOutcome = agentResult.outcome;
|
|
3661
3766
|
ctx.data.agentError = agentResult.error;
|
|
@@ -3714,7 +3819,7 @@ var persistFlowState = async (ctx) => {
|
|
|
3714
3819
|
writeTaskState("issue", issueNumber, state, ctx.cwd);
|
|
3715
3820
|
} catch (err) {
|
|
3716
3821
|
process.stderr.write(
|
|
3717
|
-
`[
|
|
3822
|
+
`[kody persistFlowState] failed to write state on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
3718
3823
|
`
|
|
3719
3824
|
);
|
|
3720
3825
|
}
|
|
@@ -3740,25 +3845,25 @@ var postClassification = async (ctx) => {
|
|
|
3740
3845
|
}
|
|
3741
3846
|
if (!classification) {
|
|
3742
3847
|
ctx.data.action = failedAction("classification missing or invalid");
|
|
3743
|
-
tryAuditComment(issueNumber, "\u26A0\uFE0F
|
|
3848
|
+
tryAuditComment(issueNumber, "\u26A0\uFE0F kody classifier could not decide \u2014 please re-run with an explicit `@kody <type>`.", ctx.cwd);
|
|
3744
3849
|
ctx.output.exitCode = 1;
|
|
3745
3850
|
ctx.output.reason = "classify: no decision";
|
|
3746
3851
|
return;
|
|
3747
3852
|
}
|
|
3748
3853
|
tryAuditComment(
|
|
3749
3854
|
issueNumber,
|
|
3750
|
-
`\u{1F50E}
|
|
3855
|
+
`\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`,
|
|
3751
3856
|
ctx.cwd
|
|
3752
3857
|
);
|
|
3753
3858
|
try {
|
|
3754
|
-
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@
|
|
3859
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
3755
3860
|
cwd: ctx.cwd,
|
|
3756
3861
|
timeout: API_TIMEOUT_MS6,
|
|
3757
3862
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3758
3863
|
});
|
|
3759
3864
|
} catch (err) {
|
|
3760
3865
|
process.stderr.write(
|
|
3761
|
-
`[
|
|
3866
|
+
`[kody postClassification] failed to dispatch @kody ${classification}: ${err instanceof Error ? err.message : String(err)}
|
|
3762
3867
|
`
|
|
3763
3868
|
);
|
|
3764
3869
|
ctx.data.action = failedAction("dispatch post failed");
|
|
@@ -3812,22 +3917,22 @@ var postIssueComment2 = async (ctx) => {
|
|
|
3812
3917
|
const prAction = ctx.data.prResult?.action;
|
|
3813
3918
|
if (!commitResult?.committed && !hasCommits) {
|
|
3814
3919
|
const reason = "no changes to commit";
|
|
3815
|
-
postWith(targetType, targetNumber, `\u26A0\uFE0F
|
|
3920
|
+
postWith(targetType, targetNumber, `\u26A0\uFE0F kody FAILED: ${reason}`, ctx.cwd);
|
|
3816
3921
|
ctx.output.exitCode = 3;
|
|
3817
3922
|
ctx.output.reason = reason;
|
|
3818
3923
|
return;
|
|
3819
3924
|
}
|
|
3820
3925
|
if (ctx.output.exitCode === 4 && ctx.data.prCrashReason) {
|
|
3821
|
-
postWith(targetType, targetNumber, `\u26A0\uFE0F
|
|
3926
|
+
postWith(targetType, targetNumber, `\u26A0\uFE0F kody FAILED: ${truncate2(ctx.data.prCrashReason, 1500)}`, ctx.cwd);
|
|
3822
3927
|
ctx.output.reason = ctx.data.prCrashReason;
|
|
3823
3928
|
return;
|
|
3824
3929
|
}
|
|
3825
3930
|
const failureReason = computeFailureReason2(ctx);
|
|
3826
3931
|
const isFailure = failureReason.length > 0;
|
|
3827
3932
|
const justPushedToExistingPr = prAction === "updated" && commitResult?.committed === true;
|
|
3828
|
-
const successMsg = justPushedToExistingPr ? `\u2705
|
|
3933
|
+
const successMsg = justPushedToExistingPr ? `\u2705 kody pushed to ${prUrl}` : prAction === "updated" ? `\u2139\uFE0F kody made no changes \u2014 PR: ${prUrl}` : `\u2705 kody PR opened: ${prUrl}`;
|
|
3829
3934
|
const failurePrSuffix = prUrl ? prAction === "updated" ? ` \u2014 PR: ${prUrl}` : ` \u2014 draft PR: ${prUrl}` : "";
|
|
3830
|
-
const msg = isFailure ? `\u26A0\uFE0F
|
|
3935
|
+
const msg = isFailure ? `\u26A0\uFE0F kody FAILED: ${truncate2(failureReason, 1500)}${failurePrSuffix}` : successMsg;
|
|
3831
3936
|
postWith(targetType, targetNumber, msg, ctx.cwd);
|
|
3832
3937
|
let exitCode = 0;
|
|
3833
3938
|
const agentDone = Boolean(ctx.data.agentDone);
|
|
@@ -3883,7 +3988,7 @@ _Orchestrator will advance to the next step automatically._`;
|
|
|
3883
3988
|
return `${head}
|
|
3884
3989
|
|
|
3885
3990
|
---
|
|
3886
|
-
Comment \`
|
|
3991
|
+
Comment \`kody run\` (prefixed with \`@\`) to execute this plan.`;
|
|
3887
3992
|
}
|
|
3888
3993
|
|
|
3889
3994
|
// src/scripts/postResearchComment.ts
|
|
@@ -3928,7 +4033,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
|
3928
4033
|
if (!agentResult || agentResult.outcome !== "completed") {
|
|
3929
4034
|
const reason = agentResult?.error ?? "agent did not complete";
|
|
3930
4035
|
try {
|
|
3931
|
-
postPrReviewComment(prNumber, `\u26A0\uFE0F
|
|
4036
|
+
postPrReviewComment(prNumber, `\u26A0\uFE0F kody review FAILED: ${truncate2(reason, 1e3)}`, ctx.cwd);
|
|
3932
4037
|
} catch {
|
|
3933
4038
|
}
|
|
3934
4039
|
ctx.output.exitCode = 1;
|
|
@@ -3939,7 +4044,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
|
3939
4044
|
const reviewBody = agentResult.finalText.trim();
|
|
3940
4045
|
if (!reviewBody) {
|
|
3941
4046
|
try {
|
|
3942
|
-
postPrReviewComment(prNumber, `\u26A0\uFE0F
|
|
4047
|
+
postPrReviewComment(prNumber, `\u26A0\uFE0F kody review FAILED: agent produced no review body`, ctx.cwd);
|
|
3943
4048
|
} catch {
|
|
3944
4049
|
}
|
|
3945
4050
|
ctx.output.exitCode = 1;
|
|
@@ -4156,11 +4261,11 @@ async function runPrepare(args) {
|
|
|
4156
4261
|
}
|
|
4157
4262
|
const base = ctx.config.git.defaultBranch;
|
|
4158
4263
|
const title = `chore: release ${tag}`;
|
|
4159
|
-
const body = `Automated release PR opened by
|
|
4264
|
+
const body = `Automated release PR opened by kody.
|
|
4160
4265
|
|
|
4161
4266
|
${entry}
|
|
4162
4267
|
|
|
4163
|
-
Merge this and then run \`
|
|
4268
|
+
Merge this and then run \`kody release --mode finalize\`.`;
|
|
4164
4269
|
let prUrl = "";
|
|
4165
4270
|
try {
|
|
4166
4271
|
prUrl = gh2(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
|
|
@@ -4231,19 +4336,19 @@ async function runFinalize(args) {
|
|
|
4231
4336
|
const r = runShell(cmd, cwd, timeoutMs);
|
|
4232
4337
|
publishStatus = r.exitCode === 0 ? "ok" : "failed";
|
|
4233
4338
|
if (r.exitCode !== 0) {
|
|
4234
|
-
process.stderr.write(`[
|
|
4339
|
+
process.stderr.write(`[kody release] publishCommand exit ${r.exitCode}
|
|
4235
4340
|
${truncate2(r.stderr, 2e3)}
|
|
4236
4341
|
`);
|
|
4237
4342
|
}
|
|
4238
4343
|
}
|
|
4239
4344
|
let releaseUrl = "";
|
|
4240
4345
|
try {
|
|
4241
|
-
const releaseArgs = ["release", "create", tag, "--title", tag, "--notes", `Release ${tag} \u2014 automated by
|
|
4346
|
+
const releaseArgs = ["release", "create", tag, "--title", tag, "--notes", `Release ${tag} \u2014 automated by kody.`];
|
|
4242
4347
|
if (releaseCfg.draftRelease) releaseArgs.push("--draft");
|
|
4243
4348
|
releaseUrl = gh2(releaseArgs, { cwd }).trim();
|
|
4244
4349
|
} catch (err) {
|
|
4245
4350
|
process.stderr.write(
|
|
4246
|
-
`[
|
|
4351
|
+
`[kody release] gh release create failed: ${err instanceof Error ? err.message : String(err)}
|
|
4247
4352
|
`
|
|
4248
4353
|
);
|
|
4249
4354
|
}
|
|
@@ -4382,14 +4487,14 @@ var resolveFlow = async (ctx) => {
|
|
|
4382
4487
|
ctx.output.exitCode = 0;
|
|
4383
4488
|
ctx.output.reason = `already up to date with origin/${baseBranch} \u2014 nothing to resolve`;
|
|
4384
4489
|
ctx.skipAgent = true;
|
|
4385
|
-
tryPostPr3(prNumber, `\u2139\uFE0F
|
|
4490
|
+
tryPostPr3(prNumber, `\u2139\uFE0F kody resolve: ${ctx.output.reason}`, ctx.cwd);
|
|
4386
4491
|
return;
|
|
4387
4492
|
}
|
|
4388
4493
|
if (mergeStatus === "error") {
|
|
4389
4494
|
ctx.output.exitCode = 99;
|
|
4390
4495
|
ctx.output.reason = `failed to merge origin/${baseBranch} (non-conflict error); see runner log`;
|
|
4391
4496
|
ctx.skipAgent = true;
|
|
4392
|
-
tryPostPr3(prNumber, `\u26A0\uFE0F
|
|
4497
|
+
tryPostPr3(prNumber, `\u26A0\uFE0F kody resolve FAILED: ${ctx.output.reason}`, ctx.cwd);
|
|
4393
4498
|
return;
|
|
4394
4499
|
}
|
|
4395
4500
|
const conflictedFiles = getConflictedFiles(ctx.cwd);
|
|
@@ -4405,7 +4510,7 @@ var resolveFlow = async (ctx) => {
|
|
|
4405
4510
|
const runSuffix = runUrl ? `, run ${runUrl}` : "";
|
|
4406
4511
|
tryPostPr3(
|
|
4407
4512
|
prNumber,
|
|
4408
|
-
`\u2699\uFE0F
|
|
4513
|
+
`\u2699\uFE0F kody resolve started on \`${ctx.data.branch}\`${runSuffix} \u2014 ${conflictedFiles.length} conflicted file(s)`,
|
|
4409
4514
|
ctx.cwd
|
|
4410
4515
|
);
|
|
4411
4516
|
};
|
|
@@ -4485,7 +4590,7 @@ var reviewFlow = async (ctx) => {
|
|
|
4485
4590
|
ctx.data.prDiff = getPrDiff(prNumber, ctx.cwd);
|
|
4486
4591
|
const runUrl = getRunUrl();
|
|
4487
4592
|
const runSuffix = runUrl ? `, run ${runUrl}` : "";
|
|
4488
|
-
tryPostPr4(prNumber, `\u{1F440}
|
|
4593
|
+
tryPostPr4(prNumber, `\u{1F440} kody review started on PR #${prNumber}${runSuffix}`, ctx.cwd);
|
|
4489
4594
|
};
|
|
4490
4595
|
function tryPostPr4(prNumber, body, cwd) {
|
|
4491
4596
|
try {
|
|
@@ -4509,13 +4614,13 @@ var runFlow = async (ctx) => {
|
|
|
4509
4614
|
ctx.output.exitCode = 5;
|
|
4510
4615
|
ctx.output.reason = err.message;
|
|
4511
4616
|
ctx.skipAgent = true;
|
|
4512
|
-
tryPost(issueNumber, `\u26A0\uFE0F
|
|
4617
|
+
tryPost(issueNumber, `\u26A0\uFE0F kody refused to start: ${err.message}`, ctx.cwd);
|
|
4513
4618
|
return;
|
|
4514
4619
|
}
|
|
4515
4620
|
throw err;
|
|
4516
4621
|
}
|
|
4517
4622
|
const runUrl = getRunUrl();
|
|
4518
|
-
const startMsg = runUrl ? `\u2699\uFE0F
|
|
4623
|
+
const startMsg = runUrl ? `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\`, run ${runUrl}` : `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\``;
|
|
4519
4624
|
tryPost(issueNumber, startMsg, ctx.cwd);
|
|
4520
4625
|
};
|
|
4521
4626
|
function tryPost(issueNumber, body, cwd) {
|
|
@@ -4559,7 +4664,7 @@ var setLifecycleLabel = async (ctx, _profile, args) => {
|
|
|
4559
4664
|
const label = args?.label;
|
|
4560
4665
|
if (typeof label !== "string" || !label.startsWith(KODY_NAMESPACE)) {
|
|
4561
4666
|
process.stderr.write(
|
|
4562
|
-
`[
|
|
4667
|
+
`[kody] setLifecycleLabel: missing or invalid "label" arg (must start with "${KODY_NAMESPACE}"): ${String(label)}
|
|
4563
4668
|
`
|
|
4564
4669
|
);
|
|
4565
4670
|
return;
|
|
@@ -4595,14 +4700,14 @@ var API_TIMEOUT_MS7 = 3e4;
|
|
|
4595
4700
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
4596
4701
|
const entry = args?.entry;
|
|
4597
4702
|
if (!entry) {
|
|
4598
|
-
process.stderr.write("[
|
|
4703
|
+
process.stderr.write("[kody startFlow] missing `with.entry` \u2014 skipping\n");
|
|
4599
4704
|
return;
|
|
4600
4705
|
}
|
|
4601
4706
|
const target = args?.target ?? "issue";
|
|
4602
4707
|
const flowName = profile.name;
|
|
4603
4708
|
const issueNumber = ctx.args.issue;
|
|
4604
4709
|
if (!issueNumber) {
|
|
4605
|
-
process.stderr.write("[
|
|
4710
|
+
process.stderr.write("[kody startFlow] no --issue arg \u2014 skipping\n");
|
|
4606
4711
|
return;
|
|
4607
4712
|
}
|
|
4608
4713
|
const state = ctx.data.taskState;
|
|
@@ -4617,12 +4722,12 @@ var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
|
4617
4722
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4618
4723
|
};
|
|
4619
4724
|
}
|
|
4620
|
-
|
|
4725
|
+
postKodyComment(target, issueNumber, state, entry, ctx.cwd);
|
|
4621
4726
|
};
|
|
4622
|
-
function
|
|
4727
|
+
function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
4623
4728
|
const targetNumber = target === "pr" && state?.core.prUrl ? parsePr2(state.core.prUrl) ?? issueNumber : issueNumber;
|
|
4624
4729
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
4625
|
-
const body = `@
|
|
4730
|
+
const body = `@kody ${next}`;
|
|
4626
4731
|
try {
|
|
4627
4732
|
execFileSync17("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
4628
4733
|
timeout: API_TIMEOUT_MS7,
|
|
@@ -4631,7 +4736,7 @@ function postKody2Comment(target, issueNumber, state, next, cwd) {
|
|
|
4631
4736
|
});
|
|
4632
4737
|
} catch (err) {
|
|
4633
4738
|
process.stderr.write(
|
|
4634
|
-
`[
|
|
4739
|
+
`[kody startFlow] failed to post @kody ${next} on ${sub} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
4635
4740
|
`
|
|
4636
4741
|
);
|
|
4637
4742
|
}
|
|
@@ -4670,7 +4775,7 @@ var syncFlow = async (ctx) => {
|
|
|
4670
4775
|
bail2(
|
|
4671
4776
|
ctx,
|
|
4672
4777
|
prNumber,
|
|
4673
|
-
`merge from origin/${baseBranch} produced conflicts \u2014 run \`@
|
|
4778
|
+
`merge from origin/${baseBranch} produced conflicts \u2014 run \`@kody resolve\` to let kody resolve them`
|
|
4674
4779
|
);
|
|
4675
4780
|
return;
|
|
4676
4781
|
}
|
|
@@ -4678,7 +4783,7 @@ var syncFlow = async (ctx) => {
|
|
|
4678
4783
|
if (headAfter === headBefore) {
|
|
4679
4784
|
ctx.output.exitCode = 0;
|
|
4680
4785
|
ctx.output.reason = `already up to date with origin/${baseBranch}`;
|
|
4681
|
-
tryPostPr5(prNumber, `\u2139\uFE0F
|
|
4786
|
+
tryPostPr5(prNumber, `\u2139\uFE0F kody sync: already up to date with origin/${baseBranch}`, ctx.cwd);
|
|
4682
4787
|
return;
|
|
4683
4788
|
}
|
|
4684
4789
|
try {
|
|
@@ -4692,14 +4797,14 @@ var syncFlow = async (ctx) => {
|
|
|
4692
4797
|
ctx.output.reason = `merged origin/${baseBranch} into ${ctx.data.branch}`;
|
|
4693
4798
|
const runUrl = getRunUrl();
|
|
4694
4799
|
const runSuffix = runUrl ? ` ([logs](${runUrl}))` : "";
|
|
4695
|
-
tryPostPr5(prNumber, `\u2705
|
|
4800
|
+
tryPostPr5(prNumber, `\u2705 kody sync: merged \`origin/${baseBranch}\` into \`${ctx.data.branch}\`${runSuffix}`, ctx.cwd);
|
|
4696
4801
|
};
|
|
4697
4802
|
function bail2(ctx, prNumber, reason) {
|
|
4698
4803
|
ctx.output.exitCode = 1;
|
|
4699
4804
|
ctx.output.reason = reason;
|
|
4700
4805
|
const runUrl = getRunUrl();
|
|
4701
4806
|
const runSuffix = runUrl ? ` ([logs](${runUrl}))` : "";
|
|
4702
|
-
tryPostPr5(prNumber, `\u274C
|
|
4807
|
+
tryPostPr5(prNumber, `\u274C kody sync could not complete${runSuffix}: ${reason}`, ctx.cwd);
|
|
4703
4808
|
}
|
|
4704
4809
|
function revParseHead(cwd) {
|
|
4705
4810
|
try {
|
|
@@ -4847,10 +4952,10 @@ function findStalePrs(cwd, staleDays, now = /* @__PURE__ */ new Date()) {
|
|
|
4847
4952
|
}
|
|
4848
4953
|
function formatStaleReport(stale, staleDays) {
|
|
4849
4954
|
if (stale.length === 0) {
|
|
4850
|
-
return `\u{1F7E2} **
|
|
4955
|
+
return `\u{1F7E2} **kody watch-stale-prs** \u2014 no open PRs untouched for more than ${staleDays} days. \u2728`;
|
|
4851
4956
|
}
|
|
4852
4957
|
const lines = [
|
|
4853
|
-
`\u{1F7E1} **
|
|
4958
|
+
`\u{1F7E1} **kody watch-stale-prs** \u2014 ${stale.length} PR(s) untouched for > ${staleDays} days:`,
|
|
4854
4959
|
""
|
|
4855
4960
|
];
|
|
4856
4961
|
for (const pr of stale.slice(0, 50)) {
|
|
@@ -4871,7 +4976,7 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
4871
4976
|
postIssueComment(reportIssueNumber, report, ctx.cwd);
|
|
4872
4977
|
} catch (err) {
|
|
4873
4978
|
process.stderr.write(
|
|
4874
|
-
`[
|
|
4979
|
+
`[kody watch] failed to post to issue #${reportIssueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
4875
4980
|
`
|
|
4876
4981
|
);
|
|
4877
4982
|
}
|
|
@@ -4894,7 +4999,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
4894
4999
|
const reason = ctx.output.reason;
|
|
4895
5000
|
const status = exitCode === 0 ? "\u2705 success" : exitCode === 3 ? "\u23ED\uFE0F no-op" : "\u26A0\uFE0F failed";
|
|
4896
5001
|
const lines = [];
|
|
4897
|
-
lines.push(`##
|
|
5002
|
+
lines.push(`## kody ${executable} \u2014 ${status}`);
|
|
4898
5003
|
lines.push("");
|
|
4899
5004
|
lines.push(`- **Executable:** \`${executable}\``);
|
|
4900
5005
|
lines.push(`- **Target:** ${target}`);
|
|
@@ -4924,6 +5029,7 @@ var preflightScripts = {
|
|
|
4924
5029
|
loadIssueContext,
|
|
4925
5030
|
loadConventions,
|
|
4926
5031
|
loadCoverageRules,
|
|
5032
|
+
loadPriorArt,
|
|
4927
5033
|
loadQaGuide,
|
|
4928
5034
|
buildSyntheticPlugin,
|
|
4929
5035
|
resolveArtifacts,
|
|
@@ -5064,7 +5170,7 @@ async function runExecutable(profileName, input) {
|
|
|
5064
5170
|
data: {},
|
|
5065
5171
|
output: { exitCode: 0 }
|
|
5066
5172
|
};
|
|
5067
|
-
const ndjsonDir = path17.join(input.cwd, ".
|
|
5173
|
+
const ndjsonDir = path17.join(input.cwd, ".kody");
|
|
5068
5174
|
const invokeAgent = async (prompt) => {
|
|
5069
5175
|
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path17.isAbsolute(p) ? p : path17.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
5070
5176
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
@@ -5079,7 +5185,7 @@ async function runExecutable(profileName, input) {
|
|
|
5079
5185
|
ndjsonDir,
|
|
5080
5186
|
allowedToolsOverride: profile.claudeCode.tools,
|
|
5081
5187
|
permissionModeOverride: profile.claudeCode.permissionMode,
|
|
5082
|
-
mcpServers: profile.claudeCode.mcpServers,
|
|
5188
|
+
mcpServers: profile.claudeCode.mcpServers.length > 0 ? profile.claudeCode.mcpServers : void 0,
|
|
5083
5189
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
5084
5190
|
maxTurns: profile.claudeCode.maxTurns,
|
|
5085
5191
|
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|
|
@@ -5114,7 +5220,7 @@ async function runExecutable(profileName, input) {
|
|
|
5114
5220
|
await fn(ctx, profile, agentResult, entry.with);
|
|
5115
5221
|
} catch (err) {
|
|
5116
5222
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5117
|
-
process.stderr.write(`[
|
|
5223
|
+
process.stderr.write(`[kody] postflight script "${entry.script}" crashed: ${msg}
|
|
5118
5224
|
`);
|
|
5119
5225
|
if (!ctx.output.reason) ctx.output.reason = `postflight ${entry.script} crashed: ${msg}`;
|
|
5120
5226
|
if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
|
|
@@ -5234,11 +5340,11 @@ function finish(out) {
|
|
|
5234
5340
|
return out;
|
|
5235
5341
|
}
|
|
5236
5342
|
|
|
5237
|
-
// src/
|
|
5238
|
-
var CI_HELP = `
|
|
5343
|
+
// src/kody-cli.ts
|
|
5344
|
+
var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
|
|
5239
5345
|
|
|
5240
5346
|
Usage:
|
|
5241
|
-
|
|
5347
|
+
kody ci --issue <N> [--cwd <path>] [--verbose|--quiet]
|
|
5242
5348
|
[--skip-install] [--skip-litellm] [--package-manager pnpm|yarn|bun|npm]
|
|
5243
5349
|
|
|
5244
5350
|
Options:
|
|
@@ -5254,7 +5360,7 @@ Environment:
|
|
|
5254
5360
|
ALL_SECRETS JSON blob of all GitHub secrets (auto-populated in CI)
|
|
5255
5361
|
KODY_TOKEN|GH_TOKEN|GITHUB_TOKEN|GH_PAT auth token for gh/git operations
|
|
5256
5362
|
|
|
5257
|
-
Exit codes (inherited from
|
|
5363
|
+
Exit codes (inherited from kody run):
|
|
5258
5364
|
0 success (PR opened, verify passed)
|
|
5259
5365
|
1 agent reported FAILED (draft PR opened)
|
|
5260
5366
|
2 verify failed (draft PR opened)
|
|
@@ -5347,7 +5453,7 @@ function isOnPath(bin) {
|
|
|
5347
5453
|
}
|
|
5348
5454
|
function ensurePackageManagerInstalled(pm, cwd) {
|
|
5349
5455
|
if (pm === "npm" || isOnPath(pm)) return 0;
|
|
5350
|
-
process.stdout.write(`\u2192
|
|
5456
|
+
process.stdout.write(`\u2192 kody: ${pm} not on PATH \u2014 installing via npm install -g ${pm}
|
|
5351
5457
|
`);
|
|
5352
5458
|
return shellOut("npm", ["install", "-g", pm], cwd);
|
|
5353
5459
|
}
|
|
@@ -5367,18 +5473,18 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5367
5473
|
const cfg = loadConfig(cwd);
|
|
5368
5474
|
const model = parseProviderModel(cfg.agent.model);
|
|
5369
5475
|
if (!needsLitellmProxy(model)) {
|
|
5370
|
-
process.stdout.write("\u2192
|
|
5476
|
+
process.stdout.write("\u2192 kody: provider is anthropic/claude, skipping LiteLLM install\n");
|
|
5371
5477
|
return 0;
|
|
5372
5478
|
}
|
|
5373
5479
|
} catch {
|
|
5374
5480
|
}
|
|
5375
5481
|
try {
|
|
5376
5482
|
execFileSync20("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
5377
|
-
process.stdout.write("\u2192
|
|
5483
|
+
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
5378
5484
|
return 0;
|
|
5379
5485
|
} catch {
|
|
5380
5486
|
}
|
|
5381
|
-
process.stdout.write("\u2192
|
|
5487
|
+
process.stdout.write("\u2192 kody: installing litellm (pip install 'litellm[proxy]')\n");
|
|
5382
5488
|
return shellOut("pip", ["install", "litellm[proxy]"], cwd);
|
|
5383
5489
|
}
|
|
5384
5490
|
function configureGitIdentity(cwd) {
|
|
@@ -5401,7 +5507,7 @@ function configureGitIdentity(cwd) {
|
|
|
5401
5507
|
}
|
|
5402
5508
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
5403
5509
|
if (!issueNumber) return;
|
|
5404
|
-
const logPath = path18.join(cwd, ".
|
|
5510
|
+
const logPath = path18.join(cwd, ".kody", "last-run.jsonl");
|
|
5405
5511
|
let tail = "";
|
|
5406
5512
|
try {
|
|
5407
5513
|
if (fs21.existsSync(logPath)) {
|
|
@@ -5410,7 +5516,7 @@ function postFailureTail(issueNumber, cwd, reason) {
|
|
|
5410
5516
|
}
|
|
5411
5517
|
} catch {
|
|
5412
5518
|
}
|
|
5413
|
-
const body = tail ? `\u26A0\uFE0F
|
|
5519
|
+
const body = tail ? `\u26A0\uFE0F kody preflight failed: ${truncate2(reason, 500)}
|
|
5414
5520
|
|
|
5415
5521
|
<details><summary>Last-run log tail</summary>
|
|
5416
5522
|
|
|
@@ -5418,7 +5524,7 @@ function postFailureTail(issueNumber, cwd, reason) {
|
|
|
5418
5524
|
${tail}
|
|
5419
5525
|
\`\`\`
|
|
5420
5526
|
|
|
5421
|
-
</details>` : `\u26A0\uFE0F
|
|
5527
|
+
</details>` : `\u26A0\uFE0F kody preflight failed: ${truncate2(reason, 1500)}`;
|
|
5422
5528
|
try {
|
|
5423
5529
|
postIssueComment(issueNumber, body, cwd);
|
|
5424
5530
|
} catch {
|
|
@@ -5454,16 +5560,16 @@ ${CI_HELP}`);
|
|
|
5454
5560
|
target: args.issueNumber
|
|
5455
5561
|
};
|
|
5456
5562
|
const issueNumber = dispatch2.target;
|
|
5457
|
-
process.stdout.write(`\u2192
|
|
5563
|
+
process.stdout.write(`\u2192 kody preflight (cwd=${cwd}, executable=${dispatch2.executable}, target=${issueNumber})
|
|
5458
5564
|
`);
|
|
5459
5565
|
try {
|
|
5460
5566
|
const n = unpackAllSecrets();
|
|
5461
|
-
if (n > 0) process.stdout.write(`\u2192
|
|
5567
|
+
if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
|
|
5462
5568
|
`);
|
|
5463
5569
|
resolveAuthToken();
|
|
5464
5570
|
reactToTriggerComment(cwd);
|
|
5465
5571
|
const pm = args.packageManager ?? detectPackageManager2(cwd);
|
|
5466
|
-
process.stdout.write(`\u2192
|
|
5572
|
+
process.stdout.write(`\u2192 kody: package manager = ${pm}
|
|
5467
5573
|
`);
|
|
5468
5574
|
if (!args.skipInstall) {
|
|
5469
5575
|
const code = installDeps(pm, cwd);
|
|
@@ -5472,7 +5578,7 @@ ${CI_HELP}`);
|
|
|
5472
5578
|
return 99;
|
|
5473
5579
|
}
|
|
5474
5580
|
} else {
|
|
5475
|
-
process.stdout.write("\u2192
|
|
5581
|
+
process.stdout.write("\u2192 kody: skipping dep install (--skip-install)\n");
|
|
5476
5582
|
}
|
|
5477
5583
|
if (!args.skipLitellm) {
|
|
5478
5584
|
const code = installLitellmIfNeeded(cwd);
|
|
@@ -5481,17 +5587,17 @@ ${CI_HELP}`);
|
|
|
5481
5587
|
return 99;
|
|
5482
5588
|
}
|
|
5483
5589
|
} else {
|
|
5484
|
-
process.stdout.write("\u2192
|
|
5590
|
+
process.stdout.write("\u2192 kody: skipping LiteLLM install (--skip-litellm)\n");
|
|
5485
5591
|
}
|
|
5486
5592
|
configureGitIdentity(cwd);
|
|
5487
5593
|
} catch (err) {
|
|
5488
5594
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5489
|
-
process.stderr.write(`[
|
|
5595
|
+
process.stderr.write(`[kody] preflight crashed: ${msg}
|
|
5490
5596
|
`);
|
|
5491
5597
|
postFailureTail(issueNumber, cwd, `preflight crashed: ${msg}`);
|
|
5492
5598
|
return 99;
|
|
5493
5599
|
}
|
|
5494
|
-
process.stdout.write(`\u2192
|
|
5600
|
+
process.stdout.write(`\u2192 kody: preflight done, handing off to kody ${dispatch2.executable}
|
|
5495
5601
|
|
|
5496
5602
|
`);
|
|
5497
5603
|
try {
|
|
@@ -5509,7 +5615,7 @@ ${CI_HELP}`);
|
|
|
5509
5615
|
return result.exitCode;
|
|
5510
5616
|
} catch (err) {
|
|
5511
5617
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5512
|
-
process.stderr.write(`[
|
|
5618
|
+
process.stderr.write(`[kody] run crashed: ${msg}
|
|
5513
5619
|
`);
|
|
5514
5620
|
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
5515
5621
|
`);
|
|
@@ -5520,10 +5626,10 @@ ${CI_HELP}`);
|
|
|
5520
5626
|
|
|
5521
5627
|
// src/chat-cli.ts
|
|
5522
5628
|
var DEFAULT_MODEL = "claude/claude-haiku-4-5-20251001";
|
|
5523
|
-
var CHAT_HELP = `
|
|
5629
|
+
var CHAT_HELP = `kody chat \u2014 dashboard-driven chat session
|
|
5524
5630
|
|
|
5525
5631
|
Usage:
|
|
5526
|
-
|
|
5632
|
+
kody chat [--session <id>] [--message <text>] [--model <provider/model>]
|
|
5527
5633
|
[--dashboard-url <url>] [--cwd <path>] [--verbose|--quiet]
|
|
5528
5634
|
|
|
5529
5635
|
All inputs may also come from env: SESSION_ID, INIT_MESSAGE, MODEL, DASHBOARD_URL.
|
|
@@ -5574,7 +5680,7 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
5574
5680
|
execFileSync21("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
5575
5681
|
} catch (err) {
|
|
5576
5682
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5577
|
-
process.stderr.write(`[
|
|
5683
|
+
process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
|
|
5578
5684
|
`);
|
|
5579
5685
|
}
|
|
5580
5686
|
}
|
|
@@ -5607,7 +5713,7 @@ ${CHAT_HELP}`);
|
|
|
5607
5713
|
const sessionId = args.sessionId;
|
|
5608
5714
|
const unpackedSecrets = unpackAllSecrets();
|
|
5609
5715
|
if (unpackedSecrets > 0) {
|
|
5610
|
-
process.stdout.write(`\u2192
|
|
5716
|
+
process.stdout.write(`\u2192 kody: unpacked ${unpackedSecrets} secret(s) from ALL_SECRETS
|
|
5611
5717
|
`);
|
|
5612
5718
|
}
|
|
5613
5719
|
resolveAuthToken();
|
|
@@ -5669,19 +5775,19 @@ ${CHAT_HELP}`);
|
|
|
5669
5775
|
}
|
|
5670
5776
|
|
|
5671
5777
|
// src/entry.ts
|
|
5672
|
-
var HELP_TEXT = `
|
|
5778
|
+
var HELP_TEXT = `kody \u2014 single-session autonomous engineer
|
|
5673
5779
|
|
|
5674
5780
|
Usage:
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5781
|
+
kody run --issue <N> [--cwd <path>] [--verbose|--quiet]
|
|
5782
|
+
kody fix --pr <N> [--feedback "..."] [--cwd <path>] [--verbose|--quiet]
|
|
5783
|
+
kody fix-ci --pr <N> [--run-id <ID>] [--cwd <path>] [--verbose|--quiet]
|
|
5784
|
+
kody resolve --pr <N> [--cwd <path>] [--verbose|--quiet]
|
|
5785
|
+
kody review --pr <N> [--cwd <path>] [--verbose|--quiet]
|
|
5786
|
+
kody <other> [--cwd <path>] [--verbose|--quiet]
|
|
5787
|
+
kody ci --issue <N> [preflight flags \u2014 see: kody ci --help]
|
|
5788
|
+
kody chat [chat flags \u2014 see: kody chat --help]
|
|
5789
|
+
kody help
|
|
5790
|
+
kody version
|
|
5685
5791
|
|
|
5686
5792
|
Each top-level command (run, fix, fix-ci, resolve, review, \u2026) is a discovered
|
|
5687
5793
|
executable under \`src/executables/<name>/profile.json\`. Drop in a new
|
|
@@ -5737,7 +5843,7 @@ ${HELP_TEXT}`);
|
|
|
5737
5843
|
return 0;
|
|
5738
5844
|
}
|
|
5739
5845
|
if (args.command === "version") {
|
|
5740
|
-
process.stdout.write(`
|
|
5846
|
+
process.stdout.write(`kody ${package_default.version}
|
|
5741
5847
|
`);
|
|
5742
5848
|
return 0;
|
|
5743
5849
|
}
|
|
@@ -5746,7 +5852,7 @@ ${HELP_TEXT}`);
|
|
|
5746
5852
|
return await runCi(args.ciArgv ?? []);
|
|
5747
5853
|
} catch (err) {
|
|
5748
5854
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5749
|
-
process.stderr.write(`[
|
|
5855
|
+
process.stderr.write(`[kody] fatal: ${msg}
|
|
5750
5856
|
`);
|
|
5751
5857
|
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
5752
5858
|
`);
|
|
@@ -5758,7 +5864,7 @@ ${HELP_TEXT}`);
|
|
|
5758
5864
|
return await runChat(args.chatArgv ?? []);
|
|
5759
5865
|
} catch (err) {
|
|
5760
5866
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5761
|
-
process.stderr.write(`[
|
|
5867
|
+
process.stderr.write(`[kody] fatal: ${msg}
|
|
5762
5868
|
`);
|
|
5763
5869
|
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
5764
5870
|
`);
|
|
@@ -5783,7 +5889,7 @@ ${HELP_TEXT}`);
|
|
|
5783
5889
|
return result.exitCode;
|
|
5784
5890
|
} catch (err) {
|
|
5785
5891
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5786
|
-
process.stderr.write(`[
|
|
5892
|
+
process.stderr.write(`[kody] ${args.executableName} crashed: ${msg}
|
|
5787
5893
|
`);
|
|
5788
5894
|
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
5789
5895
|
`);
|
|
@@ -5793,11 +5899,11 @@ ${HELP_TEXT}`);
|
|
|
5793
5899
|
}
|
|
5794
5900
|
}
|
|
5795
5901
|
|
|
5796
|
-
// bin/
|
|
5902
|
+
// bin/kody.ts
|
|
5797
5903
|
main().then((code) => {
|
|
5798
5904
|
process.exit(code);
|
|
5799
5905
|
}).catch((err) => {
|
|
5800
|
-
process.stderr.write(`[
|
|
5906
|
+
process.stderr.write(`[kody] fatal: ${err instanceof Error ? err.message : String(err)}
|
|
5801
5907
|
`);
|
|
5802
5908
|
process.exit(99);
|
|
5803
5909
|
});
|