@kody-ade/kody-engine 0.4.32 → 0.4.34

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/kody.js +106 -4
  2. package/package.json +1 -1
package/dist/bin/kody.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.4.32",
6
+ version: "0.4.34",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -5169,6 +5169,30 @@ var ensureLifecycleLabels = async (ctx) => {
5169
5169
  };
5170
5170
 
5171
5171
  // src/pr.ts
5172
+ function prMergeStatus(prNumber, cwd) {
5173
+ try {
5174
+ const out = gh(
5175
+ ["pr", "view", String(prNumber), "--json", "mergeable,mergeStateStatus"],
5176
+ { cwd }
5177
+ );
5178
+ const parsed = JSON.parse(out);
5179
+ const mergeable = parsed.mergeable ?? "UNKNOWN";
5180
+ const mergeStateStatus = parsed.mergeStateStatus ?? "UNKNOWN";
5181
+ return { status: classifyMergeStatus(mergeable, mergeStateStatus), mergeable, mergeStateStatus };
5182
+ } catch {
5183
+ return { status: "ERROR", mergeable: "", mergeStateStatus: "" };
5184
+ }
5185
+ }
5186
+ function classifyMergeStatus(mergeable, mergeStateStatus) {
5187
+ if (mergeable === "CONFLICTING") return "CONFLICTING";
5188
+ if (mergeable === "UNKNOWN") return "UNKNOWN";
5189
+ if (mergeable === "MERGEABLE") {
5190
+ if (mergeStateStatus === "CLEAN") return "MERGEABLE";
5191
+ if (mergeStateStatus === "DIRTY") return "CONFLICTING";
5192
+ return "BLOCKED";
5193
+ }
5194
+ return "UNKNOWN";
5195
+ }
5172
5196
  var TITLE_MAX = 72;
