@kody-ade/kody-engine 0.2.60 → 0.2.61
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 +71 -134
- package/package.json +1 -1
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.61",
|
|
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",
|
|
@@ -1381,14 +1381,6 @@ function postPrReviewComment(prNumber, body, cwd) {
|
|
|
1381
1381
|
|
|
1382
1382
|
// src/scripts/applyApprovals.ts
|
|
1383
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
1384
|
var applyApprovals = async (ctx) => {
|
|
1393
1385
|
const issueArg = typeof ctx.args.issue === "number" ? ctx.args.issue : null;
|
|
1394
1386
|
const prArg = typeof ctx.args.pr === "number" ? ctx.args.pr : null;
|
|
@@ -1405,28 +1397,13 @@ var applyApprovals = async (ctx) => {
|
|
|
1405
1397
|
state = null;
|
|
1406
1398
|
}
|
|
1407
1399
|
const issueNumber = currentTarget.type === "issue" ? currentTarget.number : state?.flow?.issueNumber ?? null;
|
|
1408
|
-
const
|
|
1409
|
-
const
|
|
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);
|
|
1400
|
+
const flowName = state?.flow?.name ?? null;
|
|
1401
|
+
const confirmation = formatConfirmation(currentTarget, flowName, issueNumber);
|
|
1424
1402
|
try {
|
|
1425
1403
|
if (currentTarget.type === "issue") postIssueComment(currentTarget.number, confirmation, ctx.cwd);
|
|
1426
1404
|
else postPrReviewComment(currentTarget.number, confirmation, ctx.cwd);
|
|
1427
1405
|
} catch {
|
|
1428
1406
|
}
|
|
1429
|
-
const flowName = state?.flow?.name;
|
|
1430
1407
|
if (issueNumber && typeof flowName === "string" && flowName.length > 0) {
|
|
1431
1408
|
try {
|
|
1432
1409
|
execFileSync5("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${flowName}`], {
|
|
@@ -1443,56 +1420,20 @@ var applyApprovals = async (ctx) => {
|
|
|
1443
1420
|
}
|
|
1444
1421
|
ctx.output.exitCode = 0;
|
|
1445
1422
|
};
|
|
1446
|
-
function
|
|
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);
|
|
1423
|
+
function formatConfirmation(current, flowName, issueNumber) {
|
|
1481
1424
|
const lines = [];
|
|
1482
1425
|
lines.push("\u2705 **kody2 risk gates approved.**");
|
|
1483
1426
|
lines.push("");
|
|
1484
|
-
lines.push(
|
|
1485
|
-
|
|
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) {
|
|
1427
|
+
lines.push("The approval is recorded as this comment \u2014 kody2 reads it directly on the next run.");
|
|
1428
|
+
if (flowName && issueNumber) {
|
|
1494
1429
|
lines.push("");
|
|
1495
|
-
|
|
1430
|
+
if (current.type === "pr") {
|
|
1431
|
+
lines.push(
|
|
1432
|
+
`Re-triggering the \`${flowName}\` flow on the originating issue (#${issueNumber}) \u2014 it will resume from the existing branch/PR checkpoint.`
|
|
1433
|
+
);
|
|
1434
|
+
} else {
|
|
1435
|
+
lines.push(`Re-triggering the \`${flowName}\` flow now \u2014 it will resume from the existing branch checkpoint.`);
|
|
1436
|
+
}
|
|
1496
1437
|
} else {
|
|
1497
1438
|
lines.push("");
|
|
1498
1439
|
lines.push("No active flow found in task state. Post `@kody2 <command>` to resume manually.");
|
|
@@ -2949,7 +2890,7 @@ function getIssueLabels(issueNumber, cwd) {
|
|
|
2949
2890
|
return [];
|
|
2950
2891
|
}
|
|
2951
2892
|
}
|
|
2952
|
-
function
|
|
2893
|
+
function addLabel(issueNumber, label, cwd) {
|
|
2953
2894
|
gh2(["issue", "edit", String(issueNumber), "--add-label", label], { cwd });
|
|
2954
2895
|
}
|
|
2955
2896
|
function removeLabel(issueNumber, label, cwd) {
|
|
@@ -2981,12 +2922,12 @@ function setKodyLabel(issueNumber, spec, cwd) {
|
|
|
2981
2922
|
}
|
|
2982
2923
|
}
|
|
2983
2924
|
try {
|
|
2984
|
-
|
|
2925
|
+
addLabel(issueNumber, target, cwd);
|
|
2985
2926
|
} catch (err) {
|
|
2986
2927
|
if (looksLikeMissingLabel(err)) {
|
|
2987
2928
|
try {
|
|
2988
2929
|
createLabelInRepo(spec, cwd);
|
|
2989
|
-
|
|
2930
|
+
addLabel(issueNumber, target, cwd);
|
|
2990
2931
|
return;
|
|
2991
2932
|
} catch (retryErr) {
|
|
2992
2933
|
process.stderr.write(
|
|
@@ -3743,7 +3684,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
3743
3684
|
if (!issueNumber || issueTarget !== "issue") return;
|
|
3744
3685
|
const prUrl = ctx.output.prUrl ?? ctx.data.prResult?.url;
|
|
3745
3686
|
if (!prUrl) return;
|
|
3746
|
-
const prNumber =
|
|
3687
|
+
const prNumber = parsePrNumber(prUrl);
|
|
3747
3688
|
if (!prNumber) return;
|
|
3748
3689
|
let state;
|
|
3749
3690
|
try {
|
|
@@ -3761,7 +3702,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
3761
3702
|
);
|
|
3762
3703
|
}
|
|
3763
3704
|
};
|
|
3764
|
-
function
|
|
3705
|
+
function parsePrNumber(prUrl) {
|
|
3765
3706
|
const m = prUrl.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
3766
3707
|
if (!m) return null;
|
|
3767
3708
|
const n = parseInt(m[1], 10);
|
|
@@ -4622,9 +4563,9 @@ function tryPostPr4(prNumber, body, cwd) {
|
|
|
4622
4563
|
// src/scripts/riskGate.ts
|
|
4623
4564
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
4624
4565
|
var ALL_GATES = ["secrets", "workflow-edit", "large-diff", "dep-change", "test-deletion"];
|
|
4625
|
-
var
|
|
4626
|
-
var
|
|
4627
|
-
var
|
|
4566
|
+
var WAITING_LABEL = "kody:waiting";
|
|
4567
|
+
var ADVISORY_MARKER = "kody2 risk gate halted the flow";
|
|
4568
|
+
var APPROVE_COMMAND = /(^|\s)@kody2\s+approve\b/i;
|
|
4628
4569
|
var DEFAULT_MAX_FILES = 20;
|
|
4629
4570
|
var DEFAULT_MAX_DELETIONS = 500;
|
|
4630
4571
|
var riskGate = async (ctx, profile, _agent, args) => {
|
|
@@ -4637,23 +4578,18 @@ var riskGate = async (ctx, profile, _agent, args) => {
|
|
|
4637
4578
|
}
|
|
4638
4579
|
const targetType = ctx.data.commentTargetType;
|
|
4639
4580
|
const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
|
|
4640
|
-
const
|
|
4641
|
-
const approveAll = labels.includes(APPROVE_ALL);
|
|
4642
|
-
const pending = violations.filter((v) => !isApproved(v, labels, approveAll));
|
|
4581
|
+
const approved = hasApproval(ctx, targetType, targetNumber);
|
|
4643
4582
|
ctx.data.riskGate = {
|
|
4644
4583
|
violations,
|
|
4645
|
-
pending,
|
|
4646
|
-
decision:
|
|
4584
|
+
pending: approved ? [] : violations,
|
|
4585
|
+
decision: approved ? "allow" : "halt"
|
|
4647
4586
|
};
|
|
4648
|
-
if (
|
|
4649
|
-
for (const v of pending) {
|
|
4650
|
-
ensureApproveLabel(v.name, ctx.cwd);
|
|
4651
|
-
}
|
|
4587
|
+
if (approved || !targetType || targetNumber <= 0) return;
|
|
4652
4588
|
try {
|
|
4653
4589
|
setKodyLabel(
|
|
4654
4590
|
targetNumber,
|
|
4655
4591
|
{
|
|
4656
|
-
label:
|
|
4592
|
+
label: WAITING_LABEL,
|
|
4657
4593
|
color: "fbca04",
|
|
4658
4594
|
description: "kody2: awaiting human approval of risk gate(s)"
|
|
4659
4595
|
},
|
|
@@ -4662,16 +4598,54 @@ var riskGate = async (ctx, profile, _agent, args) => {
|
|
|
4662
4598
|
} catch {
|
|
4663
4599
|
}
|
|
4664
4600
|
const compareUrl = computeCompareUrl(ctx);
|
|
4665
|
-
const body = formatAdvisory(
|
|
4601
|
+
const body = formatAdvisory(violations, compareUrl);
|
|
4666
4602
|
try {
|
|
4667
4603
|
if (targetType === "issue") postIssueComment(targetNumber, body, ctx.cwd);
|
|
4668
4604
|
else postPrReviewComment(targetNumber, body, ctx.cwd);
|
|
4669
4605
|
} catch {
|
|
4670
4606
|
}
|
|
4671
4607
|
if (!ctx.output.reason) {
|
|
4672
|
-
ctx.output.reason = `risk gate halt: ${
|
|
4608
|
+
ctx.output.reason = `risk gate halt: ${violations.map((p) => p.name).join(", ")}`;
|
|
4673
4609
|
}
|
|
4674
4610
|
};
|
|
4611
|
+
function hasApproval(ctx, targetType, targetNumber) {
|
|
4612
|
+
const surfaces = [];
|
|
4613
|
+
if (targetNumber > 0) surfaces.push(targetNumber);
|
|
4614
|
+
if (targetType === "pr") {
|
|
4615
|
+
const state = ctx.data.taskState;
|
|
4616
|
+
const issueNum = state?.flow?.issueNumber;
|
|
4617
|
+
if (typeof issueNum === "number" && issueNum > 0 && !surfaces.includes(issueNum)) {
|
|
4618
|
+
surfaces.push(issueNum);
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
for (const n of surfaces) {
|
|
4622
|
+
if (surfaceIsApproved(n, ctx.cwd)) return true;
|
|
4623
|
+
}
|
|
4624
|
+
return false;
|
|
4625
|
+
}
|
|
4626
|
+
function surfaceIsApproved(n, cwd) {
|
|
4627
|
+
let comments = [];
|
|
4628
|
+
try {
|
|
4629
|
+
comments = getIssue(n, cwd).comments;
|
|
4630
|
+
} catch {
|
|
4631
|
+
return false;
|
|
4632
|
+
}
|
|
4633
|
+
const advisoryAt = latestAdvisoryTimestamp(comments);
|
|
4634
|
+
for (const c of comments) {
|
|
4635
|
+
if (!APPROVE_COMMAND.test(c.body)) continue;
|
|
4636
|
+
if (advisoryAt !== null && c.createdAt <= advisoryAt) continue;
|
|
4637
|
+
return true;
|
|
4638
|
+
}
|
|
4639
|
+
return false;
|
|
4640
|
+
}
|
|
4641
|
+
function latestAdvisoryTimestamp(comments) {
|
|
4642
|
+
let latest = null;
|
|
4643
|
+
for (const c of comments) {
|
|
4644
|
+
if (!c.body.includes(ADVISORY_MARKER)) continue;
|
|
4645
|
+
if (latest === null || c.createdAt > latest) latest = c.createdAt;
|
|
4646
|
+
}
|
|
4647
|
+
return latest;
|
|
4648
|
+
}
|
|
4675
4649
|
function evaluateGates(ctx, profileName, changedFiles, gatesToRun, args) {
|
|
4676
4650
|
const violations = [];
|
|
4677
4651
|
if (gatesToRun.includes("secrets")) {
|
|
@@ -4736,25 +4710,6 @@ function evaluateGates(ctx, profileName, changedFiles, gatesToRun, args) {
|
|
|
4736
4710
|
}
|
|
4737
4711
|
return violations;
|
|
4738
4712
|
}
|
|
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
4713
|
function parseGates(spec) {
|
|
4759
4714
|
if (spec === void 0 || spec === null || spec === "") return ALL_GATES;
|
|
4760
4715
|
const list = String(spec).split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -4861,31 +4816,13 @@ function listDeletedFilesInHeadCommit(cwd) {
|
|
|
4861
4816
|
return [];
|
|
4862
4817
|
}
|
|
4863
4818
|
}
|
|
4864
|
-
function
|
|
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) {
|
|
4819
|
+
function formatAdvisory(violations, compareUrl) {
|
|
4883
4820
|
const lines = [];
|
|
4884
|
-
lines.push(
|
|
4821
|
+
lines.push(`\u23F8\uFE0F **${ADVISORY_MARKER}.**`);
|
|
4885
4822
|
lines.push("");
|
|
4886
4823
|
lines.push("The branch was pushed but **no PR was opened** \u2014 waiting for human approval:");
|
|
4887
4824
|
lines.push("");
|
|
4888
|
-
for (const v of
|
|
4825
|
+
for (const v of violations) {
|
|
4889
4826
|
lines.push(`- **\`${v.name}\`** _(${v.severity})_ \u2014 ${v.reason}`);
|
|
4890
4827
|
}
|
|
4891
4828
|
lines.push("");
|
|
@@ -4893,12 +4830,12 @@ function formatAdvisory(pending, compareUrl) {
|
|
|
4893
4830
|
lines.push(`\u{1F4CE} Review the branch diff: ${compareUrl}`);
|
|
4894
4831
|
lines.push("");
|
|
4895
4832
|
}
|
|
4896
|
-
lines.push("**To approve and resume**, post a comment:");
|
|
4833
|
+
lines.push("**To approve and resume**, post a comment on this issue or PR:");
|
|
4897
4834
|
lines.push("");
|
|
4898
4835
|
lines.push("> `@kody2 approve`");
|
|
4899
4836
|
lines.push("");
|
|
4900
4837
|
lines.push(
|
|
4901
|
-
"kody2 will
|
|
4838
|
+
"kody2 will open the PR and continue the flow from this checkpoint. No re-running the agent."
|
|
4902
4839
|
);
|
|
4903
4840
|
return lines.join("\n");
|
|
4904
4841
|
}
|
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.61",
|
|
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",
|