@kody-ade/kody-engine 0.2.60 → 0.2.62
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.62",
|
|
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
|
|
|
@@ -543,7 +543,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
// src/kody2-cli.ts
|
|
546
|
-
import { execFileSync as
|
|
546
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
547
547
|
import * as fs21 from "fs";
|
|
548
548
|
import * as path18 from "path";
|
|
549
549
|
|
|
@@ -581,9 +581,6 @@ function autoDispatch(opts) {
|
|
|
581
581
|
if (!targetNum) return null;
|
|
582
582
|
const afterTag = extractAfterTag(body);
|
|
583
583
|
if (isPr) {
|
|
584
|
-
if (/\bapprove\b/.test(afterTag)) {
|
|
585
|
-
return { executable: "approve", cliArgs: { pr: targetNum }, target: targetNum };
|
|
586
|
-
}
|
|
587
584
|
if (/\bfix-ci\b/.test(afterTag)) {
|
|
588
585
|
return { executable: "fix-ci", cliArgs: { pr: targetNum }, target: targetNum };
|
|
589
586
|
}
|
|
@@ -1241,265 +1238,6 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
1241
1238
|
}
|
|
1242
1239
|
};
|
|
1243
1240
|
|
|
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 ALL_APPROVE_LABELS = [
|
|
1385
|
-
{ label: "kody-approve:all", description: "kody2: approve all soft risk gates" },
|
|
1386
|
-
{ label: "kody-approve:secrets", description: "kody2: approve the secrets risk gate and resume the flow" },
|
|
1387
|
-
{ label: "kody-approve:workflow-edit", description: "kody2: approve the workflow-edit risk gate and resume the flow" },
|
|
1388
|
-
{ label: "kody-approve:large-diff", description: "kody2: approve the large-diff risk gate and resume the flow" },
|
|
1389
|
-
{ label: "kody-approve:dep-change", description: "kody2: approve the dep-change risk gate and resume the flow" },
|
|
1390
|
-
{ label: "kody-approve:test-deletion", description: "kody2: approve the test-deletion risk gate and resume the flow" }
|
|
1391
|
-
];
|
|
1392
|
-
var applyApprovals = async (ctx) => {
|
|
1393
|
-
const issueArg = typeof ctx.args.issue === "number" ? ctx.args.issue : null;
|
|
1394
|
-
const prArg = typeof ctx.args.pr === "number" ? ctx.args.pr : null;
|
|
1395
|
-
const currentTarget = issueArg ? { type: "issue", number: issueArg } : prArg ? { type: "pr", number: prArg } : null;
|
|
1396
|
-
if (!currentTarget) {
|
|
1397
|
-
ctx.output.exitCode = 64;
|
|
1398
|
-
ctx.output.reason = "approve: must be invoked with --issue or --pr";
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
let state = null;
|
|
1402
|
-
try {
|
|
1403
|
-
state = readTaskState(currentTarget.type, currentTarget.number, ctx.cwd);
|
|
1404
|
-
} catch {
|
|
1405
|
-
state = null;
|
|
1406
|
-
}
|
|
1407
|
-
const issueNumber = currentTarget.type === "issue" ? currentTarget.number : state?.flow?.issueNumber ?? null;
|
|
1408
|
-
const prNumber = currentTarget.type === "pr" ? currentTarget.number : parsePrNumber(state?.core?.prUrl);
|
|
1409
|
-
const targets = uniquePairs(
|
|
1410
|
-
[
|
|
1411
|
-
{ type: "issue", number: issueNumber },
|
|
1412
|
-
{ type: "pr", number: prNumber }
|
|
1413
|
-
].filter((t) => typeof t.number === "number" && t.number > 0)
|
|
1414
|
-
);
|
|
1415
|
-
for (const spec of ALL_APPROVE_LABELS) {
|
|
1416
|
-
ensureLabel(spec, ctx.cwd);
|
|
1417
|
-
}
|
|
1418
|
-
for (const t of targets) {
|
|
1419
|
-
for (const spec of ALL_APPROVE_LABELS) {
|
|
1420
|
-
addLabel(t.number, spec.label, ctx.cwd);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
const confirmation = formatConfirmation(currentTarget, targets, state);
|
|
1424
|
-
try {
|
|
1425
|
-
if (currentTarget.type === "issue") postIssueComment(currentTarget.number, confirmation, ctx.cwd);
|
|
1426
|
-
else postPrReviewComment(currentTarget.number, confirmation, ctx.cwd);
|
|
1427
|
-
} catch {
|
|
1428
|
-
}
|
|
1429
|
-
const flowName = state?.flow?.name;
|
|
1430
|
-
if (issueNumber && typeof flowName === "string" && flowName.length > 0) {
|
|
1431
|
-
try {
|
|
1432
|
-
execFileSync5("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${flowName}`], {
|
|
1433
|
-
timeout: API_TIMEOUT_MS4,
|
|
1434
|
-
cwd: ctx.cwd,
|
|
1435
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1436
|
-
});
|
|
1437
|
-
} catch (err) {
|
|
1438
|
-
process.stderr.write(
|
|
1439
|
-
`[kody2 approve] failed to re-trigger flow on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1440
|
-
`
|
|
1441
|
-
);
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
ctx.output.exitCode = 0;
|
|
1445
|
-
};
|
|
1446
|
-
function parsePrNumber(url) {
|
|
1447
|
-
if (!url) return null;
|
|
1448
|
-
const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
1449
|
-
if (!m) return null;
|
|
1450
|
-
const n = parseInt(m[1], 10);
|
|
1451
|
-
return Number.isFinite(n) ? n : null;
|
|
1452
|
-
}
|
|
1453
|
-
function uniquePairs(pairs) {
|
|
1454
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1455
|
-
const out = [];
|
|
1456
|
-
for (const p of pairs) {
|
|
1457
|
-
const key = `${p.type}:${p.number}`;
|
|
1458
|
-
if (seen.has(key)) continue;
|
|
1459
|
-
seen.add(key);
|
|
1460
|
-
out.push(p);
|
|
1461
|
-
}
|
|
1462
|
-
return out;
|
|
1463
|
-
}
|
|
1464
|
-
function ensureLabel(spec, cwd) {
|
|
1465
|
-
try {
|
|
1466
|
-
gh2(
|
|
1467
|
-
["label", "create", spec.label, "--force", "--color", "0e8a16", "--description", spec.description],
|
|
1468
|
-
{ cwd }
|
|
1469
|
-
);
|
|
1470
|
-
} catch {
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
function addLabel(number, label, cwd) {
|
|
1474
|
-
try {
|
|
1475
|
-
gh2(["issue", "edit", String(number), "--add-label", label], { cwd });
|
|
1476
|
-
} catch {
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
function formatConfirmation(current, targets, state) {
|
|
1480
|
-
const other = targets.find((t) => t.type !== current.type);
|
|
1481
|
-
const lines = [];
|
|
1482
|
-
lines.push("\u2705 **kody2 risk gates approved.**");
|
|
1483
|
-
lines.push("");
|
|
1484
|
-
lines.push(
|
|
1485
|
-
`Applied \`kody-approve:*\` labels on ${targets.map((t) => `${t.type} #${t.number}`).join(" and ")}.`
|
|
1486
|
-
);
|
|
1487
|
-
if (other && current.type === "pr") {
|
|
1488
|
-
lines.push(`Mirrored to the originating issue (#${other.number}) so the orchestrator also sees the approval.`);
|
|
1489
|
-
} else if (other && current.type === "issue") {
|
|
1490
|
-
lines.push(`Mirrored to PR #${other.number} so a pending \`fix\` primitive also passes the gate.`);
|
|
1491
|
-
}
|
|
1492
|
-
const flowName = state?.flow?.name;
|
|
1493
|
-
if (flowName) {
|
|
1494
|
-
lines.push("");
|
|
1495
|
-
lines.push(`Re-triggering the \`${flowName}\` flow now \u2014 it will resume from the existing branch/PR checkpoint.`);
|
|
1496
|
-
} else {
|
|
1497
|
-
lines.push("");
|
|
1498
|
-
lines.push("No active flow found in task state. Post `@kody2 <command>` to resume manually.");
|
|
1499
|
-
}
|
|
1500
|
-
return lines.join("\n");
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
1241
|
// src/scripts/buildSyntheticPlugin.ts
|
|
1504
1242
|
import * as fs8 from "fs";
|
|
1505
1243
|
import * as os2 from "os";
|
|
@@ -1594,7 +1332,7 @@ function copyDir(src, dst) {
|
|
|
1594
1332
|
}
|
|
1595
1333
|
|
|
1596
1334
|
// src/coverage.ts
|
|
1597
|
-
import { execFileSync as
|
|
1335
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
1598
1336
|
function patternToRegex(pattern) {
|
|
1599
1337
|
let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
1600
1338
|
s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
|
|
@@ -1612,7 +1350,7 @@ function renderSiblingPath(file, requireSibling) {
|
|
|
1612
1350
|
}
|
|
1613
1351
|
function safeGit(args, cwd) {
|
|
1614
1352
|
try {
|
|
1615
|
-
return
|
|
1353
|
+
return execFileSync4("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
|
|
1616
1354
|
} catch {
|
|
1617
1355
|
return "";
|
|
1618
1356
|
}
|
|
@@ -1818,10 +1556,10 @@ function defaultLabelMap() {
|
|
|
1818
1556
|
}
|
|
1819
1557
|
|
|
1820
1558
|
// src/scripts/commitAndPush.ts
|
|
1821
|
-
import { execFileSync as
|
|
1559
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
1822
1560
|
|
|
1823
1561
|
// src/commit.ts
|
|
1824
|
-
import { execFileSync as
|
|
1562
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
1825
1563
|
import * as fs10 from "fs";
|
|
1826
1564
|
import * as path9 from "path";
|
|
1827
1565
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
@@ -1851,7 +1589,7 @@ var CONVENTIONAL_PREFIXES = [
|
|
|
1851
1589
|
];
|
|
1852
1590
|
function git(args, cwd) {
|
|
1853
1591
|
try {
|
|
1854
|
-
return
|
|
1592
|
+
return execFileSync5("git", args, {
|
|
1855
1593
|
encoding: "utf-8",
|
|
1856
1594
|
timeout: 12e4,
|
|
1857
1595
|
cwd,
|
|
@@ -1909,7 +1647,7 @@ function isForbiddenPath(p) {
|
|
|
1909
1647
|
return false;
|
|
1910
1648
|
}
|
|
1911
1649
|
function listChangedFiles(cwd) {
|
|
1912
|
-
const raw =
|
|
1650
|
+
const raw = execFileSync5("git", ["status", "--porcelain=v1", "-z"], {
|
|
1913
1651
|
encoding: "utf-8",
|
|
1914
1652
|
cwd,
|
|
1915
1653
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -1921,7 +1659,7 @@ function listChangedFiles(cwd) {
|
|
|
1921
1659
|
}
|
|
1922
1660
|
function listFilesInCommit(ref = "HEAD", cwd) {
|
|
1923
1661
|
try {
|
|
1924
|
-
const raw =
|
|
1662
|
+
const raw = execFileSync5("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
|
|
1925
1663
|
encoding: "utf-8",
|
|
1926
1664
|
cwd,
|
|
1927
1665
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -2001,7 +1739,7 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
2001
1739
|
const kind = profile.name;
|
|
2002
1740
|
if (kind === "resolve") {
|
|
2003
1741
|
try {
|
|
2004
|
-
|
|
1742
|
+
execFileSync6("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
|
|
2005
1743
|
} catch {
|
|
2006
1744
|
}
|
|
2007
1745
|
} else {
|
|
@@ -2623,8 +2361,8 @@ var discoverQaContext = async (ctx) => {
|
|
|
2623
2361
|
};
|
|
2624
2362
|
|
|
2625
2363
|
// src/scripts/dispatch.ts
|
|
2626
|
-
import { execFileSync as
|
|
2627
|
-
var
|
|
2364
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
2365
|
+
var API_TIMEOUT_MS3 = 3e4;
|
|
2628
2366
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
2629
2367
|
const next = args?.next;
|
|
2630
2368
|
if (!next) {
|
|
@@ -2646,8 +2384,8 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
2646
2384
|
const sub = usePr ? "pr" : "issue";
|
|
2647
2385
|
const body = `@kody2 ${next}`;
|
|
2648
2386
|
try {
|
|
2649
|
-
|
|
2650
|
-
timeout:
|
|
2387
|
+
execFileSync7("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
2388
|
+
timeout: API_TIMEOUT_MS3,
|
|
2651
2389
|
cwd: ctx.cwd,
|
|
2652
2390
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2653
2391
|
});
|
|
@@ -2665,6 +2403,141 @@ function parsePr(url) {
|
|
|
2665
2403
|
return Number.isFinite(n) ? n : null;
|
|
2666
2404
|
}
|
|
2667
2405
|
|
|
2406
|
+
// src/issue.ts
|
|
2407
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
2408
|
+
var API_TIMEOUT_MS4 = 3e4;
|
|
2409
|
+
function ghToken2() {
|
|
2410
|
+
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
2411
|
+
}
|
|
2412
|
+
function gh2(args, options) {
|
|
2413
|
+
const token = ghToken2();
|
|
2414
|
+
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
2415
|
+
return execFileSync8("gh", args, {
|
|
2416
|
+
encoding: "utf-8",
|
|
2417
|
+
timeout: API_TIMEOUT_MS4,
|
|
2418
|
+
cwd: options?.cwd,
|
|
2419
|
+
env,
|
|
2420
|
+
input: options?.input,
|
|
2421
|
+
stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
|
|
2422
|
+
}).trim();
|
|
2423
|
+
}
|
|
2424
|
+
function getIssue(issueNumber, cwd) {
|
|
2425
|
+
const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
|
|
2426
|
+
const parsed = JSON.parse(output);
|
|
2427
|
+
if (typeof parsed?.title !== "string") {
|
|
2428
|
+
throw new Error(`Issue #${issueNumber}: unexpected response shape`);
|
|
2429
|
+
}
|
|
2430
|
+
return {
|
|
2431
|
+
number: parsed.number ?? issueNumber,
|
|
2432
|
+
title: parsed.title,
|
|
2433
|
+
body: parsed.body ?? "",
|
|
2434
|
+
comments: (parsed.comments ?? []).map((c) => ({
|
|
2435
|
+
body: c.body ?? "",
|
|
2436
|
+
author: c.author?.login ?? "unknown",
|
|
2437
|
+
createdAt: c.createdAt ?? ""
|
|
2438
|
+
})),
|
|
2439
|
+
labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
function stripKody2Mentions(body) {
|
|
2443
|
+
return body.replace(/(@)(kody2)/gi, "$1\u200B$2");
|
|
2444
|
+
}
|
|
2445
|
+
function postIssueComment(issueNumber, body, cwd) {
|
|
2446
|
+
try {
|
|
2447
|
+
gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
|
|
2448
|
+
} catch (err) {
|
|
2449
|
+
process.stderr.write(
|
|
2450
|
+
`[kody2] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2451
|
+
`
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
function truncate2(s, maxBytes) {
|
|
2456
|
+
if (s.length <= maxBytes) return s;
|
|
2457
|
+
return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
|
|
2458
|
+
}
|
|
2459
|
+
function getPr(prNumber, cwd) {
|
|
2460
|
+
const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
|
|
2461
|
+
cwd
|
|
2462
|
+
});
|
|
2463
|
+
const parsed = JSON.parse(output);
|
|
2464
|
+
if (typeof parsed?.title !== "string") {
|
|
2465
|
+
throw new Error(`PR #${prNumber}: unexpected response shape`);
|
|
2466
|
+
}
|
|
2467
|
+
return {
|
|
2468
|
+
number: parsed.number ?? prNumber,
|
|
2469
|
+
title: parsed.title,
|
|
2470
|
+
body: parsed.body ?? "",
|
|
2471
|
+
headRefName: String(parsed.headRefName ?? ""),
|
|
2472
|
+
baseRefName: String(parsed.baseRefName ?? ""),
|
|
2473
|
+
state: String(parsed.state ?? "")
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
function getPrDiff(prNumber, cwd) {
|
|
2477
|
+
try {
|
|
2478
|
+
return gh2(["pr", "diff", String(prNumber)], { cwd });
|
|
2479
|
+
} catch (err) {
|
|
2480
|
+
process.stderr.write(
|
|
2481
|
+
`[kody2] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2482
|
+
`
|
|
2483
|
+
);
|
|
2484
|
+
return "";
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
function getPrReviews(prNumber, cwd) {
|
|
2488
|
+
try {
|
|
2489
|
+
const output = gh2(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
|
|
2490
|
+
const parsed = JSON.parse(output);
|
|
2491
|
+
if (!Array.isArray(parsed?.reviews)) return [];
|
|
2492
|
+
return parsed.reviews.map(
|
|
2493
|
+
(r) => ({
|
|
2494
|
+
body: r.body ?? "",
|
|
2495
|
+
state: r.state ?? "",
|
|
2496
|
+
author: r.author?.login ?? "unknown",
|
|
2497
|
+
submittedAt: r.submittedAt ?? ""
|
|
2498
|
+
})
|
|
2499
|
+
);
|
|
2500
|
+
} catch {
|
|
2501
|
+
return [];
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
function getPrComments(prNumber, cwd) {
|
|
2505
|
+
try {
|
|
2506
|
+
const output = gh2(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
|
|
2507
|
+
const parsed = JSON.parse(output);
|
|
2508
|
+
if (!Array.isArray(parsed?.comments)) return [];
|
|
2509
|
+
return parsed.comments.map((c) => ({
|
|
2510
|
+
body: c.body ?? "",
|
|
2511
|
+
author: c.author?.login ?? "unknown",
|
|
2512
|
+
createdAt: c.createdAt ?? ""
|
|
2513
|
+
})).filter((c) => c.body.trim().length > 0);
|
|
2514
|
+
} catch {
|
|
2515
|
+
return [];
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
var VERDICT_HEADING = /(^|\n)\s*#{1,6}\s*Verdict\s*:/i;
|
|
2519
|
+
function isReviewShaped(body) {
|
|
2520
|
+
return VERDICT_HEADING.test(body);
|
|
2521
|
+
}
|
|
2522
|
+
function getPrLatestReviewBody(prNumber, cwd) {
|
|
2523
|
+
const reviews = getPrReviews(prNumber, cwd).filter((r) => r.body.trim().length > 0).map((r) => ({ body: r.body, at: r.submittedAt }));
|
|
2524
|
+
const comments = getPrComments(prNumber, cwd).filter((c) => isReviewShaped(c.body)).map((c) => ({ body: c.body, at: c.createdAt }));
|
|
2525
|
+
const all = [...reviews, ...comments].sort((a, b) => (b.at || "").localeCompare(a.at || ""));
|
|
2526
|
+
if (all.length > 0) return all[0].body;
|
|
2527
|
+
const pr = getPr(prNumber, cwd);
|
|
2528
|
+
return pr.body;
|
|
2529
|
+
}
|
|
2530
|
+
function postPrReviewComment(prNumber, body, cwd) {
|
|
2531
|
+
try {
|
|
2532
|
+
gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
|
|
2533
|
+
} catch (err) {
|
|
2534
|
+
process.stderr.write(
|
|
2535
|
+
`[kody2] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2536
|
+
`
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2668
2541
|
// src/pr.ts
|
|
2669
2542
|
var TITLE_MAX = 72;
|
|
2670
2543
|
function stripTitlePrefixes(raw) {
|
|
@@ -2830,7 +2703,7 @@ function computeFailureReason(ctx) {
|
|
|
2830
2703
|
}
|
|
2831
2704
|
|
|
2832
2705
|
// src/scripts/finishFlow.ts
|
|
2833
|
-
import { execFileSync as
|
|
2706
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
2834
2707
|
|
|
2835
2708
|
// src/registry.ts
|
|
2836
2709
|
import * as fs14 from "fs";
|
|
@@ -2949,7 +2822,7 @@ function getIssueLabels(issueNumber, cwd) {
|
|
|
2949
2822
|
return [];
|
|
2950
2823
|
}
|
|
2951
2824
|
}
|
|
2952
|
-
function
|
|
2825
|
+
function addLabel(issueNumber, label, cwd) {
|
|
2953
2826
|
gh2(["issue", "edit", String(issueNumber), "--add-label", label], { cwd });
|
|
2954
2827
|
}
|
|
2955
2828
|
function removeLabel(issueNumber, label, cwd) {
|
|
@@ -2981,12 +2854,12 @@ function setKodyLabel(issueNumber, spec, cwd) {
|
|
|
2981
2854
|
}
|
|
2982
2855
|
}
|
|
2983
2856
|
try {
|
|
2984
|
-
|
|
2857
|
+
addLabel(issueNumber, target, cwd);
|
|
2985
2858
|
} catch (err) {
|
|
2986
2859
|
if (looksLikeMissingLabel(err)) {
|
|
2987
2860
|
try {
|
|
2988
2861
|
createLabelInRepo(spec, cwd);
|
|
2989
|
-
|
|
2862
|
+
addLabel(issueNumber, target, cwd);
|
|
2990
2863
|
return;
|
|
2991
2864
|
} catch (retryErr) {
|
|
2992
2865
|
process.stderr.write(
|
|
@@ -3018,7 +2891,7 @@ function errMsg(err) {
|
|
|
3018
2891
|
}
|
|
3019
2892
|
|
|
3020
2893
|
// src/scripts/finishFlow.ts
|
|
3021
|
-
var
|
|
2894
|
+
var API_TIMEOUT_MS5 = 3e4;
|
|
3022
2895
|
var STATUS_ICON = {
|
|
3023
2896
|
"review-passed": "\u2705",
|
|
3024
2897
|
"fix-applied": "\u2705",
|
|
@@ -3050,8 +2923,8 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
3050
2923
|
**PR:** ${state.core.prUrl}` : "";
|
|
3051
2924
|
const body = `${icon} kody2 flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
3052
2925
|
try {
|
|
3053
|
-
|
|
3054
|
-
timeout:
|
|
2926
|
+
execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
2927
|
+
timeout: API_TIMEOUT_MS5,
|
|
3055
2928
|
cwd: ctx.cwd,
|
|
3056
2929
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3057
2930
|
});
|
|
@@ -3064,7 +2937,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
3064
2937
|
};
|
|
3065
2938
|
|
|
3066
2939
|
// src/branch.ts
|
|
3067
|
-
import { execFileSync as
|
|
2940
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3068
2941
|
var UncommittedChangesError = class extends Error {
|
|
3069
2942
|
constructor(branch) {
|
|
3070
2943
|
super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
|
|
@@ -3074,7 +2947,7 @@ var UncommittedChangesError = class extends Error {
|
|
|
3074
2947
|
branch;
|
|
3075
2948
|
};
|
|
3076
2949
|
function git2(args, cwd) {
|
|
3077
|
-
return
|
|
2950
|
+
return execFileSync10("git", args, {
|
|
3078
2951
|
encoding: "utf-8",
|
|
3079
2952
|
timeout: 3e4,
|
|
3080
2953
|
cwd,
|
|
@@ -3099,7 +2972,7 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
3099
2972
|
SKIP_HOOKS: "1",
|
|
3100
2973
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
3101
2974
|
};
|
|
3102
|
-
|
|
2975
|
+
execFileSync10("gh", ["pr", "checkout", String(prNumber)], {
|
|
3103
2976
|
cwd,
|
|
3104
2977
|
env,
|
|
3105
2978
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3166,7 +3039,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
3166
3039
|
}
|
|
3167
3040
|
|
|
3168
3041
|
// src/gha.ts
|
|
3169
|
-
import { execFileSync as
|
|
3042
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
3170
3043
|
import * as fs15 from "fs";
|
|
3171
3044
|
function getRunUrl() {
|
|
3172
3045
|
const server = process.env.GITHUB_SERVER_URL;
|
|
@@ -3209,7 +3082,7 @@ function reactToTriggerComment(cwd) {
|
|
|
3209
3082
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
3210
3083
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
3211
3084
|
try {
|
|
3212
|
-
|
|
3085
|
+
execFileSync11("gh", args, opts);
|
|
3213
3086
|
return;
|
|
3214
3087
|
} catch (err) {
|
|
3215
3088
|
lastErr = err;
|
|
@@ -3222,13 +3095,13 @@ function reactToTriggerComment(cwd) {
|
|
|
3222
3095
|
}
|
|
3223
3096
|
function sleepMs(ms) {
|
|
3224
3097
|
try {
|
|
3225
|
-
|
|
3098
|
+
execFileSync11("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
3226
3099
|
} catch {
|
|
3227
3100
|
}
|
|
3228
3101
|
}
|
|
3229
3102
|
|
|
3230
3103
|
// src/workflow.ts
|
|
3231
|
-
import { execFileSync as
|
|
3104
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
3232
3105
|
var GH_TIMEOUT_MS = 3e4;
|
|
3233
3106
|
function ghToken3() {
|
|
3234
3107
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -3236,7 +3109,7 @@ function ghToken3() {
|
|
|
3236
3109
|
function gh3(args, cwd) {
|
|
3237
3110
|
const token = ghToken3();
|
|
3238
3111
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
3239
|
-
return
|
|
3112
|
+
return execFileSync12("gh", args, {
|
|
3240
3113
|
encoding: "utf-8",
|
|
3241
3114
|
timeout: GH_TIMEOUT_MS,
|
|
3242
3115
|
cwd,
|
|
@@ -3420,7 +3293,7 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
3420
3293
|
}
|
|
3421
3294
|
|
|
3422
3295
|
// src/scripts/initFlow.ts
|
|
3423
|
-
import { execFileSync as
|
|
3296
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
3424
3297
|
import * as fs17 from "fs";
|
|
3425
3298
|
import * as path15 from "path";
|
|
3426
3299
|
|
|
@@ -3461,7 +3334,7 @@ function qualityCommandsFor(pm) {
|
|
|
3461
3334
|
function detectOwnerRepo(cwd) {
|
|
3462
3335
|
let url;
|
|
3463
3336
|
try {
|
|
3464
|
-
url =
|
|
3337
|
+
url = execFileSync13("git", ["remote", "get-url", "origin"], {
|
|
3465
3338
|
cwd,
|
|
3466
3339
|
encoding: "utf-8",
|
|
3467
3340
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3545,7 +3418,7 @@ jobs:
|
|
|
3545
3418
|
`;
|
|
3546
3419
|
function defaultBranchFromGit(cwd) {
|
|
3547
3420
|
try {
|
|
3548
|
-
const ref =
|
|
3421
|
+
const ref = execFileSync13("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
3549
3422
|
cwd,
|
|
3550
3423
|
encoding: "utf-8",
|
|
3551
3424
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3553,7 +3426,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
3553
3426
|
return ref.replace("refs/remotes/origin/", "");
|
|
3554
3427
|
} catch {
|
|
3555
3428
|
try {
|
|
3556
|
-
return
|
|
3429
|
+
return execFileSync13("git", ["branch", "--show-current"], {
|
|
3557
3430
|
cwd,
|
|
3558
3431
|
encoding: "utf-8",
|
|
3559
3432
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3743,7 +3616,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
3743
3616
|
if (!issueNumber || issueTarget !== "issue") return;
|
|
3744
3617
|
const prUrl = ctx.output.prUrl ?? ctx.data.prResult?.url;
|
|
3745
3618
|
if (!prUrl) return;
|
|
3746
|
-
const prNumber =
|
|
3619
|
+
const prNumber = parsePrNumber(prUrl);
|
|
3747
3620
|
if (!prNumber) return;
|
|
3748
3621
|
let state;
|
|
3749
3622
|
try {
|
|
@@ -3761,7 +3634,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
3761
3634
|
);
|
|
3762
3635
|
}
|
|
3763
3636
|
};
|
|
3764
|
-
function
|
|
3637
|
+
function parsePrNumber(prUrl) {
|
|
3765
3638
|
const m = prUrl.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
3766
3639
|
if (!m) return null;
|
|
3767
3640
|
const n = parseInt(m[1], 10);
|
|
@@ -3846,8 +3719,8 @@ var persistFlowState = async (ctx) => {
|
|
|
3846
3719
|
};
|
|
3847
3720
|
|
|
3848
3721
|
// src/scripts/postClassification.ts
|
|
3849
|
-
import { execFileSync as
|
|
3850
|
-
var
|
|
3722
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
3723
|
+
var API_TIMEOUT_MS6 = 3e4;
|
|
3851
3724
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
3852
3725
|
var postClassification = async (ctx) => {
|
|
3853
3726
|
const issueNumber = ctx.args.issue;
|
|
@@ -3876,9 +3749,9 @@ var postClassification = async (ctx) => {
|
|
|
3876
3749
|
ctx.cwd
|
|
3877
3750
|
);
|
|
3878
3751
|
try {
|
|
3879
|
-
|
|
3752
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${classification}`], {
|
|
3880
3753
|
cwd: ctx.cwd,
|
|
3881
|
-
timeout:
|
|
3754
|
+
timeout: API_TIMEOUT_MS6,
|
|
3882
3755
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3883
3756
|
});
|
|
3884
3757
|
} catch (err) {
|
|
@@ -3910,9 +3783,9 @@ function parseClassification(prSummary) {
|
|
|
3910
3783
|
}
|
|
3911
3784
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
3912
3785
|
try {
|
|
3913
|
-
|
|
3786
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
3914
3787
|
cwd,
|
|
3915
|
-
timeout:
|
|
3788
|
+
timeout: API_TIMEOUT_MS6,
|
|
3916
3789
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3917
3790
|
});
|
|
3918
3791
|
} catch {
|
|
@@ -4094,7 +3967,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
4094
3967
|
};
|
|
4095
3968
|
|
|
4096
3969
|
// src/scripts/releaseFlow.ts
|
|
4097
|
-
import { execFileSync as
|
|
3970
|
+
import { execFileSync as execFileSync15, spawnSync } from "child_process";
|
|
4098
3971
|
import * as fs18 from "fs";
|
|
4099
3972
|
import * as path16 from "path";
|
|
4100
3973
|
function bumpVersion(current, bump) {
|
|
@@ -4124,7 +3997,7 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
4124
3997
|
const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
|
|
4125
3998
|
let log = "";
|
|
4126
3999
|
try {
|
|
4127
|
-
log =
|
|
4000
|
+
log = execFileSync15("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
|
|
4128
4001
|
cwd,
|
|
4129
4002
|
encoding: "utf-8",
|
|
4130
4003
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4181,7 +4054,7 @@ ${entry}${prior.slice(idx + 1)}`);
|
|
|
4181
4054
|
}
|
|
4182
4055
|
}
|
|
4183
4056
|
function git3(args, cwd, timeout = 6e4) {
|
|
4184
|
-
return
|
|
4057
|
+
return execFileSync15("git", args, {
|
|
4185
4058
|
encoding: "utf-8",
|
|
4186
4059
|
timeout,
|
|
4187
4060
|
cwd,
|
|
@@ -4484,7 +4357,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
4484
4357
|
};
|
|
4485
4358
|
|
|
4486
4359
|
// src/scripts/resolveFlow.ts
|
|
4487
|
-
import { execFileSync as
|
|
4360
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
4488
4361
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
4489
4362
|
var resolveFlow = async (ctx) => {
|
|
4490
4363
|
const prNumber = ctx.args.pr;
|
|
@@ -4536,7 +4409,7 @@ var resolveFlow = async (ctx) => {
|
|
|
4536
4409
|
};
|
|
4537
4410
|
function getConflictedFiles(cwd) {
|
|
4538
4411
|
try {
|
|
4539
|
-
const out =
|
|
4412
|
+
const out = execFileSync16("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
4540
4413
|
encoding: "utf-8",
|
|
4541
4414
|
cwd,
|
|
4542
4415
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -4551,7 +4424,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
4551
4424
|
let total = 0;
|
|
4552
4425
|
for (const f of files) {
|
|
4553
4426
|
try {
|
|
4554
|
-
const content =
|
|
4427
|
+
const content = execFileSync16("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
4555
4428
|
const snippet = `### ${f}
|
|
4556
4429
|
|
|
4557
4430
|
\`\`\`
|
|
@@ -4619,299 +4492,6 @@ function tryPostPr4(prNumber, body, cwd) {
|
|
|
4619
4492
|
}
|
|
4620
4493
|
}
|
|
4621
4494
|
|
|
4622
|
-
// src/scripts/riskGate.ts
|
|
4623
|
-
import { execFileSync as execFileSync18 } from "child_process";
|
|
4624
|
-
var ALL_GATES = ["secrets", "workflow-edit", "large-diff", "dep-change", "test-deletion"];
|
|
4625
|
-
var HARD_GATES = /* @__PURE__ */ new Set(["secrets"]);
|
|
4626
|
-
var APPROVE_ALL = "kody-approve:all";
|
|
4627
|
-
var GATED_LABEL = "kody:gated";
|
|
4628
|
-
var DEFAULT_MAX_FILES = 20;
|
|
4629
|
-
var DEFAULT_MAX_DELETIONS = 500;
|
|
4630
|
-
var riskGate = async (ctx, profile, _agent, args) => {
|
|
4631
|
-
const changedFiles = collectBranchChangedFiles(ctx);
|
|
4632
|
-
const gatesToRun = parseGates(args?.gates);
|
|
4633
|
-
const violations = evaluateGates(ctx, profile.name, changedFiles, gatesToRun, args);
|
|
4634
|
-
if (violations.length === 0) {
|
|
4635
|
-
ctx.data.riskGate = { violations: [], pending: [], decision: "allow" };
|
|
4636
|
-
return;
|
|
4637
|
-
}
|
|
4638
|
-
const targetType = ctx.data.commentTargetType;
|
|
4639
|
-
const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
|
|
4640
|
-
const labels = collectApprovalLabels(ctx, targetType, targetNumber);
|
|
4641
|
-
const approveAll = labels.includes(APPROVE_ALL);
|
|
4642
|
-
const pending = violations.filter((v) => !isApproved(v, labels, approveAll));
|
|
4643
|
-
ctx.data.riskGate = {
|
|
4644
|
-
violations,
|
|
4645
|
-
pending,
|
|
4646
|
-
decision: pending.length === 0 ? "allow" : "halt"
|
|
4647
|
-
};
|
|
4648
|
-
if (pending.length === 0 || !targetType || targetNumber <= 0) return;
|
|
4649
|
-
for (const v of pending) {
|
|
4650
|
-
ensureApproveLabel(v.name, ctx.cwd);
|
|
4651
|
-
}
|
|
4652
|
-
try {
|
|
4653
|
-
setKodyLabel(
|
|
4654
|
-
targetNumber,
|
|
4655
|
-
{
|
|
4656
|
-
label: GATED_LABEL,
|
|
4657
|
-
color: "fbca04",
|
|
4658
|
-
description: "kody2: awaiting human approval of risk gate(s)"
|
|
4659
|
-
},
|
|
4660
|
-
ctx.cwd
|
|
4661
|
-
);
|
|
4662
|
-
} catch {
|
|
4663
|
-
}
|
|
4664
|
-
const compareUrl = computeCompareUrl(ctx);
|
|
4665
|
-
const body = formatAdvisory(pending, compareUrl);
|
|
4666
|
-
try {
|
|
4667
|
-
if (targetType === "issue") postIssueComment(targetNumber, body, ctx.cwd);
|
|
4668
|
-
else postPrReviewComment(targetNumber, body, ctx.cwd);
|
|
4669
|
-
} catch {
|
|
4670
|
-
}
|
|
4671
|
-
if (!ctx.output.reason) {
|
|
4672
|
-
ctx.output.reason = `risk gate halt: ${pending.map((p) => p.name).join(", ")}`;
|
|
4673
|
-
}
|
|
4674
|
-
};
|
|
4675
|
-
function evaluateGates(ctx, profileName, changedFiles, gatesToRun, args) {
|
|
4676
|
-
const violations = [];
|
|
4677
|
-
if (gatesToRun.includes("secrets")) {
|
|
4678
|
-
const hits = changedFiles.filter(isSecretPath);
|
|
4679
|
-
if (hits.length > 0) {
|
|
4680
|
-
violations.push({
|
|
4681
|
-
name: "secrets",
|
|
4682
|
-
severity: "hard",
|
|
4683
|
-
reason: `secret/credential files touched: ${preview(hits)}`
|
|
4684
|
-
});
|
|
4685
|
-
}
|
|
4686
|
-
}
|
|
4687
|
-
if (gatesToRun.includes("workflow-edit")) {
|
|
4688
|
-
const hits = changedFiles.filter((f) => f.startsWith(".github/workflows/"));
|
|
4689
|
-
if (hits.length > 0) {
|
|
4690
|
-
violations.push({
|
|
4691
|
-
name: "workflow-edit",
|
|
4692
|
-
severity: "soft",
|
|
4693
|
-
reason: `CI workflow files modified: ${preview(hits)}`
|
|
4694
|
-
});
|
|
4695
|
-
}
|
|
4696
|
-
}
|
|
4697
|
-
if (gatesToRun.includes("large-diff")) {
|
|
4698
|
-
const maxFiles = toPositiveInt(args?.maxFiles, DEFAULT_MAX_FILES);
|
|
4699
|
-
const maxDeletions = toPositiveInt(args?.maxDeletions, DEFAULT_MAX_DELETIONS);
|
|
4700
|
-
if (changedFiles.length > maxFiles) {
|
|
4701
|
-
violations.push({
|
|
4702
|
-
name: "large-diff",
|
|
4703
|
-
severity: "soft",
|
|
4704
|
-
reason: `${changedFiles.length} files changed (threshold: ${maxFiles})`
|
|
4705
|
-
});
|
|
4706
|
-
} else {
|
|
4707
|
-
const stats = computeDiffStats(ctx);
|
|
4708
|
-
if (stats && stats.deletions > maxDeletions) {
|
|
4709
|
-
violations.push({
|
|
4710
|
-
name: "large-diff",
|
|
4711
|
-
severity: "soft",
|
|
4712
|
-
reason: `${stats.deletions} lines deleted (threshold: ${maxDeletions})`
|
|
4713
|
-
});
|
|
4714
|
-
}
|
|
4715
|
-
}
|
|
4716
|
-
}
|
|
4717
|
-
if (gatesToRun.includes("dep-change") && profileName !== "chore") {
|
|
4718
|
-
const hits = changedFiles.filter(isDepFile);
|
|
4719
|
-
if (hits.length > 0) {
|
|
4720
|
-
violations.push({
|
|
4721
|
-
name: "dep-change",
|
|
4722
|
-
severity: "soft",
|
|
4723
|
-
reason: `dependency/lockfile changes outside a chore flow: ${preview(hits)}`
|
|
4724
|
-
});
|
|
4725
|
-
}
|
|
4726
|
-
}
|
|
4727
|
-
if (gatesToRun.includes("test-deletion")) {
|
|
4728
|
-
const deleted = listDeletedFilesInHeadCommit(ctx.cwd).filter(isTestFile);
|
|
4729
|
-
if (deleted.length > 0) {
|
|
4730
|
-
violations.push({
|
|
4731
|
-
name: "test-deletion",
|
|
4732
|
-
severity: "soft",
|
|
4733
|
-
reason: `test files deleted: ${preview(deleted)}`
|
|
4734
|
-
});
|
|
4735
|
-
}
|
|
4736
|
-
}
|
|
4737
|
-
return violations;
|
|
4738
|
-
}
|
|
4739
|
-
function collectApprovalLabels(ctx, targetType, targetNumber) {
|
|
4740
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4741
|
-
if (targetNumber > 0) {
|
|
4742
|
-
for (const l of getIssueLabels(targetNumber, ctx.cwd)) seen.add(l);
|
|
4743
|
-
}
|
|
4744
|
-
if (targetType === "pr") {
|
|
4745
|
-
const state = ctx.data.taskState;
|
|
4746
|
-
const issueNum = state?.flow?.issueNumber;
|
|
4747
|
-
if (typeof issueNum === "number" && issueNum > 0 && issueNum !== targetNumber) {
|
|
4748
|
-
for (const l of getIssueLabels(issueNum, ctx.cwd)) seen.add(l);
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
return [...seen];
|
|
4752
|
-
}
|
|
4753
|
-
function isApproved(v, labels, approveAll) {
|
|
4754
|
-
if (labels.includes(`kody-approve:${v.name}`)) return true;
|
|
4755
|
-
if (!HARD_GATES.has(v.name) && approveAll) return true;
|
|
4756
|
-
return false;
|
|
4757
|
-
}
|
|
4758
|
-
function parseGates(spec) {
|
|
4759
|
-
if (spec === void 0 || spec === null || spec === "") return ALL_GATES;
|
|
4760
|
-
const list = String(spec).split(",").map((s) => s.trim()).filter(Boolean);
|
|
4761
|
-
const valid = ALL_GATES;
|
|
4762
|
-
const matched = list.filter((n) => valid.includes(n));
|
|
4763
|
-
return matched.length > 0 ? matched : ALL_GATES;
|
|
4764
|
-
}
|
|
4765
|
-
function toPositiveInt(v, fallback) {
|
|
4766
|
-
const n = typeof v === "number" ? v : parseInt(String(v ?? ""), 10);
|
|
4767
|
-
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
4768
|
-
}
|
|
4769
|
-
function preview(list, max = 5) {
|
|
4770
|
-
if (list.length <= max) return list.join(", ");
|
|
4771
|
-
return `${list.slice(0, max).join(", ")} (+${list.length - max} more)`;
|
|
4772
|
-
}
|
|
4773
|
-
var SECRET_PATTERNS = [
|
|
4774
|
-
/(^|\/)\.env(\.|$)/i,
|
|
4775
|
-
/\.pem$/i,
|
|
4776
|
-
/\.key$/i,
|
|
4777
|
-
/(^|\/)(id_rsa|id_ed25519|id_ecdsa)(\.|$)/i,
|
|
4778
|
-
// Match the keyword anywhere inside the filename, as a whole word (so e.g.
|
|
4779
|
-
// `api-secrets.json`, `config/app.credentials.yaml`, `user-passwords.txt`
|
|
4780
|
-
// all trip — while false friends like `secretary.md` do not).
|
|
4781
|
-
/(^|\/)[^/]*\bsecrets?\b[^/]*$/i,
|
|
4782
|
-
/(^|\/)[^/]*\bcredentials?\b[^/]*$/i,
|
|
4783
|
-
/(^|\/)[^/]*\bpasswords?\b[^/]*$/i,
|
|
4784
|
-
/(^|\/)[^/]*\bapi[-_]keys?\b[^/]*$/i,
|
|
4785
|
-
/(^|\/)\.netrc$/i,
|
|
4786
|
-
/(^|\/)\.npmrc$/i
|
|
4787
|
-
];
|
|
4788
|
-
function isSecretPath(p) {
|
|
4789
|
-
return SECRET_PATTERNS.some((r) => r.test(p));
|
|
4790
|
-
}
|
|
4791
|
-
var DEP_FILES = /* @__PURE__ */ new Set([
|
|
4792
|
-
"package.json",
|
|
4793
|
-
"pnpm-lock.yaml",
|
|
4794
|
-
"package-lock.json",
|
|
4795
|
-
"yarn.lock",
|
|
4796
|
-
"requirements.txt",
|
|
4797
|
-
"Pipfile",
|
|
4798
|
-
"Pipfile.lock",
|
|
4799
|
-
"poetry.lock",
|
|
4800
|
-
"go.mod",
|
|
4801
|
-
"go.sum",
|
|
4802
|
-
"Cargo.toml",
|
|
4803
|
-
"Cargo.lock",
|
|
4804
|
-
"Gemfile",
|
|
4805
|
-
"Gemfile.lock"
|
|
4806
|
-
]);
|
|
4807
|
-
function isDepFile(p) {
|
|
4808
|
-
return DEP_FILES.has(p.split("/").pop() ?? "");
|
|
4809
|
-
}
|
|
4810
|
-
function isTestFile(p) {
|
|
4811
|
-
return /(^|\/)(tests?|__tests__|spec)\//i.test(p) || /\.(test|spec)\.[a-z0-9]+$/i.test(p);
|
|
4812
|
-
}
|
|
4813
|
-
function collectBranchChangedFiles(ctx) {
|
|
4814
|
-
const base = ctx.config.git.defaultBranch;
|
|
4815
|
-
for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
|
|
4816
|
-
try {
|
|
4817
|
-
const out = execFileSync18("git", ["diff", "--name-only", ref], {
|
|
4818
|
-
cwd: ctx.cwd,
|
|
4819
|
-
encoding: "utf-8",
|
|
4820
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4821
|
-
});
|
|
4822
|
-
const files = out.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
4823
|
-
if (files.length > 0) return files;
|
|
4824
|
-
} catch {
|
|
4825
|
-
}
|
|
4826
|
-
}
|
|
4827
|
-
return ctx.data.changedFiles ?? [];
|
|
4828
|
-
}
|
|
4829
|
-
function computeDiffStats(ctx) {
|
|
4830
|
-
const base = ctx.config.git.defaultBranch;
|
|
4831
|
-
for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
|
|
4832
|
-
try {
|
|
4833
|
-
const out = execFileSync18("git", ["diff", "--shortstat", ref], {
|
|
4834
|
-
cwd: ctx.cwd,
|
|
4835
|
-
encoding: "utf-8",
|
|
4836
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4837
|
-
}).trim();
|
|
4838
|
-
if (out) return parseShortstat(out);
|
|
4839
|
-
} catch {
|
|
4840
|
-
}
|
|
4841
|
-
}
|
|
4842
|
-
return null;
|
|
4843
|
-
}
|
|
4844
|
-
function parseShortstat(s) {
|
|
4845
|
-
const ins = /(\d+)\s+insertions?/.exec(s);
|
|
4846
|
-
const del = /(\d+)\s+deletions?/.exec(s);
|
|
4847
|
-
return {
|
|
4848
|
-
insertions: ins ? parseInt(ins[1], 10) : 0,
|
|
4849
|
-
deletions: del ? parseInt(del[1], 10) : 0
|
|
4850
|
-
};
|
|
4851
|
-
}
|
|
4852
|
-
function listDeletedFilesInHeadCommit(cwd) {
|
|
4853
|
-
try {
|
|
4854
|
-
const out = execFileSync18("git", ["show", "--name-status", "--pretty=format:", "HEAD"], {
|
|
4855
|
-
cwd,
|
|
4856
|
-
encoding: "utf-8",
|
|
4857
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4858
|
-
});
|
|
4859
|
-
return out.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("D ")).map((l) => l.slice(2).trim()).filter(Boolean);
|
|
4860
|
-
} catch {
|
|
4861
|
-
return [];
|
|
4862
|
-
}
|
|
4863
|
-
}
|
|
4864
|
-
function ensureApproveLabel(gate, cwd) {
|
|
4865
|
-
try {
|
|
4866
|
-
gh2(
|
|
4867
|
-
[
|
|
4868
|
-
"label",
|
|
4869
|
-
"create",
|
|
4870
|
-
`kody-approve:${gate}`,
|
|
4871
|
-
"--force",
|
|
4872
|
-
"--color",
|
|
4873
|
-
"0e8a16",
|
|
4874
|
-
"--description",
|
|
4875
|
-
`kody2: approve the ${gate} risk gate and resume the flow`
|
|
4876
|
-
],
|
|
4877
|
-
{ cwd }
|
|
4878
|
-
);
|
|
4879
|
-
} catch {
|
|
4880
|
-
}
|
|
4881
|
-
}
|
|
4882
|
-
function formatAdvisory(pending, compareUrl) {
|
|
4883
|
-
const lines = [];
|
|
4884
|
-
lines.push("\u23F8\uFE0F **kody2 risk gate halted the flow.**");
|
|
4885
|
-
lines.push("");
|
|
4886
|
-
lines.push("The branch was pushed but **no PR was opened** \u2014 waiting for human approval:");
|
|
4887
|
-
lines.push("");
|
|
4888
|
-
for (const v of pending) {
|
|
4889
|
-
lines.push(`- **\`${v.name}\`** _(${v.severity})_ \u2014 ${v.reason}`);
|
|
4890
|
-
}
|
|
4891
|
-
lines.push("");
|
|
4892
|
-
if (compareUrl) {
|
|
4893
|
-
lines.push(`\u{1F4CE} Review the branch diff: ${compareUrl}`);
|
|
4894
|
-
lines.push("");
|
|
4895
|
-
}
|
|
4896
|
-
lines.push("**To approve and resume**, post a comment:");
|
|
4897
|
-
lines.push("");
|
|
4898
|
-
lines.push("> `@kody2 approve`");
|
|
4899
|
-
lines.push("");
|
|
4900
|
-
lines.push(
|
|
4901
|
-
"kody2 will acknowledge all currently-pending gates (soft **and** hard), open the PR, and continue the flow from this checkpoint. No re-running the agent."
|
|
4902
|
-
);
|
|
4903
|
-
return lines.join("\n");
|
|
4904
|
-
}
|
|
4905
|
-
function computeCompareUrl(ctx) {
|
|
4906
|
-
const branch = ctx.data.branch;
|
|
4907
|
-
if (!branch) return null;
|
|
4908
|
-
const owner = ctx.config.github?.owner;
|
|
4909
|
-
const repo = ctx.config.github?.repo;
|
|
4910
|
-
if (!owner || !repo) return null;
|
|
4911
|
-
const base = ctx.config.git.defaultBranch;
|
|
4912
|
-
return `https://github.com/${owner}/${repo}/compare/${base}...${branch}`;
|
|
4913
|
-
}
|
|
4914
|
-
|
|
4915
4495
|
// src/scripts/runFlow.ts
|
|
4916
4496
|
var runFlow = async (ctx) => {
|
|
4917
4497
|
const issueNumber = ctx.args.issue;
|
|
@@ -5008,8 +4588,8 @@ var skipAgent = async (ctx) => {
|
|
|
5008
4588
|
};
|
|
5009
4589
|
|
|
5010
4590
|
// src/scripts/startFlow.ts
|
|
5011
|
-
import { execFileSync as
|
|
5012
|
-
var
|
|
4591
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
4592
|
+
var API_TIMEOUT_MS7 = 3e4;
|
|
5013
4593
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
5014
4594
|
const entry = args?.entry;
|
|
5015
4595
|
if (!entry) {
|
|
@@ -5042,8 +4622,8 @@ function postKody2Comment(target, issueNumber, state, next, cwd) {
|
|
|
5042
4622
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
5043
4623
|
const body = `@kody2 ${next}`;
|
|
5044
4624
|
try {
|
|
5045
|
-
|
|
5046
|
-
timeout:
|
|
4625
|
+
execFileSync17("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
4626
|
+
timeout: API_TIMEOUT_MS7,
|
|
5047
4627
|
cwd,
|
|
5048
4628
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5049
4629
|
});
|
|
@@ -5062,7 +4642,7 @@ function parsePr2(url) {
|
|
|
5062
4642
|
}
|
|
5063
4643
|
|
|
5064
4644
|
// src/scripts/syncFlow.ts
|
|
5065
|
-
import { execFileSync as
|
|
4645
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
5066
4646
|
var syncFlow = async (ctx) => {
|
|
5067
4647
|
ctx.skipAgent = true;
|
|
5068
4648
|
const prNumber = ctx.args.pr;
|
|
@@ -5121,7 +4701,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
5121
4701
|
}
|
|
5122
4702
|
function revParseHead(cwd) {
|
|
5123
4703
|
try {
|
|
5124
|
-
return
|
|
4704
|
+
return execFileSync18("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
5125
4705
|
} catch {
|
|
5126
4706
|
return "";
|
|
5127
4707
|
}
|
|
@@ -5129,9 +4709,9 @@ function revParseHead(cwd) {
|
|
|
5129
4709
|
function pushBranch(branch, cwd) {
|
|
5130
4710
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
5131
4711
|
try {
|
|
5132
|
-
|
|
4712
|
+
execFileSync18("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
5133
4713
|
} catch {
|
|
5134
|
-
|
|
4714
|
+
execFileSync18("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
5135
4715
|
cwd,
|
|
5136
4716
|
env,
|
|
5137
4717
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5373,9 +4953,7 @@ var postflightScripts = {
|
|
|
5373
4953
|
finishFlow,
|
|
5374
4954
|
advanceFlow,
|
|
5375
4955
|
persistFlowState,
|
|
5376
|
-
postClassification
|
|
5377
|
-
riskGate,
|
|
5378
|
-
applyApprovals
|
|
4956
|
+
postClassification
|
|
5379
4957
|
};
|
|
5380
4958
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
5381
4959
|
...Object.keys(preflightScripts),
|
|
@@ -5383,7 +4961,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
5383
4961
|
]);
|
|
5384
4962
|
|
|
5385
4963
|
// src/tools.ts
|
|
5386
|
-
import { execFileSync as
|
|
4964
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
5387
4965
|
function verifyCliTools(tools, cwd) {
|
|
5388
4966
|
const out = [];
|
|
5389
4967
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -5416,7 +4994,7 @@ function verifyOne(tool, cwd) {
|
|
|
5416
4994
|
}
|
|
5417
4995
|
function runShell2(cmd, cwd, timeoutMs = 3e4) {
|
|
5418
4996
|
try {
|
|
5419
|
-
|
|
4997
|
+
execFileSync19("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
5420
4998
|
return true;
|
|
5421
4999
|
} catch {
|
|
5422
5000
|
return false;
|
|
@@ -5746,7 +5324,7 @@ function detectPackageManager2(cwd) {
|
|
|
5746
5324
|
}
|
|
5747
5325
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
5748
5326
|
try {
|
|
5749
|
-
|
|
5327
|
+
execFileSync20(cmd, args, {
|
|
5750
5328
|
cwd,
|
|
5751
5329
|
stdio: stream ? "inherit" : "pipe",
|
|
5752
5330
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -5759,7 +5337,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
5759
5337
|
}
|
|
5760
5338
|
function isOnPath(bin) {
|
|
5761
5339
|
try {
|
|
5762
|
-
|
|
5340
|
+
execFileSync20("which", [bin], { stdio: "pipe" });
|
|
5763
5341
|
return true;
|
|
5764
5342
|
} catch {
|
|
5765
5343
|
return false;
|
|
@@ -5793,7 +5371,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5793
5371
|
} catch {
|
|
5794
5372
|
}
|
|
5795
5373
|
try {
|
|
5796
|
-
|
|
5374
|
+
execFileSync20("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
5797
5375
|
process.stdout.write("\u2192 kody2: litellm already installed\n");
|
|
5798
5376
|
return 0;
|
|
5799
5377
|
} catch {
|
|
@@ -5803,16 +5381,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5803
5381
|
}
|
|
5804
5382
|
function configureGitIdentity(cwd) {
|
|
5805
5383
|
try {
|
|
5806
|
-
const name =
|
|
5384
|
+
const name = execFileSync20("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
5807
5385
|
if (name) return;
|
|
5808
5386
|
} catch {
|
|
5809
5387
|
}
|
|
5810
5388
|
try {
|
|
5811
|
-
|
|
5389
|
+
execFileSync20("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
5812
5390
|
} catch {
|
|
5813
5391
|
}
|
|
5814
5392
|
try {
|
|
5815
|
-
|
|
5393
|
+
execFileSync20("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
5816
5394
|
cwd,
|
|
5817
5395
|
stdio: "pipe"
|
|
5818
5396
|
});
|
|
@@ -5989,9 +5567,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
5989
5567
|
if (paths.length === 0) return;
|
|
5990
5568
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
5991
5569
|
try {
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5570
|
+
execFileSync21("git", ["add", ...paths], opts);
|
|
5571
|
+
execFileSync21("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
5572
|
+
execFileSync21("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
5995
5573
|
} catch (err) {
|
|
5996
5574
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5997
5575
|
process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
|