@kody-ade/kody-engine 0.3.31 → 0.3.32
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/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.3.
|
|
6
|
+
version: "0.3.32",
|
|
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",
|
|
@@ -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
|
|
|
@@ -577,7 +577,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
577
577
|
}
|
|
578
578
|
|
|
579
579
|
// src/kody-cli.ts
|
|
580
|
-
import { execFileSync as
|
|
580
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
581
581
|
import * as fs21 from "fs";
|
|
582
582
|
import * as path18 from "path";
|
|
583
583
|
|
|
@@ -5145,6 +5145,135 @@ var verify = async (ctx) => {
|
|
|
5145
5145
|
}
|
|
5146
5146
|
};
|
|
5147
5147
|
|
|
5148
|
+
// src/scripts/waitForCi.ts
|
|
5149
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
5150
|
+
var API_TIMEOUT_MS9 = 3e4;
|
|
5151
|
+
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
5152
|
+
const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
|
|
5153
|
+
const pollSeconds = numArg(args, "pollSeconds", 30);
|
|
5154
|
+
const initialWaitSeconds = numArg(args, "initialWaitSeconds", 15);
|
|
5155
|
+
const maxFixCiAttempts = numArg(args, "maxFixCiAttempts", 3);
|
|
5156
|
+
const state = ctx.data.taskState;
|
|
5157
|
+
const prUrl = state?.core.prUrl;
|
|
5158
|
+
const prNumber = prUrl ? parsePrNumber(prUrl) : null;
|
|
5159
|
+
if (!prNumber) {
|
|
5160
|
+
ctx.data.action = mkAction("CI_GIVEUP", { reason: "no PR url in state \u2014 nothing to wait for" });
|
|
5161
|
+
return;
|
|
5162
|
+
}
|
|
5163
|
+
const fixCiAttempts = state?.core.attempts?.["fix-ci"] ?? 0;
|
|
5164
|
+
await sleep(initialWaitSeconds * 1e3);
|
|
5165
|
+
const deadline = Date.now() + timeoutMinutes * 6e4;
|
|
5166
|
+
let lastSummary = "";
|
|
5167
|
+
while (Date.now() < deadline) {
|
|
5168
|
+
const rows = fetchChecks(prNumber, ctx.cwd);
|
|
5169
|
+
if (rows === null) {
|
|
5170
|
+
await sleep(pollSeconds * 1e3);
|
|
5171
|
+
continue;
|
|
5172
|
+
}
|
|
5173
|
+
if (rows.length === 0) {
|
|
5174
|
+
await sleep(pollSeconds * 1e3);
|
|
5175
|
+
continue;
|
|
5176
|
+
}
|
|
5177
|
+
const summary = summarize(rows);
|
|
5178
|
+
if (summary !== lastSummary) {
|
|
5179
|
+
lastSummary = summary;
|
|
5180
|
+
tryPostPr6(prNumber, `\u23F3 kody waitForCi: ${summary}`, ctx.cwd);
|
|
5181
|
+
}
|
|
5182
|
+
const failed = rows.filter((r) => r.bucket === "fail" || r.bucket === "cancel");
|
|
5183
|
+
const pending = rows.filter((r) => r.bucket === "pending");
|
|
5184
|
+
if (failed.length > 0) {
|
|
5185
|
+
const detail = failed.slice(0, 5).map((r) => `${r.workflow ?? "?"} / ${r.name ?? "?"}${r.link ? ` (${r.link})` : ""}`).join("; ");
|
|
5186
|
+
if (fixCiAttempts >= maxFixCiAttempts) {
|
|
5187
|
+
ctx.data.action = mkAction("CI_GIVEUP", {
|
|
5188
|
+
reason: `fix-ci attempts (${fixCiAttempts}) hit cap (${maxFixCiAttempts})`,
|
|
5189
|
+
failedChecks: detail,
|
|
5190
|
+
prUrl
|
|
5191
|
+
});
|
|
5192
|
+
tryPostPr6(
|
|
5193
|
+
prNumber,
|
|
5194
|
+
`\u{1F6D1} kody waitForCi: giving up after ${fixCiAttempts} fix-ci attempts. Failed: ${detail}`,
|
|
5195
|
+
ctx.cwd
|
|
5196
|
+
);
|
|
5197
|
+
} else {
|
|
5198
|
+
ctx.data.action = mkAction("CI_FAILED", {
|
|
5199
|
+
failedChecks: detail,
|
|
5200
|
+
attempt: fixCiAttempts + 1,
|
|
5201
|
+
maxAttempts: maxFixCiAttempts,
|
|
5202
|
+
prUrl
|
|
5203
|
+
});
|
|
5204
|
+
tryPostPr6(
|
|
5205
|
+
prNumber,
|
|
5206
|
+
`\u274C kody waitForCi: CI failed (attempt ${fixCiAttempts + 1}/${maxFixCiAttempts}). Dispatching fix-ci. Failed: ${detail}`,
|
|
5207
|
+
ctx.cwd
|
|
5208
|
+
);
|
|
5209
|
+
}
|
|
5210
|
+
return;
|
|
5211
|
+
}
|
|
5212
|
+
if (pending.length === 0) {
|
|
5213
|
+
ctx.data.action = mkAction("CI_PASSED", { checks: rows.length, prUrl });
|
|
5214
|
+
tryPostPr6(prNumber, `\u2705 kody waitForCi: all ${rows.length} checks green on PR #${prNumber}`, ctx.cwd);
|
|
5215
|
+
return;
|
|
5216
|
+
}
|
|
5217
|
+
await sleep(pollSeconds * 1e3);
|
|
5218
|
+
}
|
|
5219
|
+
ctx.data.action = mkAction("CI_TIMEOUT", {
|
|
5220
|
+
reason: `CI did not complete within ${timeoutMinutes} minutes`,
|
|
5221
|
+
prUrl
|
|
5222
|
+
});
|
|
5223
|
+
tryPostPr6(prNumber, `\u231B kody waitForCi: timed out after ${timeoutMinutes} minutes`, ctx.cwd);
|
|
5224
|
+
};
|
|
5225
|
+
function fetchChecks(prNumber, cwd) {
|
|
5226
|
+
try {
|
|
5227
|
+
const raw = execFileSync20(
|
|
5228
|
+
"gh",
|
|
5229
|
+
["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"],
|
|
5230
|
+
{
|
|
5231
|
+
encoding: "utf-8",
|
|
5232
|
+
timeout: API_TIMEOUT_MS9,
|
|
5233
|
+
cwd,
|
|
5234
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5235
|
+
}
|
|
5236
|
+
);
|
|
5237
|
+
const parsed = JSON.parse(raw);
|
|
5238
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
5239
|
+
} catch (err) {
|
|
5240
|
+
process.stderr.write(
|
|
5241
|
+
`[kody waitForCi] gh pr checks #${prNumber} failed: ${err instanceof Error ? err.message : String(err)}
|
|
5242
|
+
`
|
|
5243
|
+
);
|
|
5244
|
+
return null;
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
function summarize(rows) {
|
|
5248
|
+
const counts = {};
|
|
5249
|
+
for (const r of rows) {
|
|
5250
|
+
const k = r.bucket ?? "unknown";
|
|
5251
|
+
counts[k] = (counts[k] ?? 0) + 1;
|
|
5252
|
+
}
|
|
5253
|
+
return Object.entries(counts).map(([k, v]) => `${k}:${v}`).join(" ");
|
|
5254
|
+
}
|
|
5255
|
+
function mkAction(type, payload) {
|
|
5256
|
+
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5257
|
+
}
|
|
5258
|
+
function numArg(args, key, fallback) {
|
|
5259
|
+
const v = args?.[key];
|
|
5260
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= 0) return v;
|
|
5261
|
+
if (typeof v === "string") {
|
|
5262
|
+
const n = Number(v);
|
|
5263
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
5264
|
+
}
|
|
5265
|
+
return fallback;
|
|
5266
|
+
}
|
|
5267
|
+
function tryPostPr6(prNumber, body, cwd) {
|
|
5268
|
+
try {
|
|
5269
|
+
postPrReviewComment(prNumber, body, cwd);
|
|
5270
|
+
} catch {
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
function sleep(ms) {
|
|
5274
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
5275
|
+
}
|
|
5276
|
+
|
|
5148
5277
|
// src/scripts/watchStalePrsFlow.ts
|
|
5149
5278
|
function readWatchConfig(ctx) {
|
|
5150
5279
|
const cfg = ctx.config.watch;
|
|
@@ -5336,7 +5465,8 @@ var postflightScripts = {
|
|
|
5336
5465
|
postClassification,
|
|
5337
5466
|
notifyTerminal,
|
|
5338
5467
|
recordOutcome,
|
|
5339
|
-
mergeReleasePr
|
|
5468
|
+
mergeReleasePr,
|
|
5469
|
+
waitForCi
|
|
5340
5470
|
};
|
|
5341
5471
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
5342
5472
|
...Object.keys(preflightScripts),
|
|
@@ -5344,7 +5474,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
5344
5474
|
]);
|
|
5345
5475
|
|
|
5346
5476
|
// src/tools.ts
|
|
5347
|
-
import { execFileSync as
|
|
5477
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
5348
5478
|
function verifyCliTools(tools, cwd) {
|
|
5349
5479
|
const out = [];
|
|
5350
5480
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -5377,7 +5507,7 @@ function verifyOne(tool, cwd) {
|
|
|
5377
5507
|
}
|
|
5378
5508
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
5379
5509
|
try {
|
|
5380
|
-
|
|
5510
|
+
execFileSync21("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
5381
5511
|
return true;
|
|
5382
5512
|
} catch {
|
|
5383
5513
|
return false;
|
|
@@ -5784,7 +5914,7 @@ function detectPackageManager2(cwd) {
|
|
|
5784
5914
|
}
|
|
5785
5915
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
5786
5916
|
try {
|
|
5787
|
-
|
|
5917
|
+
execFileSync22(cmd, args, {
|
|
5788
5918
|
cwd,
|
|
5789
5919
|
stdio: stream ? "inherit" : "pipe",
|
|
5790
5920
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -5797,7 +5927,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
5797
5927
|
}
|
|
5798
5928
|
function isOnPath(bin) {
|
|
5799
5929
|
try {
|
|
5800
|
-
|
|
5930
|
+
execFileSync22("which", [bin], { stdio: "pipe" });
|
|
5801
5931
|
return true;
|
|
5802
5932
|
} catch {
|
|
5803
5933
|
return false;
|
|
@@ -5831,7 +5961,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5831
5961
|
} catch {
|
|
5832
5962
|
}
|
|
5833
5963
|
try {
|
|
5834
|
-
|
|
5964
|
+
execFileSync22("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
5835
5965
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
5836
5966
|
return 0;
|
|
5837
5967
|
} catch {
|
|
@@ -5841,16 +5971,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5841
5971
|
}
|
|
5842
5972
|
function configureGitIdentity(cwd) {
|
|
5843
5973
|
try {
|
|
5844
|
-
const name =
|
|
5974
|
+
const name = execFileSync22("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
5845
5975
|
if (name) return;
|
|
5846
5976
|
} catch {
|
|
5847
5977
|
}
|
|
5848
5978
|
try {
|
|
5849
|
-
|
|
5979
|
+
execFileSync22("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
5850
5980
|
} catch {
|
|
5851
5981
|
}
|
|
5852
5982
|
try {
|
|
5853
|
-
|
|
5983
|
+
execFileSync22("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
5854
5984
|
cwd,
|
|
5855
5985
|
stdio: "pipe"
|
|
5856
5986
|
});
|
|
@@ -6032,9 +6162,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
6032
6162
|
if (paths.length === 0) return;
|
|
6033
6163
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
6034
6164
|
try {
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6165
|
+
execFileSync23("git", ["add", ...paths], opts);
|
|
6166
|
+
execFileSync23("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
6167
|
+
execFileSync23("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
6038
6168
|
} catch (err) {
|
|
6039
6169
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6040
6170
|
process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
|
|
@@ -62,13 +62,24 @@
|
|
|
62
62
|
{ "script": "dispatch", "with": { "next": "release-deploy", "target": "issue" },
|
|
63
63
|
"runWhen": { "data.taskState.core.lastOutcome.type": "RELEASE_PUBLISH_COMPLETED" } },
|
|
64
64
|
|
|
65
|
+
{ "script": "waitForCi",
|
|
66
|
+
"with": { "timeoutMinutes": 30, "pollSeconds": 30, "initialWaitSeconds": 15, "maxFixCiAttempts": 3 },
|
|
67
|
+
"runWhen": { "data.taskState.core.lastOutcome.type": ["RELEASE_DEPLOY_COMPLETED", "FIX_CI_COMPLETED"] } },
|
|
68
|
+
|
|
69
|
+
{ "script": "dispatch", "with": { "next": "fix-ci", "target": "pr" },
|
|
70
|
+
"runWhen": { "data.action.type": "CI_FAILED" } },
|
|
71
|
+
|
|
65
72
|
{ "script": "finishFlow",
|
|
66
73
|
"with": { "reason": "release-completed", "label": "kody:done", "color": "0e8a16", "description": "kody: release complete" },
|
|
67
|
-
"runWhen": { "data.
|
|
74
|
+
"runWhen": { "data.action.type": "CI_PASSED" } },
|
|
75
|
+
|
|
76
|
+
{ "script": "finishFlow",
|
|
77
|
+
"with": { "reason": "release-failed", "label": "kody:failed", "color": "e11d21", "description": "kody: release flow failed" },
|
|
78
|
+
"runWhen": { "data.action.type": ["CI_GIVEUP", "CI_TIMEOUT"] } },
|
|
68
79
|
|
|
69
80
|
{ "script": "finishFlow",
|
|
70
81
|
"with": { "reason": "release-failed", "label": "kody:failed", "color": "e11d21", "description": "kody: release flow failed" },
|
|
71
|
-
"runWhen": { "data.taskState.core.lastOutcome.type": ["RELEASE_PREPARE_FAILED", "RELEASE_MERGE_FAILED", "RELEASE_PUBLISH_FAILED", "RELEASE_DEPLOY_FAILED"] } },
|
|
82
|
+
"runWhen": { "data.taskState.core.lastOutcome.type": ["RELEASE_PREPARE_FAILED", "RELEASE_MERGE_FAILED", "RELEASE_PUBLISH_FAILED", "RELEASE_DEPLOY_FAILED", "FIX_CI_FAILED"] } },
|
|
72
83
|
|
|
73
84
|
{ "script": "persistFlowState" }
|
|
74
85
|
]
|
|
@@ -74,6 +74,10 @@ existing=$(gh pr list --head "$default_branch" --base "$release_branch" --state
|
|
|
74
74
|
| python3 -c 'import json,sys; data=json.load(sys.stdin); print(data[0]["url"] if data else "")' 2>/dev/null \
|
|
75
75
|
|| echo "")
|
|
76
76
|
|
|
77
|
+
# Hoisted so the kody-release-pr marker write below also runs in the
|
|
78
|
+
# reuse-existing-PR path.
|
|
79
|
+
issue_arg="${KODY_ARG_ISSUE:-}"
|
|
80
|
+
|
|
77
81
|
if [[ -n "$existing" ]]; then
|
|
78
82
|
echo " reusing existing deploy PR: ${existing}"
|
|
79
83
|
pr_url="$existing"
|
|
@@ -81,7 +85,6 @@ else
|
|
|
81
85
|
# Same Tracking-Issue marker as release-prepare — non-closing reference
|
|
82
86
|
# so the originating release issue stays open through the deploy step
|
|
83
87
|
# while the Kody Dashboard can still link this PR to the task for preview.
|
|
84
|
-
issue_arg="${KODY_ARG_ISSUE:-}"
|
|
85
88
|
tracking_line=""
|
|
86
89
|
if [[ "$issue_arg" =~ ^[0-9]+$ && "$issue_arg" != "0" ]]; then
|
|
87
90
|
tracking_line=$'\n\nTracking-Issue: #'"${issue_arg}"
|
|
@@ -102,6 +105,24 @@ if [[ -z "$pr_url" ]]; then
|
|
|
102
105
|
exit 1
|
|
103
106
|
fi
|
|
104
107
|
|
|
108
|
+
# Persist the deploy-PR marker on the originating issue body. Mirrors the
|
|
109
|
+
# release-prepare path — the issue body is owned by the orchestrator, so
|
|
110
|
+
# this signal survives any @kody fix that overwrites the PR body. The
|
|
111
|
+
# marker replaces the prepare-PR ref so the dashboard pivots to the deploy
|
|
112
|
+
# PR (the now-current task) automatically.
|
|
113
|
+
if [[ "${issue_arg:-}" =~ ^[0-9]+$ && "${issue_arg:-0}" != "0" ]]; then
|
|
114
|
+
pr_number="${pr_url##*/}"
|
|
115
|
+
if [[ "$pr_number" =~ ^[0-9]+$ ]]; then
|
|
116
|
+
cur_body=$(gh issue view "$issue_arg" --json body -q .body 2>/dev/null || echo "")
|
|
117
|
+
cleaned_body=$(printf '%s' "$cur_body" | sed -E '/<!-- kody-release-pr:[^>]*-->/d')
|
|
118
|
+
{
|
|
119
|
+
printf '%s' "$cleaned_body"
|
|
120
|
+
printf '\n\n<!-- kody-release-pr: #%s -->\n' "$pr_number"
|
|
121
|
+
} | gh issue edit "$issue_arg" --body-file - >/dev/null 2>&1 || \
|
|
122
|
+
echo "[kody release-deploy] WARN: failed to write kody-release-pr marker to issue #${issue_arg}"
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
|
|
105
126
|
echo "RELEASE_DEPLOY_PR=${pr_url}"
|
|
106
127
|
echo "KODY_PR_URL=${pr_url}"
|
|
107
128
|
|
|
@@ -333,6 +333,24 @@ if [[ -z "$pr_url" ]]; then
|
|
|
333
333
|
fail "release prepare: gh pr create returned empty URL" 4
|
|
334
334
|
fi
|
|
335
335
|
|
|
336
|
+
# Persist a release-PR marker on the originating issue body so the Kody
|
|
337
|
+
# Dashboard can link the issue → PR even if @kody fix later overwrites the
|
|
338
|
+
# PR body. Idempotent: any existing marker line is stripped before append,
|
|
339
|
+
# so re-runs replace the previous PR ref. The issue body is owned by the
|
|
340
|
+
# orchestrator (no @kody fix touches it), so this signal is durable.
|
|
341
|
+
if [[ "${issue_arg:-}" =~ ^[0-9]+$ && "${issue_arg:-0}" != "0" ]]; then
|
|
342
|
+
pr_number="${pr_url##*/}"
|
|
343
|
+
if [[ "$pr_number" =~ ^[0-9]+$ ]]; then
|
|
344
|
+
cur_body=$(gh issue view "$issue_arg" --json body -q .body 2>/dev/null || echo "")
|
|
345
|
+
cleaned_body=$(printf '%s' "$cur_body" | sed -E '/<!-- kody-release-pr:[^>]*-->/d')
|
|
346
|
+
{
|
|
347
|
+
printf '%s' "$cleaned_body"
|
|
348
|
+
printf '\n\n<!-- kody-release-pr: #%s -->\n' "$pr_number"
|
|
349
|
+
} | gh issue edit "$issue_arg" --body-file - >/dev/null 2>&1 || \
|
|
350
|
+
echo "[kody release-prepare] WARN: failed to write kody-release-pr marker to issue #${issue_arg}"
|
|
351
|
+
fi
|
|
352
|
+
fi
|
|
353
|
+
|
|
336
354
|
echo "RELEASE_PR=${pr_url}"
|
|
337
355
|
echo "KODY_PR_URL=${pr_url}"
|
|
338
356
|
echo "KODY_REASON=opened release PR for ${tag}"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.32",
|
|
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",
|