5173
5197
  function stripTitlePrefixes(raw) {
5174
5198
  let s = raw.trim();
@@ -7621,12 +7645,35 @@ var resolveFlow = async (ctx) => {
7621
7645
  ctx.data.pr = pr;
7622
7646
  ctx.data.commentTargetType = "pr";
7623
7647
  ctx.data.commentTargetNumber = prNumber;
7624
- checkoutPrBranch(prNumber, ctx.cwd);
7625
- ctx.data.branch = getCurrentBranch(ctx.cwd);
7626
7648
  const baseBranch = pr.baseRefName || ctx.config.git.defaultBranch;
7627
7649
  ctx.data.baseBranch = baseBranch;
7650
+ const ghStatus = prMergeStatus(prNumber, ctx.cwd);
7651
+ if (ghStatus.status === "MERGEABLE") {
7652
+ ctx.output.exitCode = 0;
7653
+ ctx.output.reason = `PR #${prNumber} is mergeable (no conflicts) \u2014 nothing to resolve`;
7654
+ ctx.skipAgent = true;
7655
+ tryPostPr3(prNumber, `\u2139\uFE0F kody resolve: ${ctx.output.reason}`, ctx.cwd);
7656
+ return;
7657
+ }
7658
+ if (ghStatus.status === "BLOCKED") {
7659
+ ctx.output.exitCode = 0;
7660
+ ctx.output.reason = `PR #${prNumber} is mergeable but blocked by checks/reviews (mergeStateStatus=${ghStatus.mergeStateStatus}) \u2014 nothing for resolve to do`;
7661
+ ctx.skipAgent = true;
7662
+ tryPostPr3(prNumber, `\u2139\uFE0F kody resolve: ${ctx.output.reason}`, ctx.cwd);
7663
+ return;
7664
+ }
7665
+ checkoutPrBranch(prNumber, ctx.cwd);
7666
+ ctx.data.branch = getCurrentBranch(ctx.cwd);
7628
7667
  const mergeStatus = mergeBase(baseBranch, ctx.cwd);
7629
7668
  if (mergeStatus === "clean") {
7669
+ if (ghStatus.status === "CONFLICTING") {
7670
+ const pushed = pushEmptyCommit(ctx.data.branch, ctx.cwd);
7671
+ ctx.output.exitCode = 0;
7672
+ ctx.output.reason = pushed ? `local merge clean despite GitHub reporting CONFLICTING \u2014 pushed empty commit to force re-evaluation` : `local merge clean despite GitHub reporting CONFLICTING \u2014 couldn't refresh GitHub cache (push failed)`;
7673
+ ctx.skipAgent = true;
7674
+ tryPostPr3(prNumber, `\u2139\uFE0F kody resolve: ${ctx.output.reason}`, ctx.cwd);
7675
+ return;
7676
+ }
7630
7677
  ctx.output.exitCode = 0;
7631
7678
  ctx.output.reason = `already up to date with origin/${baseBranch} \u2014 nothing to resolve`;
7632
7679
  ctx.skipAgent = true;
@@ -7713,6 +7760,24 @@ function tryPostPr3(prNumber, body, cwd) {
7713
7760
  } catch {
7714
7761
  }
7715
7762
  }
7763
+ function pushEmptyCommit(branch, cwd) {
7764
+ const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
7765
+ try {
7766
+ execFileSync22(
7767
+ "git",
7768
+ ["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
7769
+ { cwd, env, stdio: ["ignore", "pipe", "pipe"] }
7770
+ );
7771
+ execFileSync22("git", ["push", "-u", "origin", branch], {
7772
+ cwd,
7773
+ env,
7774
+ stdio: ["ignore", "pipe", "pipe"]
7775
+ });
7776
+ return true;
7777
+ } catch {
7778
+ return false;
7779
+ }
7780
+ }
7716
7781
 
7717
7782
  // src/deployments.ts
7718
7783
  function findPreviewDeploymentUrl(prNumber, cwd) {
@@ -8449,6 +8514,7 @@ function tryPostPr6(prNumber, body, cwd) {
8449
8514
  import { spawn as spawn2 } from "child_process";
8450
8515
  var TAIL_CHARS = 4e3;
8451
8516
  var COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
8517
+ var DEFAULT_TEST_RETRIES = 2;
8452
8518
  function runCommand(command, cwd) {
8453
8519
  return new Promise((resolve4) => {
8454
8520
  const start = Date.now();
@@ -8502,6 +8568,28 @@ async function verifyAll(config, cwd) {
8502
8568
  }
8503
8569
  return { ok: failed.length === 0, failed, details };
8504
8570
  }
8571
+ async function applyTestRetries(initial, testCommand, cwd, runner, testRetries = DEFAULT_TEST_RETRIES) {
8572
+ if (initial.ok) return { ...initial, recovered: [] };
8573
+ const recovered = [];
8574
+ const details = { ...initial.details };
8575
+ let failed = [...initial.failed];
8576
+ if (failed.includes("test") && testCommand && testRetries > 0) {
8577
+ for (let attempt = 1; attempt <= testRetries; attempt++) {
8578
+ const retry = await runner(testCommand, cwd);
8579
+ details[`test (retry ${attempt})`] = retry;
8580
+ if (retry.exitCode === 0) {
8581
+ failed = failed.filter((f) => f !== "test");
8582
+ recovered.push("test");
8583
+ break;
8584
+ }
8585
+ }
8586
+ }
8587
+ return { ok: failed.length === 0, failed, details, recovered };
8588
+ }
8589
+ async function verifyAllWithRetry(config, cwd, opts) {
8590
+ const initial = await verifyAll(config, cwd);
8591
+ return applyTestRetries(initial, config.quality.testUnit, cwd, runCommand, opts?.testRetries);
8592
+ }
8505
8593
  var ANSI_RE = /\x1B\[[0-?]*[ -/]*[@-~]/g;
8506
8594
  function stripAnsi(s) {
8507
8595
  return s.replace(ANSI_RE, "");
@@ -8514,6 +8602,13 @@ function summarizeFailure(result) {
8514
8602
  lines.push(`
8515
8603
  --- ${name} (exit ${d.exitCode}, ${(d.durationMs / 1e3).toFixed(1)}s) ---`);
8516
8604
  lines.push(stripAnsi(d.tail));
8605
+ for (let attempt = 1; ; attempt++) {
8606
+ const retry = result.details[`${name} (retry ${attempt})`];
8607
+ if (!retry) break;
8608
+ lines.push(`
8609
+ --- ${name} (retry ${attempt}: exit ${retry.exitCode}, ${(retry.durationMs / 1e3).toFixed(1)}s) ---`);
8610
+ lines.push(stripAnsi(retry.tail));
8611
+ }
8517
8612
  }
8518
8613
  return lines.join("\n");
8519
8614
  }
@@ -8521,9 +8616,16 @@ function summarizeFailure(result) {
8521
8616
  // src/scripts/verify.ts
8522
8617
  var verify = async (ctx) => {
8523
8618
  try {
8524
- const result = await verifyAll(ctx.config, ctx.cwd);
8619
+ const result = await verifyAllWithRetry(ctx.config, ctx.cwd);
8525
8620
  ctx.data.verifyOk = result.ok;
8526
8621
  ctx.data.verifyReason = result.ok ? "" : summarizeFailure(result);
8622
+ ctx.data.verifyRecovered = result.recovered ?? [];
8623
+ if (result.recovered && result.recovered.length > 0) {
8624
+ process.stderr.write(
8625
+ `[kody verify] caught flake on: ${result.recovered.join(", ")} (passed on retry)
8626
+ `
8627
+ );
8628
+ }
8527
8629
  } catch (err) {
8528
8630
  ctx.data.verifyOk = false;
8529
8631
  ctx.data.verifyReason = `verify crashed: ${err instanceof Error ? err.message : String(err)}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.32",
3
+ "version": "0.4.34",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",