@kody-ade/kody-engine 0.3.29 → 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
|
|
|
@@ -2580,6 +2580,19 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
2580
2580
|
return;
|
|
2581
2581
|
}
|
|
2582
2582
|
const state = ctx.data.taskState;
|
|
2583
|
+
if (target === "pr" && !state?.core.prUrl) {
|
|
2584
|
+
const reason = `cannot dispatch @kody ${next}: target=pr but state.core.prUrl is not set`;
|
|
2585
|
+
process.stderr.write(`[kody dispatch] ${reason}
|
|
2586
|
+
`);
|
|
2587
|
+
const action = {
|
|
2588
|
+
type: "AGENT_NOT_RUN",
|
|
2589
|
+
payload: { reason, dispatchTarget: "pr", next },
|
|
2590
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2591
|
+
};
|
|
2592
|
+
ctx.data.action = action;
|
|
2593
|
+
if (state) state.core.lastOutcome = action;
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2583
2596
|
if (state?.flow) {
|
|
2584
2597
|
state.flow.step = next;
|
|
2585
2598
|
}
|
|
@@ -2880,11 +2893,15 @@ function firstLine(s) {
|
|
|
2880
2893
|
}
|
|
2881
2894
|
function findExistingPr(branch, cwd) {
|
|
2882
2895
|
try {
|
|
2883
|
-
const output = gh2(
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2896
|
+
const output = gh2(
|
|
2897
|
+
["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,body", "--limit", "1"],
|
|
2898
|
+
{ cwd }
|
|
2899
|
+
);
|
|
2900
|
+
const arr = JSON.parse(output);
|
|
2901
|
+
const first = Array.isArray(arr) ? arr[0] : null;
|
|
2902
|
+
if (first && typeof first.number === "number" && typeof first.url === "string") {
|
|
2903
|
+
const body = typeof first.body === "string" ? first.body : "";
|
|
2904
|
+
return { number: first.number, url: first.url, body };
|
|
2888
2905
|
}
|
|
2889
2906
|
return null;
|
|
2890
2907
|
} catch {
|
|
@@ -5128,6 +5145,135 @@ var verify = async (ctx) => {
|
|
|
5128
5145
|
}
|
|
5129
5146
|
};
|
|
5130
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
|
+
|
|
5131
5277
|
// src/scripts/watchStalePrsFlow.ts
|
|
5132
5278
|
function readWatchConfig(ctx) {
|
|
5133
5279
|
const cfg = ctx.config.watch;
|
|
@@ -5319,7 +5465,8 @@ var postflightScripts = {
|
|
|
5319
5465
|
postClassification,
|
|
5320
5466
|
notifyTerminal,
|
|
5321
5467
|
recordOutcome,
|
|
5322
|
-
mergeReleasePr
|
|
5468
|
+
mergeReleasePr,
|
|
5469
|
+
waitForCi
|
|
5323
5470
|
};
|
|
5324
5471
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
5325
5472
|
...Object.keys(preflightScripts),
|
|
@@ -5327,7 +5474,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
5327
5474
|
]);
|
|
5328
5475
|
|
|
5329
5476
|
// src/tools.ts
|
|
5330
|
-
import { execFileSync as
|
|
5477
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
5331
5478
|
function verifyCliTools(tools, cwd) {
|
|
5332
5479
|
const out = [];
|
|
5333
5480
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -5360,7 +5507,7 @@ function verifyOne(tool, cwd) {
|
|
|
5360
5507
|
}
|
|
5361
5508
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
5362
5509
|
try {
|
|
5363
|
-
|
|
5510
|
+
execFileSync21("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
5364
5511
|
return true;
|
|
5365
5512
|
} catch {
|
|
5366
5513
|
return false;
|
|
@@ -5767,7 +5914,7 @@ function detectPackageManager2(cwd) {
|
|
|
5767
5914
|
}
|
|
5768
5915
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
5769
5916
|
try {
|
|
5770
|
-
|
|
5917
|
+
execFileSync22(cmd, args, {
|
|
5771
5918
|
cwd,
|
|
5772
5919
|
stdio: stream ? "inherit" : "pipe",
|
|
5773
5920
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -5780,7 +5927,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
5780
5927
|
}
|
|
5781
5928
|
function isOnPath(bin) {
|
|
5782
5929
|
try {
|
|
5783
|
-
|
|
5930
|
+
execFileSync22("which", [bin], { stdio: "pipe" });
|
|
5784
5931
|
return true;
|
|
5785
5932
|
} catch {
|
|
5786
5933
|
return false;
|
|
@@ -5814,7 +5961,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5814
5961
|
} catch {
|
|
5815
5962
|
}
|
|
5816
5963
|
try {
|
|
5817
|
-
|
|
5964
|
+
execFileSync22("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
5818
5965
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
5819
5966
|
return 0;
|
|
5820
5967
|
} catch {
|
|
@@ -5824,16 +5971,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5824
5971
|
}
|
|
5825
5972
|
function configureGitIdentity(cwd) {
|
|
5826
5973
|
try {
|
|
5827
|
-
const name =
|
|
5974
|
+
const name = execFileSync22("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
5828
5975
|
if (name) return;
|
|
5829
5976
|
} catch {
|
|
5830
5977
|
}
|
|
5831
5978
|
try {
|
|
5832
|
-
|
|
5979
|
+
execFileSync22("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
5833
5980
|
} catch {
|
|
5834
5981
|
}
|
|
5835
5982
|
try {
|
|
5836
|
-
|
|
5983
|
+
execFileSync22("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
5837
5984
|
cwd,
|
|
5838
5985
|
stdio: "pipe"
|
|
5839
5986
|
});
|
|
@@ -6015,9 +6162,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
6015
6162
|
if (paths.length === 0) return;
|
|
6016
6163
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
6017
6164
|
try {
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
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);
|
|
6021
6168
|
} catch (err) {
|
|
6022
6169
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6023
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,13 +74,24 @@ 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"
|
|
80
84
|
else
|
|
85
|
+
# Same Tracking-Issue marker as release-prepare — non-closing reference
|
|
86
|
+
# so the originating release issue stays open through the deploy step
|
|
87
|
+
# while the Kody Dashboard can still link this PR to the task for preview.
|
|
88
|
+
tracking_line=""
|
|
89
|
+
if [[ "$issue_arg" =~ ^[0-9]+$ && "$issue_arg" != "0" ]]; then
|
|
90
|
+
tracking_line=$'\n\nTracking-Issue: #'"${issue_arg}"
|
|
91
|
+
fi
|
|
81
92
|
body="Automated deploy PR opened by kody — promotes \`${default_branch}\` to \`${release_branch}\` for release **v${version}**.
|
|
82
93
|
|
|
83
|
-
Merge this PR to deploy v${version} to \`${release_branch}
|
|
94
|
+
Merge this PR to deploy v${version} to \`${release_branch}\`.${tracking_line}"
|
|
84
95
|
if ! pr_url=$(printf '%s' "$body" | gh pr create --head "$default_branch" --base "$release_branch" --title "deploy: ${default_branch} → ${release_branch} (v${version})" --body-file -); then
|
|
85
96
|
echo "KODY_REASON=release deploy: gh pr create failed"
|
|
86
97
|
echo "KODY_SKIP_AGENT=true"
|
|
@@ -94,6 +105,24 @@ if [[ -z "$pr_url" ]]; then
|
|
|
94
105
|
exit 1
|
|
95
106
|
fi
|
|
96
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
|
+
|
|
97
126
|
echo "RELEASE_DEPLOY_PR=${pr_url}"
|
|
98
127
|
echo "KODY_PR_URL=${pr_url}"
|
|
99
128
|
|
|
@@ -313,7 +313,19 @@ _… truncated; see CHANGELOG.md_"
|
|
|
313
313
|
else
|
|
314
314
|
body_entry="$entry"
|
|
315
315
|
fi
|
|
316
|
-
|
|
316
|
+
# Link the PR back to the originating release issue WITHOUT auto-closing
|
|
317
|
+
# it. The orchestrator continues to publish + deploy after merge using the
|
|
318
|
+
# issue as the dispatch target, so the issue must stay open for the
|
|
319
|
+
# dashboard to keep showing the task as in-progress until finishFlow
|
|
320
|
+
# applies kody:done. We use `Tracking-Issue: #N` (not `Closes #N`) — GitHub
|
|
321
|
+
# treats this as plain text, the dashboard parses it as a fallback link
|
|
322
|
+
# signal, and no auto-close fires on PR merge.
|
|
323
|
+
issue_arg="${KODY_ARG_ISSUE:-}"
|
|
324
|
+
tracking_line=""
|
|
325
|
+
if [[ "$issue_arg" =~ ^[0-9]+$ && "$issue_arg" != "0" ]]; then
|
|
326
|
+
tracking_line=$'\n\nTracking-Issue: #'"${issue_arg}"
|
|
327
|
+
fi
|
|
328
|
+
body=$'Automated release PR opened by kody.\n\n'"$body_entry"$'\n\nThe release orchestrator will merge this into `'"${default_branch}"$'` and continue to publish + deploy.'"${tracking_line}"
|
|
317
329
|
pr_url=$(printf '%s' "$body" | gh pr create --head "$release_branch" --base "$default_branch" --title "chore: release ${tag}" --body-file -)
|
|
318
330
|
fi
|
|
319
331
|
|
|
@@ -321,6 +333,24 @@ if [[ -z "$pr_url" ]]; then
|
|
|
321
333
|
fail "release prepare: gh pr create returned empty URL" 4
|
|
322
334
|
fi
|
|
323
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
|
+
|
|
324
354
|
echo "RELEASE_PR=${pr_url}"
|
|
325
355
|
echo "KODY_PR_URL=${pr_url}"
|
|
326
356
|
echo "KODY_REASON=opened release PR for ${tag}"
|
|
@@ -57,17 +57,8 @@ documented in `AGENTS.md` / `CLAUDE.md` — reference those files by path
|
|
|
57
57
|
cite it; don't copy it.
|
|
58
58
|
|
|
59
59
|
## Clarifying questions
|
|
60
|
-
Numbered list. Each question must include a one-line "Why:"
|
|
61
|
-
|
|
62
|
-
matters" in general, but which fork in the road it picks.
|
|
63
|
-
|
|
64
|
-
Default to asking. Only skip this section when every entry in
|
|
65
|
-
"Gaps & assumptions" is genuinely safe to proceed on without confirmation;
|
|
66
|
-
if you skip, justify it in one line under the heading. Prefer a few sharp
|
|
67
|
-
questions over many soft ones — a question that wouldn't change the
|
|
68
|
-
implementation is noise. Address the questions to the issue author / a
|
|
69
|
-
human reviewer; they'll be answered in a follow-up comment and picked up
|
|
70
|
-
on the next research run via delta mode.
|
|
60
|
+
Numbered list. Each question must include a one-line "Why:" explaining why
|
|
61
|
+
the answer changes the implementation. Skip if there are genuinely none.
|
|
71
62
|
|
|
72
63
|
## Gaps & assumptions
|
|
73
64
|
What is unknown, and — for each gap — what assumption the implementer would
|
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",
|