@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.
Files changed (2) hide show
  1. package/dist/bin/kody2.js +71 -134
  2. 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.60",
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 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);
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 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);
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
- `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) {
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
- lines.push(`Re-triggering the \`${flowName}\` flow now \u2014 it will resume from the existing branch/PR checkpoint.`);
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 addLabel2(issueNumber, label, cwd) {
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
- addLabel2(issueNumber, target, cwd);
2925
+ addLabel(issueNumber, target, cwd);
2985
2926
  } catch (err) {
2986
2927
  if (looksLikeMissingLabel(err)) {
2987
2928
  try {
2988
2929
  createLabelInRepo(spec, cwd);
2989
- addLabel2(issueNumber, target, cwd);
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 = parsePrNumber2(prUrl);
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 parsePrNumber2(prUrl) {
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 HARD_GATES = /* @__PURE__ */ new Set(["secrets"]);
4626
- var APPROVE_ALL = "kody-approve:all";
4627
- var GATED_LABEL = "kody:gated";
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 labels = collectApprovalLabels(ctx, targetType, targetNumber);
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: pending.length === 0 ? "allow" : "halt"
4584
+ pending: approved ? [] : violations,
4585
+ decision: approved ? "allow" : "halt"
4647
4586
  };
4648
- if (pending.length === 0 || !targetType || targetNumber <= 0) return;
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: GATED_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(pending, compareUrl);
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: ${pending.map((p) => p.name).join(", ")}`;
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 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) {
4819
+ function formatAdvisory(violations, compareUrl) {
4883
4820
  const lines = [];
4884
- lines.push("\u23F8\uFE0F **kody2 risk gate halted the flow.**");
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 pending) {
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 acknowledge all currently-pending gates (soft **and** hard), open the PR, and continue the flow from this checkpoint. No re-running the agent."
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.60",
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",