@kody-ade/kody-engine 0.2.58 → 0.2.59
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 +385 -226
- package/dist/executables/approve/profile.json +47 -0
- package/dist/executables/approve/prompt.md +1 -0
- package/dist/executables/fix/profile.json +11 -34
- package/dist/executables/release/profile.json +1 -0
- package/dist/executables/review/profile.json +1 -0
- package/dist/executables/run/profile.json +12 -37
- package/dist/executables/types.ts +7 -0
- package/package.json +1 -1
- package/templates/kody2.yml +6 -2
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.59",
|
|
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 execFileSync23 } 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 execFileSync22 } from "child_process";
|
|
547
547
|
import * as fs21 from "fs";
|
|
548
548
|
import * as path18 from "path";
|
|
549
549
|
|
|
@@ -581,6 +581,9 @@ 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
|
+
}
|
|
584
587
|
if (/\bfix-ci\b/.test(afterTag)) {
|
|
585
588
|
return { executable: "fix-ci", cliArgs: { pr: targetNum }, target: targetNum };
|
|
586
589
|
}
|
|
@@ -754,6 +757,7 @@ import * as path6 from "path";
|
|
|
754
757
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
755
758
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
756
759
|
var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "watch", "utility"]);
|
|
760
|
+
var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
|
|
757
761
|
var ProfileError = class extends Error {
|
|
758
762
|
constructor(profilePath, message) {
|
|
759
763
|
super(`Invalid profile at ${profilePath}:
|
|
@@ -788,12 +792,20 @@ function loadProfile(profilePath) {
|
|
|
788
792
|
);
|
|
789
793
|
}
|
|
790
794
|
const role = r.role;
|
|
795
|
+
let phase;
|
|
796
|
+
if (r.phase !== void 0) {
|
|
797
|
+
if (typeof r.phase !== "string" || !VALID_PHASES.has(r.phase)) {
|
|
798
|
+
throw new ProfileError(profilePath, `"phase" must be one of: ${[...VALID_PHASES].join(" | ")}`);
|
|
799
|
+
}
|
|
800
|
+
phase = r.phase;
|
|
801
|
+
}
|
|
791
802
|
const profile = {
|
|
792
803
|
name: requireString(profilePath, r, "name"),
|
|
793
804
|
describe: typeof r.describe === "string" ? r.describe : "",
|
|
794
805
|
role,
|
|
795
806
|
kind,
|
|
796
807
|
schedule: typeof r.schedule === "string" ? r.schedule : void 0,
|
|
808
|
+
phase,
|
|
797
809
|
inputs: parseInputs(profilePath, r.inputs),
|
|
798
810
|
claudeCode: parseClaudeCode(profilePath, r.claudeCode),
|
|
799
811
|
cliTools: parseCliTools(profilePath, r.cliTools),
|
|
@@ -1060,7 +1072,7 @@ function parseStateComment(body) {
|
|
|
1060
1072
|
return emptyState();
|
|
1061
1073
|
}
|
|
1062
1074
|
}
|
|
1063
|
-
function reduce(state, executable, action) {
|
|
1075
|
+
function reduce(state, executable, action, phase) {
|
|
1064
1076
|
if (!action) return state;
|
|
1065
1077
|
const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
|
|
1066
1078
|
const newExecutables = {
|
|
@@ -1079,7 +1091,7 @@ function reduce(state, executable, action) {
|
|
|
1079
1091
|
lastOutcome: action,
|
|
1080
1092
|
currentExecutable: executable,
|
|
1081
1093
|
status: statusFromAction(action),
|
|
1082
|
-
phase: phaseFromAction(
|
|
1094
|
+
phase: phaseFromAction(action, phase)
|
|
1083
1095
|
},
|
|
1084
1096
|
executables: newExecutables,
|
|
1085
1097
|
artifacts: { ...state.artifacts ?? {} },
|
|
@@ -1092,12 +1104,9 @@ function statusFromAction(action) {
|
|
|
1092
1104
|
if (/COMPLETED$|SHIPPED$|MERGED$|SUCCESS$/i.test(action.type)) return "succeeded";
|
|
1093
1105
|
return "running";
|
|
1094
1106
|
}
|
|
1095
|
-
function phaseFromAction(
|
|
1107
|
+
function phaseFromAction(action, phase) {
|
|
1096
1108
|
if (/FAILED$|ERROR$|REJECTED$/i.test(action.type)) return "failed";
|
|
1097
|
-
|
|
1098
|
-
if (executable === "review") return "reviewing";
|
|
1099
|
-
if (executable === "release") return "shipped";
|
|
1100
|
-
return "idle";
|
|
1109
|
+
return phase ?? "idle";
|
|
1101
1110
|
}
|
|
1102
1111
|
function noteFromAction(action) {
|
|
1103
1112
|
const p = action.payload;
|
|
@@ -1206,7 +1215,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
1206
1215
|
try {
|
|
1207
1216
|
const issueState = readTaskState("issue", flow.issueNumber, ctx.cwd);
|
|
1208
1217
|
issueState.flow = flow;
|
|
1209
|
-
const next = reduce(issueState, profile.name, action);
|
|
1218
|
+
const next = reduce(issueState, profile.name, action, profile.phase);
|
|
1210
1219
|
if (state?.core.prUrl && !next.core.prUrl) next.core.prUrl = state.core.prUrl;
|
|
1211
1220
|
next.flow = flow;
|
|
1212
1221
|
writeTaskState("issue", flow.issueNumber, next, ctx.cwd);
|
|
@@ -1232,6 +1241,265 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
1232
1241
|
}
|
|
1233
1242
|
};
|
|
1234
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 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
|
+
|
|
1235
1503
|
// src/scripts/buildSyntheticPlugin.ts
|
|
1236
1504
|
import * as fs8 from "fs";
|
|
1237
1505
|
import * as os2 from "os";
|
|
@@ -1326,7 +1594,7 @@ function copyDir(src, dst) {
|
|
|
1326
1594
|
}
|
|
1327
1595
|
|
|
1328
1596
|
// src/coverage.ts
|
|
1329
|
-
import { execFileSync as
|
|
1597
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
1330
1598
|
function patternToRegex(pattern) {
|
|
1331
1599
|
let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
1332
1600
|
s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
|
|
@@ -1344,7 +1612,7 @@ function renderSiblingPath(file, requireSibling) {
|
|
|
1344
1612
|
}
|
|
1345
1613
|
function safeGit(args, cwd) {
|
|
1346
1614
|
try {
|
|
1347
|
-
return
|
|
1615
|
+
return execFileSync6("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
|
|
1348
1616
|
} catch {
|
|
1349
1617
|
return "";
|
|
1350
1618
|
}
|
|
@@ -1550,10 +1818,10 @@ function defaultLabelMap() {
|
|
|
1550
1818
|
}
|
|
1551
1819
|
|
|
1552
1820
|
// src/scripts/commitAndPush.ts
|
|
1553
|
-
import { execFileSync as
|
|
1821
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
1554
1822
|
|
|
1555
1823
|
// src/commit.ts
|
|
1556
|
-
import { execFileSync as
|
|
1824
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
1557
1825
|
import * as fs10 from "fs";
|
|
1558
1826
|
import * as path9 from "path";
|
|
1559
1827
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
@@ -1583,7 +1851,7 @@ var CONVENTIONAL_PREFIXES = [
|
|
|
1583
1851
|
];
|
|
1584
1852
|
function git(args, cwd) {
|
|
1585
1853
|
try {
|
|
1586
|
-
return
|
|
1854
|
+
return execFileSync7("git", args, {
|
|
1587
1855
|
encoding: "utf-8",
|
|
1588
1856
|
timeout: 12e4,
|
|
1589
1857
|
cwd,
|
|
@@ -1641,7 +1909,7 @@ function isForbiddenPath(p) {
|
|
|
1641
1909
|
return false;
|
|
1642
1910
|
}
|
|
1643
1911
|
function listChangedFiles(cwd) {
|
|
1644
|
-
const raw =
|
|
1912
|
+
const raw = execFileSync7("git", ["status", "--porcelain=v1", "-z"], {
|
|
1645
1913
|
encoding: "utf-8",
|
|
1646
1914
|
cwd,
|
|
1647
1915
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -1653,7 +1921,7 @@ function listChangedFiles(cwd) {
|
|
|
1653
1921
|
}
|
|
1654
1922
|
function listFilesInCommit(ref = "HEAD", cwd) {
|
|
1655
1923
|
try {
|
|
1656
|
-
const raw =
|
|
1924
|
+
const raw = execFileSync7("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
|
|
1657
1925
|
encoding: "utf-8",
|
|
1658
1926
|
cwd,
|
|
1659
1927
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -1733,7 +2001,7 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
1733
2001
|
const kind = profile.name;
|
|
1734
2002
|
if (kind === "resolve") {
|
|
1735
2003
|
try {
|
|
1736
|
-
|
|
2004
|
+
execFileSync8("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
|
|
1737
2005
|
} catch {
|
|
1738
2006
|
}
|
|
1739
2007
|
} else {
|
|
@@ -2355,8 +2623,8 @@ var discoverQaContext = async (ctx) => {
|
|
|
2355
2623
|
};
|
|
2356
2624
|
|
|
2357
2625
|
// src/scripts/dispatch.ts
|
|
2358
|
-
import { execFileSync as
|
|
2359
|
-
var
|
|
2626
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
2627
|
+
var API_TIMEOUT_MS5 = 3e4;
|
|
2360
2628
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
2361
2629
|
const next = args?.next;
|
|
2362
2630
|
if (!next) {
|
|
@@ -2378,8 +2646,8 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
2378
2646
|
const sub = usePr ? "pr" : "issue";
|
|
2379
2647
|
const body = `@kody2 ${next}`;
|
|
2380
2648
|
try {
|
|
2381
|
-
|
|
2382
|
-
timeout:
|
|
2649
|
+
execFileSync9("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
2650
|
+
timeout: API_TIMEOUT_MS5,
|
|
2383
2651
|
cwd: ctx.cwd,
|
|
2384
2652
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2385
2653
|
});
|
|
@@ -2397,141 +2665,6 @@ function parsePr(url) {
|
|
|
2397
2665
|
return Number.isFinite(n) ? n : null;
|
|
2398
2666
|
}
|
|
2399
2667
|
|
|
2400
|
-
// src/issue.ts
|
|
2401
|
-
import { execFileSync as execFileSync8 } from "child_process";
|
|
2402
|
-
var API_TIMEOUT_MS4 = 3e4;
|
|
2403
|
-
function ghToken2() {
|
|
2404
|
-
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
2405
|
-
}
|
|
2406
|
-
function gh2(args, options) {
|
|
2407
|
-
const token = ghToken2();
|
|
2408
|
-
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
2409
|
-
return execFileSync8("gh", args, {
|
|
2410
|
-
encoding: "utf-8",
|
|
2411
|
-
timeout: API_TIMEOUT_MS4,
|
|
2412
|
-
cwd: options?.cwd,
|
|
2413
|
-
env,
|
|
2414
|
-
input: options?.input,
|
|
2415
|
-
stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
|
|
2416
|
-
}).trim();
|
|
2417
|
-
}
|
|
2418
|
-
function getIssue(issueNumber, cwd) {
|
|
2419
|
-
const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
|
|
2420
|
-
const parsed = JSON.parse(output);
|
|
2421
|
-
if (typeof parsed?.title !== "string") {
|
|
2422
|
-
throw new Error(`Issue #${issueNumber}: unexpected response shape`);
|
|
2423
|
-
}
|
|
2424
|
-
return {
|
|
2425
|
-
number: parsed.number ?? issueNumber,
|
|
2426
|
-
title: parsed.title,
|
|
2427
|
-
body: parsed.body ?? "",
|
|
2428
|
-
comments: (parsed.comments ?? []).map((c) => ({
|
|
2429
|
-
body: c.body ?? "",
|
|
2430
|
-
author: c.author?.login ?? "unknown",
|
|
2431
|
-
createdAt: c.createdAt ?? ""
|
|
2432
|
-
})),
|
|
2433
|
-
labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
|
|
2434
|
-
};
|
|
2435
|
-
}
|
|
2436
|
-
function stripKody2Mentions(body) {
|
|
2437
|
-
return body.replace(/(@)(kody2)/gi, "$1\u200B$2");
|
|
2438
|
-
}
|
|
2439
|
-
function postIssueComment(issueNumber, body, cwd) {
|
|
2440
|
-
try {
|
|
2441
|
-
gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
|
|
2442
|
-
} catch (err) {
|
|
2443
|
-
process.stderr.write(
|
|
2444
|
-
`[kody2] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2445
|
-
`
|
|
2446
|
-
);
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
function truncate2(s, maxBytes) {
|
|
2450
|
-
if (s.length <= maxBytes) return s;
|
|
2451
|
-
return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
|
|
2452
|
-
}
|
|
2453
|
-
function getPr(prNumber, cwd) {
|
|
2454
|
-
const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
|
|
2455
|
-
cwd
|
|
2456
|
-
});
|
|
2457
|
-
const parsed = JSON.parse(output);
|
|
2458
|
-
if (typeof parsed?.title !== "string") {
|
|
2459
|
-
throw new Error(`PR #${prNumber}: unexpected response shape`);
|
|
2460
|
-
}
|
|
2461
|
-
return {
|
|
2462
|
-
number: parsed.number ?? prNumber,
|
|
2463
|
-
title: parsed.title,
|
|
2464
|
-
body: parsed.body ?? "",
|
|
2465
|
-
headRefName: String(parsed.headRefName ?? ""),
|
|
2466
|
-
baseRefName: String(parsed.baseRefName ?? ""),
|
|
2467
|
-
state: String(parsed.state ?? "")
|
|
2468
|
-
};
|
|
2469
|
-
}
|
|
2470
|
-
function getPrDiff(prNumber, cwd) {
|
|
2471
|
-
try {
|
|
2472
|
-
return gh2(["pr", "diff", String(prNumber)], { cwd });
|
|
2473
|
-
} catch (err) {
|
|
2474
|
-
process.stderr.write(
|
|
2475
|
-
`[kody2] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2476
|
-
`
|
|
2477
|
-
);
|
|
2478
|
-
return "";
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
function getPrReviews(prNumber, cwd) {
|
|
2482
|
-
try {
|
|
2483
|
-
const output = gh2(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
|
|
2484
|
-
const parsed = JSON.parse(output);
|
|
2485
|
-
if (!Array.isArray(parsed?.reviews)) return [];
|
|
2486
|
-
return parsed.reviews.map(
|
|
2487
|
-
(r) => ({
|
|
2488
|
-
body: r.body ?? "",
|
|
2489
|
-
state: r.state ?? "",
|
|
2490
|
-
author: r.author?.login ?? "unknown",
|
|
2491
|
-
submittedAt: r.submittedAt ?? ""
|
|
2492
|
-
})
|
|
2493
|
-
);
|
|
2494
|
-
} catch {
|
|
2495
|
-
return [];
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
function getPrComments(prNumber, cwd) {
|
|
2499
|
-
try {
|
|
2500
|
-
const output = gh2(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
|
|
2501
|
-
const parsed = JSON.parse(output);
|
|
2502
|
-
if (!Array.isArray(parsed?.comments)) return [];
|
|
2503
|
-
return parsed.comments.map((c) => ({
|
|
2504
|
-
body: c.body ?? "",
|
|
2505
|
-
author: c.author?.login ?? "unknown",
|
|
2506
|
-
createdAt: c.createdAt ?? ""
|
|
2507
|
-
})).filter((c) => c.body.trim().length > 0);
|
|
2508
|
-
} catch {
|
|
2509
|
-
return [];
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
var VERDICT_HEADING = /(^|\n)\s*#{1,6}\s*Verdict\s*:/i;
|
|
2513
|
-
function isReviewShaped(body) {
|
|
2514
|
-
return VERDICT_HEADING.test(body);
|
|
2515
|
-
}
|
|
2516
|
-
function getPrLatestReviewBody(prNumber, cwd) {
|
|
2517
|
-
const reviews = getPrReviews(prNumber, cwd).filter((r) => r.body.trim().length > 0).map((r) => ({ body: r.body, at: r.submittedAt }));
|
|
2518
|
-
const comments = getPrComments(prNumber, cwd).filter((c) => isReviewShaped(c.body)).map((c) => ({ body: c.body, at: c.createdAt }));
|
|
2519
|
-
const all = [...reviews, ...comments].sort((a, b) => (b.at || "").localeCompare(a.at || ""));
|
|
2520
|
-
if (all.length > 0) return all[0].body;
|
|
2521
|
-
const pr = getPr(prNumber, cwd);
|
|
2522
|
-
return pr.body;
|
|
2523
|
-
}
|
|
2524
|
-
function postPrReviewComment(prNumber, body, cwd) {
|
|
2525
|
-
try {
|
|
2526
|
-
gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
|
|
2527
|
-
} catch (err) {
|
|
2528
|
-
process.stderr.write(
|
|
2529
|
-
`[kody2] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
2530
|
-
`
|
|
2531
|
-
);
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
2668
|
// src/pr.ts
|
|
2536
2669
|
var TITLE_MAX = 72;
|
|
2537
2670
|
function stripTitlePrefixes(raw) {
|
|
@@ -2697,7 +2830,7 @@ function computeFailureReason(ctx) {
|
|
|
2697
2830
|
}
|
|
2698
2831
|
|
|
2699
2832
|
// src/scripts/finishFlow.ts
|
|
2700
|
-
import { execFileSync as
|
|
2833
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
2701
2834
|
|
|
2702
2835
|
// src/registry.ts
|
|
2703
2836
|
import * as fs14 from "fs";
|
|
@@ -2816,7 +2949,7 @@ function getIssueLabels(issueNumber, cwd) {
|
|
|
2816
2949
|
return [];
|
|
2817
2950
|
}
|
|
2818
2951
|
}
|
|
2819
|
-
function
|
|
2952
|
+
function addLabel2(issueNumber, label, cwd) {
|
|
2820
2953
|
gh2(["issue", "edit", String(issueNumber), "--add-label", label], { cwd });
|
|
2821
2954
|
}
|
|
2822
2955
|
function removeLabel(issueNumber, label, cwd) {
|
|
@@ -2848,12 +2981,12 @@ function setKodyLabel(issueNumber, spec, cwd) {
|
|
|
2848
2981
|
}
|
|
2849
2982
|
}
|
|
2850
2983
|
try {
|
|
2851
|
-
|
|
2984
|
+
addLabel2(issueNumber, target, cwd);
|
|
2852
2985
|
} catch (err) {
|
|
2853
2986
|
if (looksLikeMissingLabel(err)) {
|
|
2854
2987
|
try {
|
|
2855
2988
|
createLabelInRepo(spec, cwd);
|
|
2856
|
-
|
|
2989
|
+
addLabel2(issueNumber, target, cwd);
|
|
2857
2990
|
return;
|
|
2858
2991
|
} catch (retryErr) {
|
|
2859
2992
|
process.stderr.write(
|
|
@@ -2885,7 +3018,7 @@ function errMsg(err) {
|
|
|
2885
3018
|
}
|
|
2886
3019
|
|
|
2887
3020
|
// src/scripts/finishFlow.ts
|
|
2888
|
-
var
|
|
3021
|
+
var API_TIMEOUT_MS6 = 3e4;
|
|
2889
3022
|
var STATUS_ICON = {
|
|
2890
3023
|
"review-passed": "\u2705",
|
|
2891
3024
|
"fix-applied": "\u2705",
|
|
@@ -2917,8 +3050,8 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
2917
3050
|
**PR:** ${state.core.prUrl}` : "";
|
|
2918
3051
|
const body = `${icon} kody2 flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
2919
3052
|
try {
|
|
2920
|
-
|
|
2921
|
-
timeout:
|
|
3053
|
+
execFileSync10("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
3054
|
+
timeout: API_TIMEOUT_MS6,
|
|
2922
3055
|
cwd: ctx.cwd,
|
|
2923
3056
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2924
3057
|
});
|
|
@@ -2931,7 +3064,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
2931
3064
|
};
|
|
2932
3065
|
|
|
2933
3066
|
// src/branch.ts
|
|
2934
|
-
import { execFileSync as
|
|
3067
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
2935
3068
|
var UncommittedChangesError = class extends Error {
|
|
2936
3069
|
constructor(branch) {
|
|
2937
3070
|
super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
|
|
@@ -2941,7 +3074,7 @@ var UncommittedChangesError = class extends Error {
|
|
|
2941
3074
|
branch;
|
|
2942
3075
|
};
|
|
2943
3076
|
function git2(args, cwd) {
|
|
2944
|
-
return
|
|
3077
|
+
return execFileSync11("git", args, {
|
|
2945
3078
|
encoding: "utf-8",
|
|
2946
3079
|
timeout: 3e4,
|
|
2947
3080
|
cwd,
|
|
@@ -2966,7 +3099,7 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
2966
3099
|
SKIP_HOOKS: "1",
|
|
2967
3100
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
2968
3101
|
};
|
|
2969
|
-
|
|
3102
|
+
execFileSync11("gh", ["pr", "checkout", String(prNumber)], {
|
|
2970
3103
|
cwd,
|
|
2971
3104
|
env,
|
|
2972
3105
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3033,7 +3166,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
3033
3166
|
}
|
|
3034
3167
|
|
|
3035
3168
|
// src/gha.ts
|
|
3036
|
-
import { execFileSync as
|
|
3169
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
3037
3170
|
import * as fs15 from "fs";
|
|
3038
3171
|
function getRunUrl() {
|
|
3039
3172
|
const server = process.env.GITHUB_SERVER_URL;
|
|
@@ -3076,7 +3209,7 @@ function reactToTriggerComment(cwd) {
|
|
|
3076
3209
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
3077
3210
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
3078
3211
|
try {
|
|
3079
|
-
|
|
3212
|
+
execFileSync12("gh", args, opts);
|
|
3080
3213
|
return;
|
|
3081
3214
|
} catch (err) {
|
|
3082
3215
|
lastErr = err;
|
|
@@ -3089,13 +3222,13 @@ function reactToTriggerComment(cwd) {
|
|
|
3089
3222
|
}
|
|
3090
3223
|
function sleepMs(ms) {
|
|
3091
3224
|
try {
|
|
3092
|
-
|
|
3225
|
+
execFileSync12("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
3093
3226
|
} catch {
|
|
3094
3227
|
}
|
|
3095
3228
|
}
|
|
3096
3229
|
|
|
3097
3230
|
// src/workflow.ts
|
|
3098
|
-
import { execFileSync as
|
|
3231
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
3099
3232
|
var GH_TIMEOUT_MS = 3e4;
|
|
3100
3233
|
function ghToken3() {
|
|
3101
3234
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -3103,7 +3236,7 @@ function ghToken3() {
|
|
|
3103
3236
|
function gh3(args, cwd) {
|
|
3104
3237
|
const token = ghToken3();
|
|
3105
3238
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
3106
|
-
return
|
|
3239
|
+
return execFileSync13("gh", args, {
|
|
3107
3240
|
encoding: "utf-8",
|
|
3108
3241
|
timeout: GH_TIMEOUT_MS,
|
|
3109
3242
|
cwd,
|
|
@@ -3287,7 +3420,7 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
3287
3420
|
}
|
|
3288
3421
|
|
|
3289
3422
|
// src/scripts/initFlow.ts
|
|
3290
|
-
import { execFileSync as
|
|
3423
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
3291
3424
|
import * as fs17 from "fs";
|
|
3292
3425
|
import * as path15 from "path";
|
|
3293
3426
|
|
|
@@ -3328,7 +3461,7 @@ function qualityCommandsFor(pm) {
|
|
|
3328
3461
|
function detectOwnerRepo(cwd) {
|
|
3329
3462
|
let url;
|
|
3330
3463
|
try {
|
|
3331
|
-
url =
|
|
3464
|
+
url = execFileSync14("git", ["remote", "get-url", "origin"], {
|
|
3332
3465
|
cwd,
|
|
3333
3466
|
encoding: "utf-8",
|
|
3334
3467
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3412,7 +3545,7 @@ jobs:
|
|
|
3412
3545
|
`;
|
|
3413
3546
|
function defaultBranchFromGit(cwd) {
|
|
3414
3547
|
try {
|
|
3415
|
-
const ref =
|
|
3548
|
+
const ref = execFileSync14("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
3416
3549
|
cwd,
|
|
3417
3550
|
encoding: "utf-8",
|
|
3418
3551
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3420,7 +3553,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
3420
3553
|
return ref.replace("refs/remotes/origin/", "");
|
|
3421
3554
|
} catch {
|
|
3422
3555
|
try {
|
|
3423
|
-
return
|
|
3556
|
+
return execFileSync14("git", ["branch", "--show-current"], {
|
|
3424
3557
|
cwd,
|
|
3425
3558
|
encoding: "utf-8",
|
|
3426
3559
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3610,7 +3743,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
3610
3743
|
if (!issueNumber || issueTarget !== "issue") return;
|
|
3611
3744
|
const prUrl = ctx.output.prUrl ?? ctx.data.prResult?.url;
|
|
3612
3745
|
if (!prUrl) return;
|
|
3613
|
-
const prNumber =
|
|
3746
|
+
const prNumber = parsePrNumber2(prUrl);
|
|
3614
3747
|
if (!prNumber) return;
|
|
3615
3748
|
let state;
|
|
3616
3749
|
try {
|
|
@@ -3628,7 +3761,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
3628
3761
|
);
|
|
3629
3762
|
}
|
|
3630
3763
|
};
|
|
3631
|
-
function
|
|
3764
|
+
function parsePrNumber2(prUrl) {
|
|
3632
3765
|
const m = prUrl.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
3633
3766
|
if (!m) return null;
|
|
3634
3767
|
const n = parseInt(m[1], 10);
|
|
@@ -3713,8 +3846,8 @@ var persistFlowState = async (ctx) => {
|
|
|
3713
3846
|
};
|
|
3714
3847
|
|
|
3715
3848
|
// src/scripts/postClassification.ts
|
|
3716
|
-
import { execFileSync as
|
|
3717
|
-
var
|
|
3849
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
3850
|
+
var API_TIMEOUT_MS7 = 3e4;
|
|
3718
3851
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
3719
3852
|
var postClassification = async (ctx) => {
|
|
3720
3853
|
const issueNumber = ctx.args.issue;
|
|
@@ -3743,9 +3876,9 @@ var postClassification = async (ctx) => {
|
|
|
3743
3876
|
ctx.cwd
|
|
3744
3877
|
);
|
|
3745
3878
|
try {
|
|
3746
|
-
|
|
3879
|
+
execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${classification}`], {
|
|
3747
3880
|
cwd: ctx.cwd,
|
|
3748
|
-
timeout:
|
|
3881
|
+
timeout: API_TIMEOUT_MS7,
|
|
3749
3882
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3750
3883
|
});
|
|
3751
3884
|
} catch (err) {
|
|
@@ -3777,9 +3910,9 @@ function parseClassification(prSummary) {
|
|
|
3777
3910
|
}
|
|
3778
3911
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
3779
3912
|
try {
|
|
3780
|
-
|
|
3913
|
+
execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
3781
3914
|
cwd,
|
|
3782
|
-
timeout:
|
|
3915
|
+
timeout: API_TIMEOUT_MS7,
|
|
3783
3916
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3784
3917
|
});
|
|
3785
3918
|
} catch {
|
|
@@ -3961,7 +4094,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
3961
4094
|
};
|
|
3962
4095
|
|
|
3963
4096
|
// src/scripts/releaseFlow.ts
|
|
3964
|
-
import { execFileSync as
|
|
4097
|
+
import { execFileSync as execFileSync16, spawnSync } from "child_process";
|
|
3965
4098
|
import * as fs18 from "fs";
|
|
3966
4099
|
import * as path16 from "path";
|
|
3967
4100
|
function bumpVersion(current, bump) {
|
|
@@ -3991,7 +4124,7 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
3991
4124
|
const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
|
|
3992
4125
|
let log = "";
|
|
3993
4126
|
try {
|
|
3994
|
-
log =
|
|
4127
|
+
log = execFileSync16("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
|
|
3995
4128
|
cwd,
|
|
3996
4129
|
encoding: "utf-8",
|
|
3997
4130
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4048,7 +4181,7 @@ ${entry}${prior.slice(idx + 1)}`);
|
|
|
4048
4181
|
}
|
|
4049
4182
|
}
|
|
4050
4183
|
function git3(args, cwd, timeout = 6e4) {
|
|
4051
|
-
return
|
|
4184
|
+
return execFileSync16("git", args, {
|
|
4052
4185
|
encoding: "utf-8",
|
|
4053
4186
|
timeout,
|
|
4054
4187
|
cwd,
|
|
@@ -4351,7 +4484,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
4351
4484
|
};
|
|
4352
4485
|
|
|
4353
4486
|
// src/scripts/resolveFlow.ts
|
|
4354
|
-
import { execFileSync as
|
|
4487
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
4355
4488
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
4356
4489
|
var resolveFlow = async (ctx) => {
|
|
4357
4490
|
const prNumber = ctx.args.pr;
|
|
@@ -4403,7 +4536,7 @@ var resolveFlow = async (ctx) => {
|
|
|
4403
4536
|
};
|
|
4404
4537
|
function getConflictedFiles(cwd) {
|
|
4405
4538
|
try {
|
|
4406
|
-
const out =
|
|
4539
|
+
const out = execFileSync17("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
4407
4540
|
encoding: "utf-8",
|
|
4408
4541
|
cwd,
|
|
4409
4542
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -4418,7 +4551,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
4418
4551
|
let total = 0;
|
|
4419
4552
|
for (const f of files) {
|
|
4420
4553
|
try {
|
|
4421
|
-
const content =
|
|
4554
|
+
const content = execFileSync17("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
4422
4555
|
const snippet = `### ${f}
|
|
4423
4556
|
|
|
4424
4557
|
\`\`\`
|
|
@@ -4487,7 +4620,7 @@ function tryPostPr4(prNumber, body, cwd) {
|
|
|
4487
4620
|
}
|
|
4488
4621
|
|
|
4489
4622
|
// src/scripts/riskGate.ts
|
|
4490
|
-
import { execFileSync as
|
|
4623
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
4491
4624
|
var ALL_GATES = ["secrets", "workflow-edit", "large-diff", "dep-change", "test-deletion"];
|
|
4492
4625
|
var HARD_GATES = /* @__PURE__ */ new Set(["secrets"]);
|
|
4493
4626
|
var APPROVE_ALL = "kody-approve:all";
|
|
@@ -4504,7 +4637,7 @@ var riskGate = async (ctx, profile, _agent, args) => {
|
|
|
4504
4637
|
}
|
|
4505
4638
|
const targetType = ctx.data.commentTargetType;
|
|
4506
4639
|
const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
|
|
4507
|
-
const labels =
|
|
4640
|
+
const labels = collectApprovalLabels(ctx, targetType, targetNumber);
|
|
4508
4641
|
const approveAll = labels.includes(APPROVE_ALL);
|
|
4509
4642
|
const pending = violations.filter((v) => !isApproved(v, labels, approveAll));
|
|
4510
4643
|
ctx.data.riskGate = {
|
|
@@ -4528,7 +4661,8 @@ var riskGate = async (ctx, profile, _agent, args) => {
|
|
|
4528
4661
|
);
|
|
4529
4662
|
} catch {
|
|
4530
4663
|
}
|
|
4531
|
-
const
|
|
4664
|
+
const compareUrl = computeCompareUrl(ctx);
|
|
4665
|
+
const body = formatAdvisory(pending, compareUrl);
|
|
4532
4666
|
try {
|
|
4533
4667
|
if (targetType === "issue") postIssueComment(targetNumber, body, ctx.cwd);
|
|
4534
4668
|
else postPrReviewComment(targetNumber, body, ctx.cwd);
|
|
@@ -4602,6 +4736,20 @@ function evaluateGates(ctx, profileName, changedFiles, gatesToRun, args) {
|
|
|
4602
4736
|
}
|
|
4603
4737
|
return violations;
|
|
4604
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
|
+
}
|
|
4605
4753
|
function isApproved(v, labels, approveAll) {
|
|
4606
4754
|
if (labels.includes(`kody-approve:${v.name}`)) return true;
|
|
4607
4755
|
if (!HARD_GATES.has(v.name) && approveAll) return true;
|
|
@@ -4661,7 +4809,7 @@ function collectBranchChangedFiles(ctx) {
|
|
|
4661
4809
|
const base = ctx.config.git.defaultBranch;
|
|
4662
4810
|
for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
|
|
4663
4811
|
try {
|
|
4664
|
-
const out =
|
|
4812
|
+
const out = execFileSync18("git", ["diff", "--name-only", ref], {
|
|
4665
4813
|
cwd: ctx.cwd,
|
|
4666
4814
|
encoding: "utf-8",
|
|
4667
4815
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4677,7 +4825,7 @@ function computeDiffStats(ctx) {
|
|
|
4677
4825
|
const base = ctx.config.git.defaultBranch;
|
|
4678
4826
|
for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
|
|
4679
4827
|
try {
|
|
4680
|
-
const out =
|
|
4828
|
+
const out = execFileSync18("git", ["diff", "--shortstat", ref], {
|
|
4681
4829
|
cwd: ctx.cwd,
|
|
4682
4830
|
encoding: "utf-8",
|
|
4683
4831
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4698,7 +4846,7 @@ function parseShortstat(s) {
|
|
|
4698
4846
|
}
|
|
4699
4847
|
function listDeletedFilesInHeadCommit(cwd) {
|
|
4700
4848
|
try {
|
|
4701
|
-
const out =
|
|
4849
|
+
const out = execFileSync18("git", ["show", "--name-status", "--pretty=format:", "HEAD"], {
|
|
4702
4850
|
cwd,
|
|
4703
4851
|
encoding: "utf-8",
|
|
4704
4852
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4726,28 +4874,38 @@ function ensureApproveLabel(gate, cwd) {
|
|
|
4726
4874
|
} catch {
|
|
4727
4875
|
}
|
|
4728
4876
|
}
|
|
4729
|
-
function formatAdvisory(pending) {
|
|
4877
|
+
function formatAdvisory(pending, compareUrl) {
|
|
4730
4878
|
const lines = [];
|
|
4731
4879
|
lines.push("\u23F8\uFE0F **kody2 risk gate halted the flow.**");
|
|
4732
4880
|
lines.push("");
|
|
4733
|
-
lines.push("The
|
|
4881
|
+
lines.push("The branch was pushed but **no PR was opened** \u2014 waiting for human approval:");
|
|
4734
4882
|
lines.push("");
|
|
4735
4883
|
for (const v of pending) {
|
|
4736
4884
|
lines.push(`- **\`${v.name}\`** _(${v.severity})_ \u2014 ${v.reason}`);
|
|
4737
4885
|
}
|
|
4738
4886
|
lines.push("");
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
if (pending.some((v) => v.severity === "soft")) {
|
|
4743
|
-
lines.push("- `kody-approve:all` \u2014 bypass **soft** gates at once (hard gates still require their specific label)");
|
|
4887
|
+
if (compareUrl) {
|
|
4888
|
+
lines.push(`\u{1F4CE} Review the branch diff: ${compareUrl}`);
|
|
4889
|
+
lines.push("");
|
|
4744
4890
|
}
|
|
4891
|
+
lines.push("**To approve and resume**, post a comment:");
|
|
4892
|
+
lines.push("");
|
|
4893
|
+
lines.push("> `@kody2 approve`");
|
|
4745
4894
|
lines.push("");
|
|
4746
4895
|
lines.push(
|
|
4747
|
-
"
|
|
4896
|
+
"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."
|
|
4748
4897
|
);
|
|
4749
4898
|
return lines.join("\n");
|
|
4750
4899
|
}
|
|
4900
|
+
function computeCompareUrl(ctx) {
|
|
4901
|
+
const branch = ctx.data.branch;
|
|
4902
|
+
if (!branch) return null;
|
|
4903
|
+
const owner = ctx.config.github?.owner;
|
|
4904
|
+
const repo = ctx.config.github?.repo;
|
|
4905
|
+
if (!owner || !repo) return null;
|
|
4906
|
+
const base = ctx.config.git.defaultBranch;
|
|
4907
|
+
return `https://github.com/${owner}/${repo}/compare/${base}...${branch}`;
|
|
4908
|
+
}
|
|
4751
4909
|
|
|
4752
4910
|
// src/scripts/runFlow.ts
|
|
4753
4911
|
var runFlow = async (ctx) => {
|
|
@@ -4790,7 +4948,7 @@ var saveTaskState = async (ctx, profile) => {
|
|
|
4790
4948
|
const action = ctx.data.action ?? synthesizeAction(ctx);
|
|
4791
4949
|
if (ctx.output.prUrl && !state.core.prUrl) state.core.prUrl = ctx.output.prUrl;
|
|
4792
4950
|
if (typeof ctx.data.runUrl === "string") state.core.runUrl = ctx.data.runUrl;
|
|
4793
|
-
const next = reduce(state, executable, action);
|
|
4951
|
+
const next = reduce(state, executable, action, profile.phase);
|
|
4794
4952
|
if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
|
|
4795
4953
|
if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
|
|
4796
4954
|
writeTaskState(target, number, next, ctx.cwd);
|
|
@@ -4845,8 +5003,8 @@ var skipAgent = async (ctx) => {
|
|
|
4845
5003
|
};
|
|
4846
5004
|
|
|
4847
5005
|
// src/scripts/startFlow.ts
|
|
4848
|
-
import { execFileSync as
|
|
4849
|
-
var
|
|
5006
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
5007
|
+
var API_TIMEOUT_MS8 = 3e4;
|
|
4850
5008
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
4851
5009
|
const entry = args?.entry;
|
|
4852
5010
|
if (!entry) {
|
|
@@ -4879,8 +5037,8 @@ function postKody2Comment(target, issueNumber, state, next, cwd) {
|
|
|
4879
5037
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
4880
5038
|
const body = `@kody2 ${next}`;
|
|
4881
5039
|
try {
|
|
4882
|
-
|
|
4883
|
-
timeout:
|
|
5040
|
+
execFileSync19("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
5041
|
+
timeout: API_TIMEOUT_MS8,
|
|
4884
5042
|
cwd,
|
|
4885
5043
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4886
5044
|
});
|
|
@@ -4899,7 +5057,7 @@ function parsePr2(url) {
|
|
|
4899
5057
|
}
|
|
4900
5058
|
|
|
4901
5059
|
// src/scripts/syncFlow.ts
|
|
4902
|
-
import { execFileSync as
|
|
5060
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
4903
5061
|
var syncFlow = async (ctx) => {
|
|
4904
5062
|
ctx.skipAgent = true;
|
|
4905
5063
|
const prNumber = ctx.args.pr;
|
|
@@ -4958,7 +5116,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
4958
5116
|
}
|
|
4959
5117
|
function revParseHead(cwd) {
|
|
4960
5118
|
try {
|
|
4961
|
-
return
|
|
5119
|
+
return execFileSync20("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
4962
5120
|
} catch {
|
|
4963
5121
|
return "";
|
|
4964
5122
|
}
|
|
@@ -4966,9 +5124,9 @@ function revParseHead(cwd) {
|
|
|
4966
5124
|
function pushBranch(branch, cwd) {
|
|
4967
5125
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
4968
5126
|
try {
|
|
4969
|
-
|
|
5127
|
+
execFileSync20("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
4970
5128
|
} catch {
|
|
4971
|
-
|
|
5129
|
+
execFileSync20("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
4972
5130
|
cwd,
|
|
4973
5131
|
env,
|
|
4974
5132
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5211,7 +5369,8 @@ var postflightScripts = {
|
|
|
5211
5369
|
advanceFlow,
|
|
5212
5370
|
persistFlowState,
|
|
5213
5371
|
postClassification,
|
|
5214
|
-
riskGate
|
|
5372
|
+
riskGate,
|
|
5373
|
+
applyApprovals
|
|
5215
5374
|
};
|
|
5216
5375
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
5217
5376
|
...Object.keys(preflightScripts),
|
|
@@ -5219,7 +5378,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
5219
5378
|
]);
|
|
5220
5379
|
|
|
5221
5380
|
// src/tools.ts
|
|
5222
|
-
import { execFileSync as
|
|
5381
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
5223
5382
|
function verifyCliTools(tools, cwd) {
|
|
5224
5383
|
const out = [];
|
|
5225
5384
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -5252,7 +5411,7 @@ function verifyOne(tool, cwd) {
|
|
|
5252
5411
|
}
|
|
5253
5412
|
function runShell2(cmd, cwd, timeoutMs = 3e4) {
|
|
5254
5413
|
try {
|
|
5255
|
-
|
|
5414
|
+
execFileSync21("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
5256
5415
|
return true;
|
|
5257
5416
|
} catch {
|
|
5258
5417
|
return false;
|
|
@@ -5582,7 +5741,7 @@ function detectPackageManager2(cwd) {
|
|
|
5582
5741
|
}
|
|
5583
5742
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
5584
5743
|
try {
|
|
5585
|
-
|
|
5744
|
+
execFileSync22(cmd, args, {
|
|
5586
5745
|
cwd,
|
|
5587
5746
|
stdio: stream ? "inherit" : "pipe",
|
|
5588
5747
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -5595,7 +5754,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
5595
5754
|
}
|
|
5596
5755
|
function isOnPath(bin) {
|
|
5597
5756
|
try {
|
|
5598
|
-
|
|
5757
|
+
execFileSync22("which", [bin], { stdio: "pipe" });
|
|
5599
5758
|
return true;
|
|
5600
5759
|
} catch {
|
|
5601
5760
|
return false;
|
|
@@ -5629,7 +5788,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5629
5788
|
} catch {
|
|
5630
5789
|
}
|
|
5631
5790
|
try {
|
|
5632
|
-
|
|
5791
|
+
execFileSync22("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
5633
5792
|
process.stdout.write("\u2192 kody2: litellm already installed\n");
|
|
5634
5793
|
return 0;
|
|
5635
5794
|
} catch {
|
|
@@ -5639,16 +5798,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5639
5798
|
}
|
|
5640
5799
|
function configureGitIdentity(cwd) {
|
|
5641
5800
|
try {
|
|
5642
|
-
const name =
|
|
5801
|
+
const name = execFileSync22("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
5643
5802
|
if (name) return;
|
|
5644
5803
|
} catch {
|
|
5645
5804
|
}
|
|
5646
5805
|
try {
|
|
5647
|
-
|
|
5806
|
+
execFileSync22("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
5648
5807
|
} catch {
|
|
5649
5808
|
}
|
|
5650
5809
|
try {
|
|
5651
|
-
|
|
5810
|
+
execFileSync22("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
5652
5811
|
cwd,
|
|
5653
5812
|
stdio: "pipe"
|
|
5654
5813
|
});
|
|
@@ -5825,9 +5984,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
5825
5984
|
if (paths.length === 0) return;
|
|
5826
5985
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
5827
5986
|
try {
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5987
|
+
execFileSync23("git", ["add", ...paths], opts);
|
|
5988
|
+
execFileSync23("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
5989
|
+
execFileSync23("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
5831
5990
|
} catch (err) {
|
|
5832
5991
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5833
5992
|
process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(approve is an agent-less utility; this file is a placeholder kept to satisfy the profile layout convention.)
|
|
@@ -66,40 +66,17 @@
|
|
|
66
66
|
}
|
|
67
67
|
],
|
|
68
68
|
"postflight": [
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
"script": "commitAndPush"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"script": "ensurePr"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"script": "postIssueComment"
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"script": "writeRunSummary"
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"script": "saveTaskState"
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
"script": "riskGate"
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
"script": "advanceFlow",
|
|
101
|
-
"runWhen": { "data.riskGate.decision": "allow" }
|
|
102
|
-
}
|
|
69
|
+
{ "script": "parseAgentResult" },
|
|
70
|
+
{ "script": "requireFeedbackActions" },
|
|
71
|
+
{ "script": "verify" },
|
|
72
|
+
{ "script": "checkCoverageWithRetry" },
|
|
73
|
+
{ "script": "commitAndPush" },
|
|
74
|
+
{ "script": "riskGate" },
|
|
75
|
+
{ "script": "ensurePr", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
76
|
+
{ "script": "postIssueComment", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
77
|
+
{ "script": "writeRunSummary" },
|
|
78
|
+
{ "script": "saveTaskState", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
79
|
+
{ "script": "advanceFlow", "runWhen": { "data.riskGate.decision": "allow" } }
|
|
103
80
|
]
|
|
104
81
|
},
|
|
105
82
|
"output": {
|
|
@@ -62,43 +62,18 @@
|
|
|
62
62
|
}
|
|
63
63
|
],
|
|
64
64
|
"postflight": [
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
{
|
|
78
|
-
"script": "commitAndPush"
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"script": "ensurePr"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"script": "postIssueComment"
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
"script": "writeRunSummary"
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
"script": "saveTaskState"
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
"script": "mirrorStateToPr"
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"script": "riskGate"
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
"script": "advanceFlow",
|
|
100
|
-
"runWhen": { "data.riskGate.decision": "allow" }
|
|
101
|
-
}
|
|
65
|
+
{ "script": "parseAgentResult" },
|
|
66
|
+
{ "script": "requirePlanDeviations" },
|
|
67
|
+
{ "script": "verify" },
|
|
68
|
+
{ "script": "checkCoverageWithRetry" },
|
|
69
|
+
{ "script": "commitAndPush" },
|
|
70
|
+
{ "script": "riskGate" },
|
|
71
|
+
{ "script": "ensurePr", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
72
|
+
{ "script": "postIssueComment", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
73
|
+
{ "script": "writeRunSummary" },
|
|
74
|
+
{ "script": "saveTaskState", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
75
|
+
{ "script": "mirrorStateToPr", "runWhen": { "data.riskGate.decision": "allow" } },
|
|
76
|
+
{ "script": "advanceFlow", "runWhen": { "data.riskGate.decision": "allow" } }
|
|
102
77
|
]
|
|
103
78
|
},
|
|
104
79
|
"input": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { AgentResult } from "../agent.js"
|
|
11
11
|
import type { Kody2Config } from "../config.js"
|
|
12
|
+
import type { Phase } from "../state.js"
|
|
12
13
|
|
|
13
14
|
// ────────────────────────────────────────────────────────────────────────────
|
|
14
15
|
// Profile shape (mirrors the JSON on disk).
|
|
@@ -37,6 +38,12 @@ export interface Profile {
|
|
|
37
38
|
kind: "oneshot" | "scheduled"
|
|
38
39
|
/** Cron expression for scheduled profiles (e.g. "0 8 * * MON"). */
|
|
39
40
|
schedule?: string
|
|
41
|
+
/**
|
|
42
|
+
* Task-state phase label emitted when this executable completes successfully.
|
|
43
|
+
* Failing actions always set phase to "failed" regardless. Omitted → "idle".
|
|
44
|
+
* Lets state.ts stay generic — phase semantics live on the profile.
|
|
45
|
+
*/
|
|
46
|
+
phase?: Phase
|
|
40
47
|
inputs: InputSpec[]
|
|
41
48
|
claudeCode: ClaudeCodeSpec
|
|
42
49
|
cliTools: CliToolSpec[]
|
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.59",
|
|
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
|
@@ -14,10 +14,14 @@
|
|
|
14
14
|
# toJSON(secrets) — no need to list them here.
|
|
15
15
|
#
|
|
16
16
|
# Recommended: KODY_TOKEN secret — a fine-grained PAT or GitHub App token
|
|
17
|
-
# with `repo` + `read:org` scopes. Without it, kody2's
|
|
18
|
-
# still work via github.token, but
|
|
17
|
+
# with `repo` + `read:org` + `workflow` scopes. Without it, kody2's
|
|
18
|
+
# commits/PR-creation still work via github.token, but three things degrade:
|
|
19
19
|
# 1. PR body updates fail with "token lacks read:org scope" (cosmetic).
|
|
20
20
|
# 2. Pushes from kody2 won't trigger downstream workflows.
|
|
21
|
+
# 3. Any commit that modifies `.github/workflows/*` is REJECTED by
|
|
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.)
|
|
21
25
|
# Set KODY_TOKEN in repo Settings → Secrets → Actions.
|
|
22
26
|
|
|
23
27
|
name: kody2
|