@kody-ade/kody-engine 0.2.61 → 0.2.63
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/kody2.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.2.
|
|
6
|
+
version: "0.2.63",
|
|
7
7
|
description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -50,7 +50,7 @@ var package_default = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
// src/chat-cli.ts
|
|
53
|
-
import { execFileSync as
|
|
53
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
54
54
|
import * as fs22 from "fs";
|
|
55
55
|
import * as path19 from "path";
|
|
56
56
|
|
|
@@ -181,7 +181,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
181
181
|
},
|
|
182
182
|
issueContext: parseIssueContext(raw.issueContext),
|
|
183
183
|
testRequirements: parseTestRequirements(raw.testRequirements),
|
|
184
|
-
defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable :
|
|
184
|
+
defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable : "classify",
|
|
185
185
|
classify: parseClassifyConfig(raw.classify),
|
|
186
186
|
release: parseReleaseConfig(raw.release)
|
|
187
187
|
};
|
|
@@ -295,7 +295,7 @@ ${truncate(text, 4e3)}`);
|
|
|
295
295
|
}
|
|
296
296
|
function formatResult(msg) {
|
|
297
297
|
const ok = msg.subtype === "success";
|
|
298
|
-
const tag = ok ? "
|
|
298
|
+
const tag = ok ? "SESSION ok" : `SESSION failed (${msg.subtype ?? "unknown"})`;
|
|
299
299
|
const dur = msg.duration_ms ? ` ${(msg.duration_ms / 1e3).toFixed(1)}s` : "";
|
|
300
300
|
const turns = msg.num_turns ? ` ${msg.num_turns} turns` : "";
|
|
301
301
|
const cost = typeof msg.total_cost_usd === "number" ? ` $${msg.total_cost_usd.toFixed(4)}` : "";
|
|
@@ -354,7 +354,7 @@ async function runAgent(opts) {
|
|
|
354
354
|
env.ANTHROPIC_BASE_URL = opts.litellmUrl;
|
|
355
355
|
env.ANTHROPIC_API_KEY = getAnthropicApiKeyOrDummy();
|
|
356
356
|
}
|
|
357
|
-
|
|
357
|
+
const resultTexts = [];
|
|
358
358
|
let outcome = "failed";
|
|
359
359
|
let errorMessage;
|
|
360
360
|
try {
|
|
@@ -399,7 +399,8 @@ async function runAgent(opts) {
|
|
|
399
399
|
if (m.type === "result") {
|
|
400
400
|
if (m.subtype === "success") {
|
|
401
401
|
outcome = "completed";
|
|
402
|
-
|
|
402
|
+
const text = (typeof m.result === "string" ? m.result : "").trim();
|
|
403
|
+
if (text) resultTexts.push(text);
|
|
403
404
|
} else {
|
|
404
405
|
outcome = "failed";
|
|
405
406
|
errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
|
|
@@ -415,6 +416,7 @@ async function runAgent(opts) {
|
|
|
415
416
|
} catch {
|
|
416
417
|
}
|
|
417
418
|
}
|
|
419
|
+
const finalText = resultTexts.join("\n\n---\n\n");
|
|
418
420
|
return { outcome, finalText, error: errorMessage, ndjsonPath };
|
|
419
421
|
}
|
|
420
422
|
|
|
@@ -543,7 +545,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
543
545
|
}
|
|
544
546
|
|
|
545
547
|
// src/kody2-cli.ts
|
|
546
|
-
import { execFileSync as
|
|
548
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
547
549
|
import * as fs21 from "fs";
|
|
548
550
|
import * as path18 from "path";
|
|
549
551
|
|
|
@@ -581,9 +583,6 @@ function autoDispatch(opts) {
|
|
|
581
583
|
if (!targetNum) return null;
|
|
582
584
|
const afterTag = extractAfterTag(body);
|
|
583
585
|
if (isPr) {
|
|
584
|
-
if (/\bapprove\b/.test(afterTag)) {
|
|
585
|
-
return { executable: "approve", cliArgs: { pr: targetNum }, target: targetNum };
|
|
586
|
-
}
|
|
587
586
|
if (/\bfix-ci\b/.test(afterTag)) {
|
|
588
587
|
return { executable: "fix-ci", cliArgs: { pr: targetNum }, target: targetNum };
|
|
589
588
|
}
|
|
@@ -607,8 +606,9 @@ function autoDispatch(opts) {
|
|
|
607
606
|
};
|
|
608
607
|
}
|
|
609
608
|
const sub = extractSubcommand(afterTag);
|
|
610
|
-
const defaultExec = opts?.config?.defaultExecutable ?? "run";
|
|
611
609
|
if (!sub) {
|
|
610
|
+
const defaultExec = opts?.config?.defaultExecutable;
|
|
611
|
+
if (!defaultExec) return null;
|
|
612
612
|
return asDispatch(defaultExec, targetNum);
|
|
613
613
|
}
|
|
614
614
|
if (sub === "build") {
|
|
@@ -1241,206 +1241,6 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
1241
1241
|
}
|
|
1242
1242
|
};
|
|
1243
1243
|
|
|
1244
|
-
// src/scripts/applyApprovals.ts
|
|
1245
|
-
import { execFileSync as execFileSync5 } from "child_process";
|
|
1246
|
-
|
|
1247
|
-
// src/issue.ts
|
|
1248
|
-
import { execFileSync as execFileSync4 } from "child_process";
|
|
1249
|
-
var API_TIMEOUT_MS3 = 3e4;
|
|
1250
|
-
function ghToken2() {
|
|
1251
|
-
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
1252
|
-
}
|
|
1253
|
-
function gh2(args, options) {
|
|
1254
|
-
const token = ghToken2();
|
|
1255
|
-
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
1256
|
-
return execFileSync4("gh", args, {
|
|
1257
|
-
encoding: "utf-8",
|
|
1258
|
-
timeout: API_TIMEOUT_MS3,
|
|
1259
|
-
cwd: options?.cwd,
|
|
1260
|
-
env,
|
|
1261
|
-
input: options?.input,
|
|
1262
|
-
stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
|
|
1263
|
-
}).trim();
|
|
1264
|
-
}
|
|
1265
|
-
function getIssue(issueNumber, cwd) {
|
|
1266
|
-
const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
|
|
1267
|
-
const parsed = JSON.parse(output);
|
|
1268
|
-
if (typeof parsed?.title !== "string") {
|
|
1269
|
-
throw new Error(`Issue #${issueNumber}: unexpected response shape`);
|
|
1270
|
-
}
|
|
1271
|
-
return {
|
|
1272
|
-
number: parsed.number ?? issueNumber,
|
|
1273
|
-
title: parsed.title,
|
|
1274
|
-
body: parsed.body ?? "",
|
|
1275
|
-
comments: (parsed.comments ?? []).map((c) => ({
|
|
1276
|
-
body: c.body ?? "",
|
|
1277
|
-
author: c.author?.login ?? "unknown",
|
|
1278
|
-
createdAt: c.createdAt ?? ""
|
|
1279
|
-
})),
|
|
1280
|
-
labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
function stripKody2Mentions(body) {
|
|
1284
|
-
return body.replace(/(@)(kody2)/gi, "$1\u200B$2");
|
|
1285
|
-
}
|
|
1286
|
-
function postIssueComment(issueNumber, body, cwd) {
|
|
1287
|
-
try {
|
|
1288
|
-
gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
|
|
1289
|
-
} catch (err) {
|
|
1290
|
-
process.stderr.write(
|
|
1291
|
-
`[kody2] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1292
|
-
`
|
|
1293
|
-
);
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
function truncate2(s, maxBytes) {
|
|
1297
|
-
if (s.length <= maxBytes) return s;
|
|
1298
|
-
return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
|
|
1299
|
-
}
|
|
1300
|
-
function getPr(prNumber, cwd) {
|
|
1301
|
-
const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
|
|
1302
|
-
cwd
|
|
1303
|
-
});
|
|
1304
|
-
const parsed = JSON.parse(output);
|
|
1305
|
-
if (typeof parsed?.title !== "string") {
|
|
1306
|
-
throw new Error(`PR #${prNumber}: unexpected response shape`);
|
|
1307
|
-
}
|
|
1308
|
-
return {
|
|
1309
|
-
number: parsed.number ?? prNumber,
|
|
1310
|
-
title: parsed.title,
|
|
1311
|
-
body: parsed.body ?? "",
|
|
1312
|
-
headRefName: String(parsed.headRefName ?? ""),
|
|
1313
|
-
baseRefName: String(parsed.baseRefName ?? ""),
|
|
1314
|
-
state: String(parsed.state ?? "")
|
|
1315
|
-
};
|
|
1316
|
-
}
|
|
1317
|
-
function getPrDiff(prNumber, cwd) {
|
|
1318
|
-
try {
|
|
1319
|
-
return gh2(["pr", "diff", String(prNumber)], { cwd });
|
|
1320
|
-
} catch (err) {
|
|
1321
|
-
process.stderr.write(
|
|
1322
|
-
`[kody2] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1323
|
-
`
|
|
1324
|
-
);
|
|
1325
|
-
return "";
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
function getPrReviews(prNumber, cwd) {
|
|
1329
|
-
try {
|
|
1330
|
-
const output = gh2(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
|
|
1331
|
-
const parsed = JSON.parse(output);
|
|
1332
|
-
if (!Array.isArray(parsed?.reviews)) return [];
|
|
1333
|
-
return parsed.reviews.map(
|
|
1334
|
-
(r) => ({
|
|
1335
|
-
body: r.body ?? "",
|
|
1336
|
-
state: r.state ?? "",
|
|
1337
|
-
author: r.author?.login ?? "unknown",
|
|
1338
|
-
submittedAt: r.submittedAt ?? ""
|
|
1339
|
-
})
|
|
1340
|
-
);
|
|
1341
|
-
} catch {
|
|
1342
|
-
return [];
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
function getPrComments(prNumber, cwd) {
|
|
1346
|
-
try {
|
|
1347
|
-
const output = gh2(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
|
|
1348
|
-
const parsed = JSON.parse(output);
|
|
1349
|
-
if (!Array.isArray(parsed?.comments)) return [];
|
|
1350
|
-
return parsed.comments.map((c) => ({
|
|
1351
|
-
body: c.body ?? "",
|
|
1352
|
-
author: c.author?.login ?? "unknown",
|
|
1353
|
-
createdAt: c.createdAt ?? ""
|
|
1354
|
-
})).filter((c) => c.body.trim().length > 0);
|
|
1355
|
-
} catch {
|
|
1356
|
-
return [];
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
var VERDICT_HEADING = /(^|\n)\s*#{1,6}\s*Verdict\s*:/i;
|
|
1360
|
-
function isReviewShaped(body) {
|
|
1361
|
-
return VERDICT_HEADING.test(body);
|
|
1362
|
-
}
|
|
1363
|
-
function getPrLatestReviewBody(prNumber, cwd) {
|
|
1364
|
-
const reviews = getPrReviews(prNumber, cwd).filter((r) => r.body.trim().length > 0).map((r) => ({ body: r.body, at: r.submittedAt }));
|
|
1365
|
-
const comments = getPrComments(prNumber, cwd).filter((c) => isReviewShaped(c.body)).map((c) => ({ body: c.body, at: c.createdAt }));
|
|
1366
|
-
const all = [...reviews, ...comments].sort((a, b) => (b.at || "").localeCompare(a.at || ""));
|
|
1367
|
-
if (all.length > 0) return all[0].body;
|
|
1368
|
-
const pr = getPr(prNumber, cwd);
|
|
1369
|
-
return pr.body;
|
|
1370
|
-
}
|
|
1371
|
-
function postPrReviewComment(prNumber, body, cwd) {
|
|
1372
|
-
try {
|
|
1373
|
-
gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
|
|
1374
|
-
} catch (err) {
|
|
1375
|
-
process.stderr.write(
|
|
1376
|
-
`[kody2] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1377
|
-
`
|
|
1378
|
-
);
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
// src/scripts/applyApprovals.ts
|
|
1383
|
-
var API_TIMEOUT_MS4 = 3e4;
|
|
1384
|
-
var applyApprovals = async (ctx) => {
|
|
1385
|
-
const issueArg = typeof ctx.args.issue === "number" ? ctx.args.issue : null;
|
|
1386
|
-
const prArg = typeof ctx.args.pr === "number" ? ctx.args.pr : null;
|
|
1387
|
-
const currentTarget = issueArg ? { type: "issue", number: issueArg } : prArg ? { type: "pr", number: prArg } : null;
|
|
1388
|
-
if (!currentTarget) {
|
|
1389
|
-
ctx.output.exitCode = 64;
|
|
1390
|
-
ctx.output.reason = "approve: must be invoked with --issue or --pr";
|
|
1391
|
-
return;
|
|
1392
|
-
}
|
|
1393
|
-
let state = null;
|
|
1394
|
-
try {
|
|
1395
|
-
state = readTaskState(currentTarget.type, currentTarget.number, ctx.cwd);
|
|
1396
|
-
} catch {
|
|
1397
|
-
state = null;
|
|
1398
|
-
}
|
|
1399
|
-
const issueNumber = currentTarget.type === "issue" ? currentTarget.number : state?.flow?.issueNumber ?? null;
|
|
1400
|
-
const flowName = state?.flow?.name ?? null;
|
|
1401
|
-
const confirmation = formatConfirmation(currentTarget, flowName, issueNumber);
|
|
1402
|
-
try {
|
|
1403
|
-
if (currentTarget.type === "issue") postIssueComment(currentTarget.number, confirmation, ctx.cwd);
|
|
1404
|
-
else postPrReviewComment(currentTarget.number, confirmation, ctx.cwd);
|
|
1405
|
-
} catch {
|
|
1406
|
-
}
|
|
1407
|
-
if (issueNumber && typeof flowName === "string" && flowName.length > 0) {
|
|
1408
|
-
try {
|
|
1409
|
-
execFileSync5("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${flowName}`], {
|
|
1410
|
-
timeout: API_TIMEOUT_MS4,
|
|
1411
|
-
cwd: ctx.cwd,
|
|
1412
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1413
|
-
});
|
|
1414
|
-
} catch (err) {
|
|
1415
|
-
process.stderr.write(
|
|
1416
|
-
`[kody2 approve] failed to re-trigger flow on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1417
|
-
`
|
|
1418
|
-
);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
ctx.output.exitCode = 0;
|
|
1422
|
-
};
|
|
1423
|
-
function formatConfirmation(current, flowName, issueNumber) {
|
|
1424
|
-
const lines = [];
|
|
1425
|
-
lines.push("\u2705 **kody2 risk gates approved.**");
|
|
1426
|
-
lines.push("");
|
|
1427
|
-
lines.push("The approval is recorded as this comment \u2014 kody2 reads it directly on the next run.");
|
|
1428
|
-
if (flowName && issueNumber) {
|
|
1429
|
-
lines.push("");
|
|
1430
|
-
if (current.type === "pr") {
|
|
1431
|
-
lines.push(
|
|
1432
|
-
`Re-triggering the \`${flowName}\` flow on the originating issue (#${issueNumber}) \u2014 it will resume from the existing branch/PR checkpoint.`
|
|
1433
|
-
);
|
|
1434
|
-
} else {
|
|
1435
|
-
lines.push(`Re-triggering the \`${flowName}\` flow now \u2014 it will resume from the existing branch checkpoint.`);
|
|
1436
|
-
}
|
|
1437
|
-
} else {
|
|
1438
|
-
lines.push("");
|
|
1439
|
-
lines.push("No active flow found in task state. Post `@kody2 <command>` to resume manually.");
|
|
1440
|
-
}
|
|
1441
|
-
return lines.join("\n");
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
1244
|
// src/scripts/buildSyntheticPlugin.ts
|
|
1445
1245
|
import * as fs8 from "fs";
|
|
1446
1246
|
import * as os2 from "os";
|
|
@@ -1535,7 +1335,7 @@ function copyDir(src, dst) {
|
|
|
1535
1335
|
}
|
|
1536
1336
|
|
|
1537
1337
|
// src/coverage.ts
|
|
1538
|
-
import { execFileSync as
|
|
1338
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
1539
1339
|
function patternToRegex(pattern) {
|
|
1540
1340
|
let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
1541
1341
|
s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
|
|
@@ -1553,7 +1353,7 @@ function renderSiblingPath(file, requireSibling) {
|
|
|
1553
1353
|
}
|
|
1554
1354
|
function safeGit(args, cwd) {
|
|
1555
1355
|
try {
|
|
1556
|
-
return
|
|
1356
|
+
return execFileSync4("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
|
|
1557
1357
|
} catch {
|
|
1558
1358
|
return "";
|
|
1559
1359
|
}
|
|
@@ -1641,7 +1441,9 @@ function parseAgentResult(finalText) {
|
|
|
1641
1441
|
failureReason: failedMatch[1].trim()
|
|
1642
1442
|
};
|
|
1643
1443
|
}
|
|
1644
|
-
|
|
1444
|
+
const hasDoneMarker = /(^|\n)\s*DONE\b/i.test(text);
|
|
1445
|
+
const hasCommitMsg = /^[ \t]*COMMIT_MSG\s*:/im.test(text);
|
|
1446
|
+
if (!hasDoneMarker && !hasCommitMsg) {
|
|
1645
1447
|
return {
|
|
1646
1448
|
done: false,
|
|
1647
1449
|
commitMessage: "",
|
|
@@ -1759,10 +1561,10 @@ function defaultLabelMap() {
|
|
|
1759
1561
|
}
|
|
1760
1562
|
|
|
1761
1563
|
// src/scripts/commitAndPush.ts
|
|
1762
|
-
import { execFileSync as
|
|
1564
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
1763
1565
|
|
|
1764
1566
|
// src/commit.ts
|
|
1765
|
-
import { execFileSync as
|
|
1567
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
1766
1568
|
import * as fs10 from "fs";
|
|
1767
1569
|
import * as path9 from "path";
|
|
1768
1570
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
@@ -1792,7 +1594,7 @@ var CONVENTIONAL_PREFIXES = [
|
|
|
1792
1594
|
];
|
|
1793
1595
|
function git(args, cwd) {
|
|
1794
1596
|
try {
|
|
1795
|
-
return
|
|
1597
|
+
return execFileSync5("git", args, {
|
|
1796
1598
|
encoding: "utf-8",
|
|
1797
1599
|
timeout: 12e4,
|
|
1798
1600
|
cwd,
|
|
@@ -1850,7 +1652,7 @@ function isForbiddenPath(p) {
|
|
|
1850
1652
|
return false;
|
|
1851
1653
|
}
|
|
1852
1654
|
function listChangedFiles(cwd) {
|
|
1853
|
-
const raw =
|
|
1655
|
+
const raw = execFileSync5("git", ["status", "--porcelain=v1", "-z"], {
|
|
1854
1656
|
encoding: "utf-8",
|
|
1855
1657
|
cwd,
|
|
1856
1658
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -1862,7 +1664,7 @@ function listChangedFiles(cwd) {
|
|
|
1862
1664
|
}
|
|
1863
1665
|
function listFilesInCommit(ref = "HEAD", cwd) {
|
|
1864
1666
|
try {
|
|
1865
|
-
const raw =
|
|
1667
|
+
const raw = execFileSync5("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
|
|
1866
1668
|
encoding: "utf-8",
|
|
1867
1669
|
cwd,
|
|
1868
1670
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -1942,7 +1744,7 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
1942
1744
|
const kind = profile.name;
|
|
1943
1745
|
if (kind === "resolve") {
|
|
1944
1746
|
try {
|
|
1945
|
-
|
|
1747
|
+
execFileSync6("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
|
|
1946
1748
|
} catch {
|
|
1947
1749
|
}
|
|
1948
1750
|
} else {
|
|
@@ -2564,8 +2366,8 @@ var discoverQaContext = async (ctx) => {
|
|
|
2564
2366
|
};
|
|
2565
2367
|
|
|
2566
2368
|
// src/scripts/dispatch.ts
|
|
2567
|
-
import { execFileSync as
|
|
2568
|
-
var
|
|
2369
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
2370
|
+
var API_TIMEOUT_MS3 = 3e4;
|
|
2569
2371
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
2570
2372
|
const next = args?.next;
|
|
2571
2373
|
if (!next) {
|
|
@@ -2587,8 +2389,8 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
2587
2389
|
const sub = usePr ? "pr" : "issue";
|
|
2588
2390
|
const body = `@kody2 ${next}`;
|
|
2589
2391
|
try {
|
|
2590
|
-
|
|
2591
|
-
timeout:
|
|
2392
|
+
execFileSync7("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
2393
|
+
timeout: API_TIMEOUT_MS3,
|
|
2592
2394
|
cwd: ctx.cwd,
|
|
2593
2395
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2594
2396
|
});
|
|
@@ -2606,6 +2408,141 @@ function parsePr(url) {
|
|
|
2606
2408
|
return Number.isFinite(n) ? n : null;
|
|
2607
2409
|
}
|
|
2608
2410
|
|
|
2411
|
+
// src/issue.ts
|
|
2412
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
2413
|
+
var API_TIMEOUT_MS4 = 3e4;
|
|
2414
|
+
function ghToken2() {
|
|
2415
|
+
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
2416
|
+
}
|
|
2417
|
+
function gh2(args, options) {
|
|
2418
|
+
const token = ghToken2();
|
|
2419
|
+
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
2420
|
+
return execFileSync8("gh", args, {
|
|
2421
|
+
encoding: "utf-8",
|
|
2422
|
+
timeout: API_TIMEOUT_MS4,
|
|
2423
|
+
cwd: options?.cwd,
|
|
2424
|
+
env,
|
|
2425
|
+
input: options?.input,
|
|
2426
|
+
stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
|
|
2427
|
+
}).trim();
|
|
2428
|
+
}
|
|
2429
|
+
function getIssue(issueNumber, cwd) {
|
|
2430
|
+
const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
|
|
2431
|
+
const parsed = JSON.parse(output);
|
|
2432
|
+
if (typeof parsed?.title !== "string") {
|
|
2433
|
+
throw new Error(`Issue #${issueNumber}: unexpected response shape`);
|
|
2434
|
+
}
|
|
2435
|
+
return {
|
|
2436
|
+
number: parsed.number ?? issueNumber,
|
|
2437
|
+
title: parsed.title,
|
|
2438
|
+
body: parsed.body ?? "",
|
|
2439
|
+
comments: (parsed.comments ?? []).map((c) => ({
|
|
2440
|
+
body: c.body ?? "",
|
|
2441
|
+
author: c.author?.login ?? "unknown",
|
|
2442
|
+
createdAt: c.createdAt ?? ""
|
|
2443
|
+
})),
|
|
2444
|
+
labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
function stripKody2Mentions(body) {
|
|
2448
|
+
return body.replace(/(@)(kody2)/gi, "$1\u200B$2");
|
|
2449
|
+
}
|
|
2450
|
+
function postIssueComment(issueNumber, body, cwd) {
|
|
2451
|
+
try {
|
|
2452
|
+
gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
|
|
2453
|
+
} catch (err) {
|
|
2454
|
+
process.stderr.write(
|
|
2455
|
+
`[kody2] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2456
|
+
`
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
function truncate2(s, maxBytes) {
|
|
2461
|
+
if (s.length <= maxBytes) return s;
|
|
2462
|
+
return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
|
|
2463
|
+
}
|
|
2464
|
+
function getPr(prNumber, cwd) {
|
|
2465
|
+
const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
|
|
2466
|
+
cwd
|
|
2467
|
+
});
|
|
2468
|
+
const parsed = JSON.parse(output);
|
|
2469
|
+
if (typeof parsed?.title !== "string") {
|
|
2470
|
+
throw new Error(`PR #${prNumber}: unexpected response shape`);
|
|
2471
|
+
}
|
|
2472
|
+
return {
|
|
2473
|
+
number: parsed.number ?? prNumber,
|
|
2474
|
+
title: parsed.title,
|
|
2475
|
+
body: parsed.body ?? "",
|
|
2476
|
+
headRefName: String(parsed.headRefName ?? ""),
|
|
2477
|
+
baseRefName: String(parsed.baseRefName ?? ""),
|
|
2478
|
+
state: String(parsed.state ?? "")
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
function getPrDiff(prNumber, cwd) {
|
|
2482
|
+
try {
|
|
2483
|
+
return gh2(["pr", "diff", String(prNumber)], { cwd });
|
|
2484
|
+
} catch (err) {
|
|
2485
|
+
process.stderr.write(
|
|
2486
|
+
`[kody2] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2487
|
+
`
|
|
2488
|
+
);
|
|
2489
|
+
return "";
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
function getPrReviews(prNumber, cwd) {
|
|
2493
|
+
try {
|
|
2494
|
+
const output = gh2(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
|
|
2495
|
+
const parsed = JSON.parse(output);
|
|
2496
|
+
if (!Array.isArray(parsed?.reviews)) return [];
|
|
2497
|
+
return parsed.reviews.map(
|
|
2498
|
+
(r) => ({
|
|
2499
|
+
body: r.body ?? "",
|
|
2500
|
+
state: r.state ?? "",
|
|
2501
|
+
author: r.author?.login ?? "unknown",
|
|
2502
|
+
submittedAt: r.submittedAt ?? ""
|
|
2503
|
+
})
|
|
2504
|
+
);
|
|
2505
|
+
} catch {
|
|
2506
|
+
return [];
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
function getPrComments(prNumber, cwd) {
|
|
2510
|
+
try {
|
|
2511
|
+
const output = gh2(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
|
|
2512
|
+
const parsed = JSON.parse(output);
|
|
2513
|
+
if (!Array.isArray(parsed?.comments)) return [];
|
|
2514
|
+
return parsed.comments.map((c) => ({
|
|
2515
|
+
body: c.body ?? "",
|
|
2516
|
+
author: c.author?.login ?? "unknown",
|
|
2517
|
+
createdAt: c.createdAt ?? ""
|
|
2518
|
+
})).filter((c) => c.body.trim().length > 0);
|
|
2519
|
+
} catch {
|
|
2520
|
+
return [];
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
var VERDICT_HEADING = /(^|\n)\s*#{1,6}\s*Verdict\s*:/i;
|
|
2524
|
+
function isReviewShaped(body) {
|
|
2525
|
+
return VERDICT_HEADING.test(body);
|
|
2526
|
+
}
|
|
2527
|
+
function getPrLatestReviewBody(prNumber, cwd) {
|
|
2528
|
+
const reviews = getPrReviews(prNumber, cwd).filter((r) => r.body.trim().length > 0).map((r) => ({ body: r.body, at: r.submittedAt }));
|
|
2529
|
+
const comments = getPrComments(prNumber, cwd).filter((c) => isReviewShaped(c.body)).map((c) => ({ body: c.body, at: c.createdAt }));
|
|
2530
|
+
const all = [...reviews, ...comments].sort((a, b) => (b.at || "").localeCompare(a.at || ""));
|
|
2531
|
+
if (all.length > 0) return all[0].body;
|
|
2532
|
+
const pr = getPr(prNumber, cwd);
|
|
2533
|
+
return pr.body;
|
|
2534
|
+
}
|
|
2535
|
+
function postPrReviewComment(prNumber, body, cwd) {
|
|
2536
|
+
try {
|
|
2537
|
+
gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
|
|
2538
|
+
} catch (err) {
|
|
2539
|
+
process.stderr.write(
|
|
2540
|
+
`[kody2] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2541
|
+
`
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2609
2546
|
// src/pr.ts
|
|
2610
2547
|
var TITLE_MAX = 72;
|
|
2611
2548
|
function stripTitlePrefixes(raw) {
|
|
@@ -2692,10 +2629,7 @@ function ensurePr(opts) {
|
|
|
2692
2629
|
try {
|
|
2693
2630
|
gh2(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: opts.cwd });
|
|
2694
2631
|
} catch (err) {
|
|
2695
|
-
|
|
2696
|
-
`[kody2] failed to update PR #${existing.number}: ${err instanceof Error ? err.message : String(err)}
|
|
2697
|
-
`
|
|
2698
|
-
);
|
|
2632
|
+
throw new Error(`gh pr edit #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2699
2633
|
}
|
|
2700
2634
|
return { url: existing.url, number: existing.number, draft: opts.draft, action: "updated" };
|
|
2701
2635
|
}
|
|
@@ -2771,7 +2705,7 @@ function computeFailureReason(ctx) {
|
|
|
2771
2705
|
}
|
|
2772
2706
|
|
|
2773
2707
|
// src/scripts/finishFlow.ts
|
|
2774
|
-
import { execFileSync as
|
|
2708
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
2775
2709
|
|
|
2776
2710
|
// src/registry.ts
|
|
2777
2711
|
import * as fs14 from "fs";
|
|
@@ -2959,7 +2893,7 @@ function errMsg(err) {
|
|
|
2959
2893
|
}
|
|
2960
2894
|
|
|
2961
2895
|
// src/scripts/finishFlow.ts
|
|
2962
|
-
var
|
|
2896
|
+
var API_TIMEOUT_MS5 = 3e4;
|
|
2963
2897
|
var STATUS_ICON = {
|
|
2964
2898
|
"review-passed": "\u2705",
|
|
2965
2899
|
"fix-applied": "\u2705",
|
|
@@ -2991,8 +2925,8 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
2991
2925
|
**PR:** ${state.core.prUrl}` : "";
|
|
2992
2926
|
const body = `${icon} kody2 flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
2993
2927
|
try {
|
|
2994
|
-
|
|
2995
|
-
timeout:
|
|
2928
|
+
execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
2929
|
+
timeout: API_TIMEOUT_MS5,
|
|
2996
2930
|
cwd: ctx.cwd,
|
|
2997
2931
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2998
2932
|
});
|
|
@@ -3005,7 +2939,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
3005
2939
|
};
|
|
3006
2940
|
|
|
3007
2941
|
// src/branch.ts
|
|
3008
|
-
import { execFileSync as
|
|
2942
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3009
2943
|
var UncommittedChangesError = class extends Error {
|
|
3010
2944
|
constructor(branch) {
|
|
3011
2945
|
super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
|
|
@@ -3015,7 +2949,7 @@ var UncommittedChangesError = class extends Error {
|
|
|
3015
2949
|
branch;
|
|
3016
2950
|
};
|
|
3017
2951
|
function git2(args, cwd) {
|
|
3018
|
-
return
|
|
2952
|
+
return execFileSync10("git", args, {
|
|
3019
2953
|
encoding: "utf-8",
|
|
3020
2954
|
timeout: 3e4,
|
|
3021
2955
|
cwd,
|
|
@@ -3040,7 +2974,7 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
3040
2974
|
SKIP_HOOKS: "1",
|
|
3041
2975
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
3042
2976
|
};
|
|
3043
|
-
|
|
2977
|
+
execFileSync10("gh", ["pr", "checkout", String(prNumber)], {
|
|
3044
2978
|
cwd,
|
|
3045
2979
|
env,
|
|
3046
2980
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3107,7 +3041,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
3107
3041
|
}
|
|
3108
3042
|
|
|
3109
3043
|
// src/gha.ts
|
|
3110
|
-
import { execFileSync as
|
|
3044
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
3111
3045
|
import * as fs15 from "fs";
|
|
3112
3046
|
function getRunUrl() {
|
|
3113
3047
|
const server = process.env.GITHUB_SERVER_URL;
|
|
@@ -3150,7 +3084,7 @@ function reactToTriggerComment(cwd) {
|
|
|
3150
3084
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
3151
3085
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
3152
3086
|
try {
|
|
3153
|
-
|
|
3087
|
+
execFileSync11("gh", args, opts);
|
|
3154
3088
|
return;
|
|
3155
3089
|
} catch (err) {
|
|
3156
3090
|
lastErr = err;
|
|
@@ -3163,13 +3097,13 @@ function reactToTriggerComment(cwd) {
|
|
|
3163
3097
|
}
|
|
3164
3098
|
function sleepMs(ms) {
|
|
3165
3099
|
try {
|
|
3166
|
-
|
|
3100
|
+
execFileSync11("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
3167
3101
|
} catch {
|
|
3168
3102
|
}
|
|
3169
3103
|
}
|
|
3170
3104
|
|
|
3171
3105
|
// src/workflow.ts
|
|
3172
|
-
import { execFileSync as
|
|
3106
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
3173
3107
|
var GH_TIMEOUT_MS = 3e4;
|
|
3174
3108
|
function ghToken3() {
|
|
3175
3109
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -3177,7 +3111,7 @@ function ghToken3() {
|
|
|
3177
3111
|
function gh3(args, cwd) {
|
|
3178
3112
|
const token = ghToken3();
|
|
3179
3113
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
3180
|
-
return
|
|
3114
|
+
return execFileSync12("gh", args, {
|
|
3181
3115
|
encoding: "utf-8",
|
|
3182
3116
|
timeout: GH_TIMEOUT_MS,
|
|
3183
3117
|
cwd,
|
|
@@ -3361,7 +3295,7 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
3361
3295
|
}
|
|
3362
3296
|
|
|
3363
3297
|
// src/scripts/initFlow.ts
|
|
3364
|
-
import { execFileSync as
|
|
3298
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
3365
3299
|
import * as fs17 from "fs";
|
|
3366
3300
|
import * as path15 from "path";
|
|
3367
3301
|
|
|
@@ -3402,7 +3336,7 @@ function qualityCommandsFor(pm) {
|
|
|
3402
3336
|
function detectOwnerRepo(cwd) {
|
|
3403
3337
|
let url;
|
|
3404
3338
|
try {
|
|
3405
|
-
url =
|
|
3339
|
+
url = execFileSync13("git", ["remote", "get-url", "origin"], {
|
|
3406
3340
|
cwd,
|
|
3407
3341
|
encoding: "utf-8",
|
|
3408
3342
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3486,7 +3420,7 @@ jobs:
|
|
|
3486
3420
|
`;
|
|
3487
3421
|
function defaultBranchFromGit(cwd) {
|
|
3488
3422
|
try {
|
|
3489
|
-
const ref =
|
|
3423
|
+
const ref = execFileSync13("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
3490
3424
|
cwd,
|
|
3491
3425
|
encoding: "utf-8",
|
|
3492
3426
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3494,7 +3428,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
3494
3428
|
return ref.replace("refs/remotes/origin/", "");
|
|
3495
3429
|
} catch {
|
|
3496
3430
|
try {
|
|
3497
|
-
return
|
|
3431
|
+
return execFileSync13("git", ["branch", "--show-current"], {
|
|
3498
3432
|
cwd,
|
|
3499
3433
|
encoding: "utf-8",
|
|
3500
3434
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3787,8 +3721,8 @@ var persistFlowState = async (ctx) => {
|
|
|
3787
3721
|
};
|
|
3788
3722
|
|
|
3789
3723
|
// src/scripts/postClassification.ts
|
|
3790
|
-
import { execFileSync as
|
|
3791
|
-
var
|
|
3724
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
3725
|
+
var API_TIMEOUT_MS6 = 3e4;
|
|
3792
3726
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
3793
3727
|
var postClassification = async (ctx) => {
|
|
3794
3728
|
const issueNumber = ctx.args.issue;
|
|
@@ -3817,9 +3751,9 @@ var postClassification = async (ctx) => {
|
|
|
3817
3751
|
ctx.cwd
|
|
3818
3752
|
);
|
|
3819
3753
|
try {
|
|
3820
|
-
|
|
3754
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${classification}`], {
|
|
3821
3755
|
cwd: ctx.cwd,
|
|
3822
|
-
timeout:
|
|
3756
|
+
timeout: API_TIMEOUT_MS6,
|
|
3823
3757
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3824
3758
|
});
|
|
3825
3759
|
} catch (err) {
|
|
@@ -3851,9 +3785,9 @@ function parseClassification(prSummary) {
|
|
|
3851
3785
|
}
|
|
3852
3786
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
3853
3787
|
try {
|
|
3854
|
-
|
|
3788
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
3855
3789
|
cwd,
|
|
3856
|
-
timeout:
|
|
3790
|
+
timeout: API_TIMEOUT_MS6,
|
|
3857
3791
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3858
3792
|
});
|
|
3859
3793
|
} catch {
|
|
@@ -4035,7 +3969,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
4035
3969
|
};
|
|
4036
3970
|
|
|
4037
3971
|
// src/scripts/releaseFlow.ts
|
|
4038
|
-
import { execFileSync as
|
|
3972
|
+
import { execFileSync as execFileSync15, spawnSync } from "child_process";
|
|
4039
3973
|
import * as fs18 from "fs";
|
|
4040
3974
|
import * as path16 from "path";
|
|
4041
3975
|
function bumpVersion(current, bump) {
|
|
@@ -4065,7 +3999,7 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
4065
3999
|
const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
|
|
4066
4000
|
let log = "";
|
|
4067
4001
|
try {
|
|
4068
|
-
log =
|
|
4002
|
+
log = execFileSync15("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
|
|
4069
4003
|
cwd,
|
|
4070
4004
|
encoding: "utf-8",
|
|
4071
4005
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4122,7 +4056,7 @@ ${entry}${prior.slice(idx + 1)}`);
|
|
|
4122
4056
|
}
|
|
4123
4057
|
}
|
|
4124
4058
|
function git3(args, cwd, timeout = 6e4) {
|
|
4125
|
-
return
|
|
4059
|
+
return execFileSync15("git", args, {
|
|
4126
4060
|
encoding: "utf-8",
|
|
4127
4061
|
timeout,
|
|
4128
4062
|
cwd,
|
|
@@ -4425,7 +4359,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
4425
4359
|
};
|
|
4426
4360
|
|
|
4427
4361
|
// src/scripts/resolveFlow.ts
|
|
4428
|
-
import { execFileSync as
|
|
4362
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
4429
4363
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
4430
4364
|
var resolveFlow = async (ctx) => {
|
|
4431
4365
|
const prNumber = ctx.args.pr;
|
|
@@ -4477,7 +4411,7 @@ var resolveFlow = async (ctx) => {
|
|
|
4477
4411
|
};
|
|
4478
4412
|
function getConflictedFiles(cwd) {
|
|
4479
4413
|
try {
|
|
4480
|
-
const out =
|
|
4414
|
+
const out = execFileSync16("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
4481
4415
|
encoding: "utf-8",
|
|
4482
4416
|
cwd,
|
|
4483
4417
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -4492,7 +4426,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
4492
4426
|
let total = 0;
|
|
4493
4427
|
for (const f of files) {
|
|
4494
4428
|
try {
|
|
4495
|
-
const content =
|
|
4429
|
+
const content = execFileSync16("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
4496
4430
|
const snippet = `### ${f}
|
|
4497
4431
|
|
|
4498
4432
|
\`\`\`
|
|
@@ -4560,295 +4494,6 @@ function tryPostPr4(prNumber, body, cwd) {
|
|
|
4560
4494
|
}
|
|
4561
4495
|
}
|
|
4562
4496
|
|
|
4563
|
-
// src/scripts/riskGate.ts
|
|
4564
|
-
import { execFileSync as execFileSync18 } from "child_process";
|
|
4565
|
-
var ALL_GATES = ["secrets", "workflow-edit", "large-diff", "dep-change", "test-deletion"];
|
|
4566
|
-
var WAITING_LABEL = "kody:waiting";
|
|
4567
|
-
var ADVISORY_MARKER = "kody2 risk gate halted the flow";
|
|
4568
|
-
var APPROVE_COMMAND = /(^|\s)@kody2\s+approve\b/i;
|
|
4569
|
-
var DEFAULT_MAX_FILES = 20;
|
|
4570
|
-
var DEFAULT_MAX_DELETIONS = 500;
|
|
4571
|
-
var riskGate = async (ctx, profile, _agent, args) => {
|
|
4572
|
-
const changedFiles = collectBranchChangedFiles(ctx);
|
|
4573
|
-
const gatesToRun = parseGates(args?.gates);
|
|
4574
|
-
const violations = evaluateGates(ctx, profile.name, changedFiles, gatesToRun, args);
|
|
4575
|
-
if (violations.length === 0) {
|
|
4576
|
-
ctx.data.riskGate = { violations: [], pending: [], decision: "allow" };
|
|
4577
|
-
return;
|
|
4578
|
-
}
|
|
4579
|
-
const targetType = ctx.data.commentTargetType;
|
|
4580
|
-
const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
|
|
4581
|
-
const approved = hasApproval(ctx, targetType, targetNumber);
|
|
4582
|
-
ctx.data.riskGate = {
|
|
4583
|
-
violations,
|
|
4584
|
-
pending: approved ? [] : violations,
|
|
4585
|
-
decision: approved ? "allow" : "halt"
|
|
4586
|
-
};
|
|
4587
|
-
if (approved || !targetType || targetNumber <= 0) return;
|
|
4588
|
-
try {
|
|
4589
|
-
setKodyLabel(
|
|
4590
|
-
targetNumber,
|
|
4591
|
-
{
|
|
4592
|
-
label: WAITING_LABEL,
|
|
4593
|
-
color: "fbca04",
|
|
4594
|
-
description: "kody2: awaiting human approval of risk gate(s)"
|
|
4595
|
-
},
|
|
4596
|
-
ctx.cwd
|
|
4597
|
-
);
|
|
4598
|
-
} catch {
|
|
4599
|
-
}
|
|
4600
|
-
const compareUrl = computeCompareUrl(ctx);
|
|
4601
|
-
const body = formatAdvisory(violations, compareUrl);
|
|
4602
|
-
try {
|
|
4603
|
-
if (targetType === "issue") postIssueComment(targetNumber, body, ctx.cwd);
|
|
4604
|
-
else postPrReviewComment(targetNumber, body, ctx.cwd);
|
|
4605
|
-
} catch {
|
|
4606
|
-
}
|
|
4607
|
-
if (!ctx.output.reason) {
|
|
4608
|
-
ctx.output.reason = `risk gate halt: ${violations.map((p) => p.name).join(", ")}`;
|
|
4609
|
-
}
|
|
4610
|
-
};
|
|
4611
|
-
function hasApproval(ctx, targetType, targetNumber) {
|
|
4612
|
-
const surfaces = [];
|
|
4613
|
-
if (targetNumber > 0) surfaces.push(targetNumber);
|
|
4614
|
-
if (targetType === "pr") {
|
|
4615
|
-
const state = ctx.data.taskState;
|
|
4616
|
-
const issueNum = state?.flow?.issueNumber;
|
|
4617
|
-
if (typeof issueNum === "number" && issueNum > 0 && !surfaces.includes(issueNum)) {
|
|
4618
|
-
surfaces.push(issueNum);
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
for (const n of surfaces) {
|
|
4622
|
-
if (surfaceIsApproved(n, ctx.cwd)) return true;
|
|
4623
|
-
}
|
|
4624
|
-
return false;
|
|
4625
|
-
}
|
|
4626
|
-
function surfaceIsApproved(n, cwd) {
|
|
4627
|
-
let comments = [];
|
|
4628
|
-
try {
|
|
4629
|
-
comments = getIssue(n, cwd).comments;
|
|
4630
|
-
} catch {
|
|
4631
|
-
return false;
|
|
4632
|
-
}
|
|
4633
|
-
const advisoryAt = latestAdvisoryTimestamp(comments);
|
|
4634
|
-
for (const c of comments) {
|
|
4635
|
-
if (!APPROVE_COMMAND.test(c.body)) continue;
|
|
4636
|
-
if (advisoryAt !== null && c.createdAt <= advisoryAt) continue;
|
|
4637
|
-
return true;
|
|
4638
|
-
}
|
|
4639
|
-
return false;
|
|
4640
|
-
}
|
|
4641
|
-
function latestAdvisoryTimestamp(comments) {
|
|
4642
|
-
let latest = null;
|
|
4643
|
-
for (const c of comments) {
|
|
4644
|
-
if (!c.body.includes(ADVISORY_MARKER)) continue;
|
|
4645
|
-
if (latest === null || c.createdAt > latest) latest = c.createdAt;
|
|
4646
|
-
}
|
|
4647
|
-
return latest;
|
|
4648
|
-
}
|
|
4649
|
-
function evaluateGates(ctx, profileName, changedFiles, gatesToRun, args) {
|
|
4650
|
-
const violations = [];
|
|
4651
|
-
if (gatesToRun.includes("secrets")) {
|
|
4652
|
-
const hits = changedFiles.filter(isSecretPath);
|
|
4653
|
-
if (hits.length > 0) {
|
|
4654
|
-
violations.push({
|
|
4655
|
-
name: "secrets",
|
|
4656
|
-
severity: "hard",
|
|
4657
|
-
reason: `secret/credential files touched: ${preview(hits)}`
|
|
4658
|
-
});
|
|
4659
|
-
}
|
|
4660
|
-
}
|
|
4661
|
-
if (gatesToRun.includes("workflow-edit")) {
|
|
4662
|
-
const hits = changedFiles.filter((f) => f.startsWith(".github/workflows/"));
|
|
4663
|
-
if (hits.length > 0) {
|
|
4664
|
-
violations.push({
|
|
4665
|
-
name: "workflow-edit",
|
|
4666
|
-
severity: "soft",
|
|
4667
|
-
reason: `CI workflow files modified: ${preview(hits)}`
|
|
4668
|
-
});
|
|
4669
|
-
}
|
|
4670
|
-
}
|
|
4671
|
-
if (gatesToRun.includes("large-diff")) {
|
|
4672
|
-
const maxFiles = toPositiveInt(args?.maxFiles, DEFAULT_MAX_FILES);
|
|
4673
|
-
const maxDeletions = toPositiveInt(args?.maxDeletions, DEFAULT_MAX_DELETIONS);
|
|
4674
|
-
if (changedFiles.length > maxFiles) {
|
|
4675
|
-
violations.push({
|
|
4676
|
-
name: "large-diff",
|
|
4677
|
-
severity: "soft",
|
|
4678
|
-
reason: `${changedFiles.length} files changed (threshold: ${maxFiles})`
|
|
4679
|
-
});
|
|
4680
|
-
} else {
|
|
4681
|
-
const stats = computeDiffStats(ctx);
|
|
4682
|
-
if (stats && stats.deletions > maxDeletions) {
|
|
4683
|
-
violations.push({
|
|
4684
|
-
name: "large-diff",
|
|
4685
|
-
severity: "soft",
|
|
4686
|
-
reason: `${stats.deletions} lines deleted (threshold: ${maxDeletions})`
|
|
4687
|
-
});
|
|
4688
|
-
}
|
|
4689
|
-
}
|
|
4690
|
-
}
|
|
4691
|
-
if (gatesToRun.includes("dep-change") && profileName !== "chore") {
|
|
4692
|
-
const hits = changedFiles.filter(isDepFile);
|
|
4693
|
-
if (hits.length > 0) {
|
|
4694
|
-
violations.push({
|
|
4695
|
-
name: "dep-change",
|
|
4696
|
-
severity: "soft",
|
|
4697
|
-
reason: `dependency/lockfile changes outside a chore flow: ${preview(hits)}`
|
|
4698
|
-
});
|
|
4699
|
-
}
|
|
4700
|
-
}
|
|
4701
|
-
if (gatesToRun.includes("test-deletion")) {
|
|
4702
|
-
const deleted = listDeletedFilesInHeadCommit(ctx.cwd).filter(isTestFile);
|
|
4703
|
-
if (deleted.length > 0) {
|
|
4704
|
-
violations.push({
|
|
4705
|
-
name: "test-deletion",
|
|
4706
|
-
severity: "soft",
|
|
4707
|
-
reason: `test files deleted: ${preview(deleted)}`
|
|
4708
|
-
});
|
|
4709
|
-
}
|
|
4710
|
-
}
|
|
4711
|
-
return violations;
|
|
4712
|
-
}
|
|
4713
|
-
function parseGates(spec) {
|
|
4714
|
-
if (spec === void 0 || spec === null || spec === "") return ALL_GATES;
|
|
4715
|
-
const list = String(spec).split(",").map((s) => s.trim()).filter(Boolean);
|
|
4716
|
-
const valid = ALL_GATES;
|
|
4717
|
-
const matched = list.filter((n) => valid.includes(n));
|
|
4718
|
-
return matched.length > 0 ? matched : ALL_GATES;
|
|
4719
|
-
}
|
|
4720
|
-
function toPositiveInt(v, fallback) {
|
|
4721
|
-
const n = typeof v === "number" ? v : parseInt(String(v ?? ""), 10);
|
|
4722
|
-
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
4723
|
-
}
|
|
4724
|
-
function preview(list, max = 5) {
|
|
4725
|
-
if (list.length <= max) return list.join(", ");
|
|
4726
|
-
return `${list.slice(0, max).join(", ")} (+${list.length - max} more)`;
|
|
4727
|
-
}
|
|
4728
|
-
var SECRET_PATTERNS = [
|
|
4729
|
-
/(^|\/)\.env(\.|$)/i,
|
|
4730
|
-
/\.pem$/i,
|
|
4731
|
-
/\.key$/i,
|
|
4732
|
-
/(^|\/)(id_rsa|id_ed25519|id_ecdsa)(\.|$)/i,
|
|
4733
|
-
// Match the keyword anywhere inside the filename, as a whole word (so e.g.
|
|
4734
|
-
// `api-secrets.json`, `config/app.credentials.yaml`, `user-passwords.txt`
|
|
4735
|
-
// all trip — while false friends like `secretary.md` do not).
|
|
4736
|
-
/(^|\/)[^/]*\bsecrets?\b[^/]*$/i,
|
|
4737
|
-
/(^|\/)[^/]*\bcredentials?\b[^/]*$/i,
|
|
4738
|
-
/(^|\/)[^/]*\bpasswords?\b[^/]*$/i,
|
|
4739
|
-
/(^|\/)[^/]*\bapi[-_]keys?\b[^/]*$/i,
|
|
4740
|
-
/(^|\/)\.netrc$/i,
|
|
4741
|
-
/(^|\/)\.npmrc$/i
|
|
4742
|
-
];
|
|
4743
|
-
function isSecretPath(p) {
|
|
4744
|
-
return SECRET_PATTERNS.some((r) => r.test(p));
|
|
4745
|
-
}
|
|
4746
|
-
var DEP_FILES = /* @__PURE__ */ new Set([
|
|
4747
|
-
"package.json",
|
|
4748
|
-
"pnpm-lock.yaml",
|
|
4749
|
-
"package-lock.json",
|
|
4750
|
-
"yarn.lock",
|
|
4751
|
-
"requirements.txt",
|
|
4752
|
-
"Pipfile",
|
|
4753
|
-
"Pipfile.lock",
|
|
4754
|
-
"poetry.lock",
|
|
4755
|
-
"go.mod",
|
|
4756
|
-
"go.sum",
|
|
4757
|
-
"Cargo.toml",
|
|
4758
|
-
"Cargo.lock",
|
|
4759
|
-
"Gemfile",
|
|
4760
|
-
"Gemfile.lock"
|
|
4761
|
-
]);
|
|
4762
|
-
function isDepFile(p) {
|
|
4763
|
-
return DEP_FILES.has(p.split("/").pop() ?? "");
|
|
4764
|
-
}
|
|
4765
|
-
function isTestFile(p) {
|
|
4766
|
-
return /(^|\/)(tests?|__tests__|spec)\//i.test(p) || /\.(test|spec)\.[a-z0-9]+$/i.test(p);
|
|
4767
|
-
}
|
|
4768
|
-
function collectBranchChangedFiles(ctx) {
|
|
4769
|
-
const base = ctx.config.git.defaultBranch;
|
|
4770
|
-
for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
|
|
4771
|
-
try {
|
|
4772
|
-
const out = execFileSync18("git", ["diff", "--name-only", ref], {
|
|
4773
|
-
cwd: ctx.cwd,
|
|
4774
|
-
encoding: "utf-8",
|
|
4775
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4776
|
-
});
|
|
4777
|
-
const files = out.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
4778
|
-
if (files.length > 0) return files;
|
|
4779
|
-
} catch {
|
|
4780
|
-
}
|
|
4781
|
-
}
|
|
4782
|
-
return ctx.data.changedFiles ?? [];
|
|
4783
|
-
}
|
|
4784
|
-
function computeDiffStats(ctx) {
|
|
4785
|
-
const base = ctx.config.git.defaultBranch;
|
|
4786
|
-
for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
|
|
4787
|
-
try {
|
|
4788
|
-
const out = execFileSync18("git", ["diff", "--shortstat", ref], {
|
|
4789
|
-
cwd: ctx.cwd,
|
|
4790
|
-
encoding: "utf-8",
|
|
4791
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4792
|
-
}).trim();
|
|
4793
|
-
if (out) return parseShortstat(out);
|
|
4794
|
-
} catch {
|
|
4795
|
-
}
|
|
4796
|
-
}
|
|
4797
|
-
return null;
|
|
4798
|
-
}
|
|
4799
|
-
function parseShortstat(s) {
|
|
4800
|
-
const ins = /(\d+)\s+insertions?/.exec(s);
|
|
4801
|
-
const del = /(\d+)\s+deletions?/.exec(s);
|
|
4802
|
-
return {
|
|
4803
|
-
insertions: ins ? parseInt(ins[1], 10) : 0,
|
|
4804
|
-
deletions: del ? parseInt(del[1], 10) : 0
|
|
4805
|
-
};
|
|
4806
|
-
}
|
|
4807
|
-
function listDeletedFilesInHeadCommit(cwd) {
|
|
4808
|
-
try {
|
|
4809
|
-
const out = execFileSync18("git", ["show", "--name-status", "--pretty=format:", "HEAD"], {
|
|
4810
|
-
cwd,
|
|
4811
|
-
encoding: "utf-8",
|
|
4812
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4813
|
-
});
|
|
4814
|
-
return out.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("D ")).map((l) => l.slice(2).trim()).filter(Boolean);
|
|
4815
|
-
} catch {
|
|
4816
|
-
return [];
|
|
4817
|
-
}
|
|
4818
|
-
}
|
|
4819
|
-
function formatAdvisory(violations, compareUrl) {
|
|
4820
|
-
const lines = [];
|
|
4821
|
-
lines.push(`\u23F8\uFE0F **${ADVISORY_MARKER}.**`);
|
|
4822
|
-
lines.push("");
|
|
4823
|
-
lines.push("The branch was pushed but **no PR was opened** \u2014 waiting for human approval:");
|
|
4824
|
-
lines.push("");
|
|
4825
|
-
for (const v of violations) {
|
|
4826
|
-
lines.push(`- **\`${v.name}\`** _(${v.severity})_ \u2014 ${v.reason}`);
|
|
4827
|
-
}
|
|
4828
|
-
lines.push("");
|
|
4829
|
-
if (compareUrl) {
|
|
4830
|
-
lines.push(`\u{1F4CE} Review the branch diff: ${compareUrl}`);
|
|
4831
|
-
lines.push("");
|
|
4832
|
-
}
|
|
4833
|
-
lines.push("**To approve and resume**, post a comment on this issue or PR:");
|
|
4834
|
-
lines.push("");
|
|
4835
|
-
lines.push("> `@kody2 approve`");
|
|
4836
|
-
lines.push("");
|
|
4837
|
-
lines.push(
|
|
4838
|
-
"kody2 will open the PR and continue the flow from this checkpoint. No re-running the agent."
|
|
4839
|
-
);
|
|
4840
|
-
return lines.join("\n");
|
|
4841
|
-
}
|
|
4842
|
-
function computeCompareUrl(ctx) {
|
|
4843
|
-
const branch = ctx.data.branch;
|
|
4844
|
-
if (!branch) return null;
|
|
4845
|
-
const owner = ctx.config.github?.owner;
|
|
4846
|
-
const repo = ctx.config.github?.repo;
|
|
4847
|
-
if (!owner || !repo) return null;
|
|
4848
|
-
const base = ctx.config.git.defaultBranch;
|
|
4849
|
-
return `https://github.com/${owner}/${repo}/compare/${base}...${branch}`;
|
|
4850
|
-
}
|
|
4851
|
-
|
|
4852
4497
|
// src/scripts/runFlow.ts
|
|
4853
4498
|
var runFlow = async (ctx) => {
|
|
4854
4499
|
const issueNumber = ctx.args.issue;
|
|
@@ -4945,8 +4590,8 @@ var skipAgent = async (ctx) => {
|
|
|
4945
4590
|
};
|
|
4946
4591
|
|
|
4947
4592
|
// src/scripts/startFlow.ts
|
|
4948
|
-
import { execFileSync as
|
|
4949
|
-
var
|
|
4593
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
4594
|
+
var API_TIMEOUT_MS7 = 3e4;
|
|
4950
4595
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
4951
4596
|
const entry = args?.entry;
|
|
4952
4597
|
if (!entry) {
|
|
@@ -4979,8 +4624,8 @@ function postKody2Comment(target, issueNumber, state, next, cwd) {
|
|
|
4979
4624
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
4980
4625
|
const body = `@kody2 ${next}`;
|
|
4981
4626
|
try {
|
|
4982
|
-
|
|
4983
|
-
timeout:
|
|
4627
|
+
execFileSync17("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
4628
|
+
timeout: API_TIMEOUT_MS7,
|
|
4984
4629
|
cwd,
|
|
4985
4630
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4986
4631
|
});
|
|
@@ -4999,7 +4644,7 @@ function parsePr2(url) {
|
|
|
4999
4644
|
}
|
|
5000
4645
|
|
|
5001
4646
|
// src/scripts/syncFlow.ts
|
|
5002
|
-
import { execFileSync as
|
|
4647
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
5003
4648
|
var syncFlow = async (ctx) => {
|
|
5004
4649
|
ctx.skipAgent = true;
|
|
5005
4650
|
const prNumber = ctx.args.pr;
|
|
@@ -5058,7 +4703,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
5058
4703
|
}
|
|
5059
4704
|
function revParseHead(cwd) {
|
|
5060
4705
|
try {
|
|
5061
|
-
return
|
|
4706
|
+
return execFileSync18("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
5062
4707
|
} catch {
|
|
5063
4708
|
return "";
|
|
5064
4709
|
}
|
|
@@ -5066,9 +4711,9 @@ function revParseHead(cwd) {
|
|
|
5066
4711
|
function pushBranch(branch, cwd) {
|
|
5067
4712
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
5068
4713
|
try {
|
|
5069
|
-
|
|
4714
|
+
execFileSync18("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
5070
4715
|
} catch {
|
|
5071
|
-
|
|
4716
|
+
execFileSync18("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
5072
4717
|
cwd,
|
|
5073
4718
|
env,
|
|
5074
4719
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5310,9 +4955,7 @@ var postflightScripts = {
|
|
|
5310
4955
|
finishFlow,
|
|
5311
4956
|
advanceFlow,
|
|
5312
4957
|
persistFlowState,
|
|
5313
|
-
postClassification
|
|
5314
|
-
riskGate,
|
|
5315
|
-
applyApprovals
|
|
4958
|
+
postClassification
|
|
5316
4959
|
};
|
|
5317
4960
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
5318
4961
|
...Object.keys(preflightScripts),
|
|
@@ -5320,7 +4963,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
5320
4963
|
]);
|
|
5321
4964
|
|
|
5322
4965
|
// src/tools.ts
|
|
5323
|
-
import { execFileSync as
|
|
4966
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
5324
4967
|
function verifyCliTools(tools, cwd) {
|
|
5325
4968
|
const out = [];
|
|
5326
4969
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -5353,7 +4996,7 @@ function verifyOne(tool, cwd) {
|
|
|
5353
4996
|
}
|
|
5354
4997
|
function runShell2(cmd, cwd, timeoutMs = 3e4) {
|
|
5355
4998
|
try {
|
|
5356
|
-
|
|
4999
|
+
execFileSync19("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
5357
5000
|
return true;
|
|
5358
5001
|
} catch {
|
|
5359
5002
|
return false;
|
|
@@ -5683,7 +5326,7 @@ function detectPackageManager2(cwd) {
|
|
|
5683
5326
|
}
|
|
5684
5327
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
5685
5328
|
try {
|
|
5686
|
-
|
|
5329
|
+
execFileSync20(cmd, args, {
|
|
5687
5330
|
cwd,
|
|
5688
5331
|
stdio: stream ? "inherit" : "pipe",
|
|
5689
5332
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -5696,7 +5339,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
5696
5339
|
}
|
|
5697
5340
|
function isOnPath(bin) {
|
|
5698
5341
|
try {
|
|
5699
|
-
|
|
5342
|
+
execFileSync20("which", [bin], { stdio: "pipe" });
|
|
5700
5343
|
return true;
|
|
5701
5344
|
} catch {
|
|
5702
5345
|
return false;
|
|
@@ -5730,7 +5373,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5730
5373
|
} catch {
|
|
5731
5374
|
}
|
|
5732
5375
|
try {
|
|
5733
|
-
|
|
5376
|
+
execFileSync20("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
5734
5377
|
process.stdout.write("\u2192 kody2: litellm already installed\n");
|
|
5735
5378
|
return 0;
|
|
5736
5379
|
} catch {
|
|
@@ -5740,16 +5383,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5740
5383
|
}
|
|
5741
5384
|
function configureGitIdentity(cwd) {
|
|
5742
5385
|
try {
|
|
5743
|
-
const name =
|
|
5386
|
+
const name = execFileSync20("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
5744
5387
|
if (name) return;
|
|
5745
5388
|
} catch {
|
|
5746
5389
|
}
|
|
5747
5390
|
try {
|
|
5748
|
-
|
|
5391
|
+
execFileSync20("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
5749
5392
|
} catch {
|
|
5750
5393
|
}
|
|
5751
5394
|
try {
|
|
5752
|
-
|
|
5395
|
+
execFileSync20("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
5753
5396
|
cwd,
|
|
5754
5397
|
stdio: "pipe"
|
|
5755
5398
|
});
|
|
@@ -5926,9 +5569,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
5926
5569
|
if (paths.length === 0) return;
|
|
5927
5570
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
5928
5571
|
try {
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5572
|
+
execFileSync21("git", ["add", ...paths], opts);
|
|
5573
|
+
execFileSync21("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
5574
|
+
execFileSync21("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
5932
5575
|
} catch (err) {
|
|
5933
5576
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5934
5577
|
process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
|
|
@@ -49,21 +49,11 @@
|
|
|
49
49
|
"description": "kody2: applying review feedback"
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"script": "loadConventions"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"script": "loadCoverageRules"
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"script": "composePrompt"
|
|
66
|
-
}
|
|
52
|
+
{ "script": "fixFlow" },
|
|
53
|
+
{ "script": "loadTaskState" },
|
|
54
|
+
{ "script": "loadConventions" },
|
|
55
|
+
{ "script": "loadCoverageRules" },
|
|
56
|
+
{ "script": "composePrompt" }
|
|
67
57
|
],
|
|
68
58
|
"postflight": [
|
|
69
59
|
{ "script": "parseAgentResult" },
|
|
@@ -71,12 +61,11 @@
|
|
|
71
61
|
{ "script": "verify" },
|
|
72
62
|
{ "script": "checkCoverageWithRetry" },
|
|
73
63
|
{ "script": "commitAndPush" },
|
|
74
|
-
{ "script": "
|
|
75
|
-
{ "script": "
|
|
76
|
-
{ "script": "postIssueComment", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
64
|
+
{ "script": "ensurePr" },
|
|
65
|
+
{ "script": "postIssueComment" },
|
|
77
66
|
{ "script": "writeRunSummary" },
|
|
78
|
-
{ "script": "saveTaskState"
|
|
79
|
-
{ "script": "advanceFlow"
|
|
67
|
+
{ "script": "saveTaskState" },
|
|
68
|
+
{ "script": "advanceFlow" }
|
|
80
69
|
]
|
|
81
70
|
},
|
|
82
71
|
"output": {
|
|
@@ -42,24 +42,12 @@
|
|
|
42
42
|
"description": "kody2: implementing the change"
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
{
|
|
52
|
-
"script": "resolveArtifacts"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"script": "loadConventions"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"script": "loadCoverageRules"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"script": "composePrompt"
|
|
62
|
-
}
|
|
45
|
+
{ "script": "runFlow" },
|
|
46
|
+
{ "script": "loadTaskState" },
|
|
47
|
+
{ "script": "resolveArtifacts" },
|
|
48
|
+
{ "script": "loadConventions" },
|
|
49
|
+
{ "script": "loadCoverageRules" },
|
|
50
|
+
{ "script": "composePrompt" }
|
|
63
51
|
],
|
|
64
52
|
"postflight": [
|
|
65
53
|
{ "script": "parseAgentResult" },
|
|
@@ -67,13 +55,12 @@
|
|
|
67
55
|
{ "script": "verify" },
|
|
68
56
|
{ "script": "checkCoverageWithRetry" },
|
|
69
57
|
{ "script": "commitAndPush" },
|
|
70
|
-
{ "script": "
|
|
71
|
-
{ "script": "
|
|
72
|
-
{ "script": "postIssueComment", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
58
|
+
{ "script": "ensurePr" },
|
|
59
|
+
{ "script": "postIssueComment" },
|
|
73
60
|
{ "script": "writeRunSummary" },
|
|
74
|
-
{ "script": "saveTaskState"
|
|
75
|
-
{ "script": "mirrorStateToPr"
|
|
76
|
-
{ "script": "advanceFlow"
|
|
61
|
+
{ "script": "saveTaskState" },
|
|
62
|
+
{ "script": "mirrorStateToPr" },
|
|
63
|
+
{ "script": "advanceFlow" }
|
|
77
64
|
]
|
|
78
65
|
},
|
|
79
66
|
"input": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.63",
|
|
4
4
|
"description": "kody2 — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/templates/kody2.yml
CHANGED
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
# 2. Pushes from kody2 won't trigger downstream workflows.
|
|
21
21
|
# 3. Any commit that modifies `.github/workflows/*` is REJECTED by
|
|
22
22
|
# GitHub — the default GITHUB_TOKEN can't touch workflow files.
|
|
23
|
-
# (The `workflow-edit` risk gate is effectively unreachable without
|
|
24
|
-
# a token carrying the `workflow` scope.)
|
|
25
23
|
# Set KODY_TOKEN in repo Settings → Secrets → Actions.
|
|
26
24
|
|
|
27
25
|
name: kody2
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "approve",
|
|
3
|
-
"role": "utility",
|
|
4
|
-
"describe": "Acknowledge pending risk gate(s) on an issue or PR: applies kody-approve:* labels on both the issue and its PR (if discoverable via task state), then re-triggers the paused flow. Invoked by `@kody2 approve`.",
|
|
5
|
-
"inputs": [
|
|
6
|
-
{
|
|
7
|
-
"name": "issue",
|
|
8
|
-
"flag": "--issue",
|
|
9
|
-
"type": "int",
|
|
10
|
-
"required": false,
|
|
11
|
-
"describe": "Issue number (when approving on the originating issue)."
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"name": "pr",
|
|
15
|
-
"flag": "--pr",
|
|
16
|
-
"type": "int",
|
|
17
|
-
"required": false,
|
|
18
|
-
"describe": "PR number (when approving on the PR side)."
|
|
19
|
-
}
|
|
20
|
-
],
|
|
21
|
-
"claudeCode": {
|
|
22
|
-
"model": "inherit",
|
|
23
|
-
"permissionMode": "default",
|
|
24
|
-
"maxTurns": 0,
|
|
25
|
-
"maxThinkingTokens": null,
|
|
26
|
-
"systemPromptAppend": null,
|
|
27
|
-
"tools": [],
|
|
28
|
-
"hooks": [],
|
|
29
|
-
"skills": [],
|
|
30
|
-
"commands": [],
|
|
31
|
-
"subagents": [],
|
|
32
|
-
"plugins": [],
|
|
33
|
-
"mcpServers": []
|
|
34
|
-
},
|
|
35
|
-
"cliTools": [],
|
|
36
|
-
"scripts": {
|
|
37
|
-
"preflight": [
|
|
38
|
-
{ "script": "skipAgent" }
|
|
39
|
-
],
|
|
40
|
-
"postflight": [
|
|
41
|
-
{ "script": "applyApprovals" }
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
|
-
"output": {
|
|
45
|
-
"actionTypes": []
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(approve is an agent-less utility; this file is a placeholder kept to satisfy the profile layout convention.)
|