@kody-ade/kody-engine 0.3.21 → 0.3.22
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 +189 -448
- package/dist/executables/release/profile.json +49 -51
- package/dist/executables/release-deploy/deploy.sh +84 -0
- package/dist/executables/release-deploy/profile.json +53 -0
- package/dist/executables/release-prepare/prepare.sh +328 -0
- package/dist/executables/release-prepare/profile.json +70 -0
- package/dist/executables/release-publish/profile.json +53 -0
- package/dist/executables/release-publish/publish.sh +110 -0
- package/dist/executables/research/prompt.md +11 -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.3.
|
|
6
|
+
version: "0.3.22",
|
|
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",
|
|
@@ -51,8 +51,8 @@ var package_default = {
|
|
|
51
51
|
|
|
52
52
|
// src/chat-cli.ts
|
|
53
53
|
import { execFileSync as execFileSync22 } from "child_process";
|
|
54
|
-
import * as
|
|
55
|
-
import * as
|
|
54
|
+
import * as fs22 from "fs";
|
|
55
|
+
import * as path19 from "path";
|
|
56
56
|
|
|
57
57
|
// src/chat/events.ts
|
|
58
58
|
import * as fs from "fs";
|
|
@@ -154,7 +154,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
154
154
|
throw new Error(`kody.config.json is invalid JSON: ${msg}`);
|
|
155
155
|
}
|
|
156
156
|
const quality = raw.quality ?? {};
|
|
157
|
-
const
|
|
157
|
+
const git3 = raw.git ?? {};
|
|
158
158
|
const github = raw.github ?? {};
|
|
159
159
|
const agent = raw.agent ?? {};
|
|
160
160
|
if (!agent.model || typeof agent.model !== "string") {
|
|
@@ -170,7 +170,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
170
170
|
testUnit: typeof quality.testUnit === "string" ? quality.testUnit : ""
|
|
171
171
|
},
|
|
172
172
|
git: {
|
|
173
|
-
defaultBranch: typeof
|
|
173
|
+
defaultBranch: typeof git3.defaultBranch === "string" ? git3.defaultBranch : "main"
|
|
174
174
|
},
|
|
175
175
|
github: {
|
|
176
176
|
owner: String(github.owner),
|
|
@@ -223,6 +223,7 @@ function parseReleaseConfig(raw) {
|
|
|
223
223
|
if (Array.isArray(r.versionFiles)) out.versionFiles = r.versionFiles.filter((f) => typeof f === "string");
|
|
224
224
|
if (typeof r.publishCommand === "string") out.publishCommand = r.publishCommand;
|
|
225
225
|
if (typeof r.notifyCommand === "string") out.notifyCommand = r.notifyCommand;
|
|
226
|
+
if (typeof r.deployCommand === "string") out.deployCommand = r.deployCommand;
|
|
226
227
|
if (typeof r.e2eCommand === "string") out.e2eCommand = r.e2eCommand;
|
|
227
228
|
if (typeof r.draftRelease === "boolean") out.draftRelease = r.draftRelease;
|
|
228
229
|
if (typeof r.releaseBranch === "string") out.releaseBranch = r.releaseBranch;
|
|
@@ -577,8 +578,8 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
577
578
|
|
|
578
579
|
// src/kody-cli.ts
|
|
579
580
|
import { execFileSync as execFileSync21 } from "child_process";
|
|
580
|
-
import * as
|
|
581
|
-
import * as
|
|
581
|
+
import * as fs21 from "fs";
|
|
582
|
+
import * as path18 from "path";
|
|
582
583
|
|
|
583
584
|
// src/dispatch.ts
|
|
584
585
|
import * as fs6 from "fs";
|
|
@@ -680,19 +681,7 @@ function autoDispatch(opts) {
|
|
|
680
681
|
if (eventName === "schedule") {
|
|
681
682
|
return { executable: "mission-scheduler", cliArgs: {}, target: 0 };
|
|
682
683
|
}
|
|
683
|
-
if (eventName === "pull_request")
|
|
684
|
-
const merged = event.pull_request?.merged === true;
|
|
685
|
-
const headRef = String(event.pull_request?.head?.ref ?? "");
|
|
686
|
-
const prNumber = Number(event.pull_request?.number ?? 0);
|
|
687
|
-
if (merged && /^release\/v\d+\.\d+\.\d+/.test(headRef) && prNumber > 0) {
|
|
688
|
-
return {
|
|
689
|
-
executable: "release",
|
|
690
|
-
cliArgs: { mode: "finalize", issue: prNumber },
|
|
691
|
-
target: prNumber
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
return null;
|
|
695
|
-
}
|
|
684
|
+
if (eventName === "pull_request") return null;
|
|
696
685
|
if (eventName !== "issue_comment") return null;
|
|
697
686
|
const rawBody = String(event.comment?.body ?? "");
|
|
698
687
|
const authorLogin = String(event.comment?.user?.login ?? "");
|
|
@@ -816,9 +805,9 @@ function coerceBare(spec, value) {
|
|
|
816
805
|
}
|
|
817
806
|
|
|
818
807
|
// src/executor.ts
|
|
819
|
-
import { spawnSync
|
|
820
|
-
import * as
|
|
821
|
-
import * as
|
|
808
|
+
import { spawnSync } from "child_process";
|
|
809
|
+
import * as fs20 from "fs";
|
|
810
|
+
import * as path17 from "path";
|
|
822
811
|
|
|
823
812
|
// src/litellm.ts
|
|
824
813
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -4044,6 +4033,53 @@ var loadTaskState = async (ctx) => {
|
|
|
4044
4033
|
ctx.data.taskState = readTaskState(target, number, ctx.cwd);
|
|
4045
4034
|
};
|
|
4046
4035
|
|
|
4036
|
+
// src/scripts/mergeReleasePr.ts
|
|
4037
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
4038
|
+
var API_TIMEOUT_MS6 = 6e4;
|
|
4039
|
+
var mergeReleasePr = async (ctx) => {
|
|
4040
|
+
const state = ctx.data.taskState;
|
|
4041
|
+
const prUrl = state?.core.prUrl;
|
|
4042
|
+
if (!prUrl) {
|
|
4043
|
+
ctx.data.action = makeAction("RELEASE_MERGE_FAILED", { reason: "no prUrl on task state" });
|
|
4044
|
+
if (state) state.core.lastOutcome = ctx.data.action;
|
|
4045
|
+
return;
|
|
4046
|
+
}
|
|
4047
|
+
const prNumber = parsePrNumber2(prUrl);
|
|
4048
|
+
if (!prNumber) {
|
|
4049
|
+
ctx.data.action = makeAction("RELEASE_MERGE_FAILED", { reason: `cannot parse PR number from ${prUrl}` });
|
|
4050
|
+
if (state) state.core.lastOutcome = ctx.data.action;
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
try {
|
|
4054
|
+
execFileSync14("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
4055
|
+
timeout: API_TIMEOUT_MS6,
|
|
4056
|
+
cwd: ctx.cwd,
|
|
4057
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4058
|
+
});
|
|
4059
|
+
} catch (err) {
|
|
4060
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4061
|
+
if (/already merged/i.test(msg)) {
|
|
4062
|
+
ctx.data.action = makeAction("RELEASE_MERGE_COMPLETED", { prUrl, alreadyMerged: true });
|
|
4063
|
+
if (state) state.core.lastOutcome = ctx.data.action;
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
ctx.data.action = makeAction("RELEASE_MERGE_FAILED", { reason: msg, prUrl });
|
|
4067
|
+
if (state) state.core.lastOutcome = ctx.data.action;
|
|
4068
|
+
return;
|
|
4069
|
+
}
|
|
4070
|
+
ctx.data.action = makeAction("RELEASE_MERGE_COMPLETED", { prUrl });
|
|
4071
|
+
if (state) state.core.lastOutcome = ctx.data.action;
|
|
4072
|
+
};
|
|
4073
|
+
function makeAction(type, payload) {
|
|
4074
|
+
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4075
|
+
}
|
|
4076
|
+
function parsePrNumber2(prUrl) {
|
|
4077
|
+
const m = prUrl.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
4078
|
+
if (!m) return null;
|
|
4079
|
+
const n = parseInt(m[1], 10);
|
|
4080
|
+
return Number.isFinite(n) ? n : null;
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4047
4083
|
// src/scripts/mirrorStateToPr.ts
|
|
4048
4084
|
var mirrorStateToPr = async (ctx) => {
|
|
4049
4085
|
const issueNumber = ctx.data.commentTargetNumber;
|
|
@@ -4051,7 +4087,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
4051
4087
|
if (!issueNumber || issueTarget !== "issue") return;
|
|
4052
4088
|
const prUrl = ctx.output.prUrl ?? ctx.data.prResult?.url;
|
|
4053
4089
|
if (!prUrl) return;
|
|
4054
|
-
const prNumber =
|
|
4090
|
+
const prNumber = parsePrNumber3(prUrl);
|
|
4055
4091
|
if (!prNumber) return;
|
|
4056
4092
|
let state;
|
|
4057
4093
|
try {
|
|
@@ -4069,18 +4105,49 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
4069
4105
|
);
|
|
4070
4106
|
}
|
|
4071
4107
|
};
|
|
4072
|
-
function
|
|
4108
|
+
function parsePrNumber3(prUrl) {
|
|
4073
4109
|
const m = prUrl.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
4074
4110
|
if (!m) return null;
|
|
4075
4111
|
const n = parseInt(m[1], 10);
|
|
4076
4112
|
return Number.isFinite(n) ? n : null;
|
|
4077
4113
|
}
|
|
4078
4114
|
|
|
4115
|
+
// src/scripts/notifyTerminal.ts
|
|
4116
|
+
var notifyTerminal = async (ctx, _profile, _agentResult, args) => {
|
|
4117
|
+
const issueNumber = ctx.args.issue;
|
|
4118
|
+
if (!issueNumber || issueNumber <= 0) return;
|
|
4119
|
+
const label = args?.label ?? "kody run";
|
|
4120
|
+
const dryRun = ctx.args["dry-run"] === true || ctx.args.dryRun === true;
|
|
4121
|
+
const exit = ctx.output.exitCode ?? 0;
|
|
4122
|
+
const reason = ctx.output.reason;
|
|
4123
|
+
const prUrl = ctx.output.prUrl;
|
|
4124
|
+
const body = composeBody({ label, exit, prUrl, reason, dryRun });
|
|
4125
|
+
try {
|
|
4126
|
+
postIssueComment(issueNumber, body, ctx.cwd);
|
|
4127
|
+
} catch (err) {
|
|
4128
|
+
process.stderr.write(
|
|
4129
|
+
`[kody notifyTerminal] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
4130
|
+
`
|
|
4131
|
+
);
|
|
4132
|
+
}
|
|
4133
|
+
};
|
|
4134
|
+
function composeBody({ label, exit, prUrl, reason, dryRun }) {
|
|
4135
|
+
if (exit !== 0) {
|
|
4136
|
+
const suffix = prUrl ? ` \u2014 ${prUrl}` : "";
|
|
4137
|
+
return `\u26A0\uFE0F kody ${label} failed: ${truncate2(reason ?? "unknown error", 1500)}${suffix}`;
|
|
4138
|
+
}
|
|
4139
|
+
if (dryRun) {
|
|
4140
|
+
return `\u2139\uFE0F kody ${label} (dry-run): ${reason ?? "plan printed, no changes applied"}`;
|
|
4141
|
+
}
|
|
4142
|
+
if (prUrl) return `\u2705 kody ${label}: ${prUrl}`;
|
|
4143
|
+
return `\u2705 kody ${label} complete`;
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4079
4146
|
// src/scripts/parseAgentResult.ts
|
|
4080
4147
|
var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
4081
4148
|
if (!agentResult) {
|
|
4082
4149
|
ctx.data.agentDone = false;
|
|
4083
|
-
ctx.data.action =
|
|
4150
|
+
ctx.data.action = makeAction2("AGENT_NOT_RUN", { reason: "no agent result" });
|
|
4084
4151
|
return;
|
|
4085
4152
|
}
|
|
4086
4153
|
const parsed = parseAgentResult(agentResult.finalText);
|
|
@@ -4095,17 +4162,17 @@ var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
|
4095
4162
|
ctx.data.agentError = agentResult.error;
|
|
4096
4163
|
const modeSeg = (ctx.args.mode ?? profile.name).replace(/-/g, "_").toUpperCase();
|
|
4097
4164
|
if (parsed.done) {
|
|
4098
|
-
ctx.data.action =
|
|
4165
|
+
ctx.data.action = makeAction2(`${modeSeg}_COMPLETED`, {
|
|
4099
4166
|
commitMessage: parsed.commitMessage,
|
|
4100
4167
|
prSummary: parsed.prSummary
|
|
4101
4168
|
});
|
|
4102
4169
|
} else {
|
|
4103
|
-
ctx.data.action =
|
|
4170
|
+
ctx.data.action = makeAction2(`${modeSeg}_FAILED`, {
|
|
4104
4171
|
reason: parsed.failureReason || agentResult.error || "unknown failure"
|
|
4105
4172
|
});
|
|
4106
4173
|
}
|
|
4107
4174
|
};
|
|
4108
|
-
function
|
|
4175
|
+
function makeAction2(type, payload) {
|
|
4109
4176
|
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4110
4177
|
}
|
|
4111
4178
|
|
|
@@ -4202,8 +4269,8 @@ var persistFlowState = async (ctx) => {
|
|
|
4202
4269
|
};
|
|
4203
4270
|
|
|
4204
4271
|
// src/scripts/postClassification.ts
|
|
4205
|
-
import { execFileSync as
|
|
4206
|
-
var
|
|
4272
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
4273
|
+
var API_TIMEOUT_MS7 = 3e4;
|
|
4207
4274
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
4208
4275
|
var postClassification = async (ctx) => {
|
|
4209
4276
|
const issueNumber = ctx.args.issue;
|
|
@@ -4232,9 +4299,9 @@ var postClassification = async (ctx) => {
|
|
|
4232
4299
|
ctx.cwd
|
|
4233
4300
|
);
|
|
4234
4301
|
try {
|
|
4235
|
-
|
|
4302
|
+
execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
4236
4303
|
cwd: ctx.cwd,
|
|
4237
|
-
timeout:
|
|
4304
|
+
timeout: API_TIMEOUT_MS7,
|
|
4238
4305
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4239
4306
|
});
|
|
4240
4307
|
} catch (err) {
|
|
@@ -4247,7 +4314,7 @@ var postClassification = async (ctx) => {
|
|
|
4247
4314
|
ctx.output.reason = "classify: dispatch failed";
|
|
4248
4315
|
return;
|
|
4249
4316
|
}
|
|
4250
|
-
ctx.data.action =
|
|
4317
|
+
ctx.data.action = makeAction3(`CLASSIFIED_AS_${classification.toUpperCase()}`, {
|
|
4251
4318
|
classification,
|
|
4252
4319
|
reason: reason ?? "",
|
|
4253
4320
|
source: ctx.data.classificationSource ?? "agent"
|
|
@@ -4266,15 +4333,15 @@ function parseClassification(prSummary) {
|
|
|
4266
4333
|
}
|
|
4267
4334
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
4268
4335
|
try {
|
|
4269
|
-
|
|
4336
|
+
execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
4270
4337
|
cwd,
|
|
4271
|
-
timeout:
|
|
4338
|
+
timeout: API_TIMEOUT_MS7,
|
|
4272
4339
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4273
4340
|
});
|
|
4274
4341
|
} catch {
|
|
4275
4342
|
}
|
|
4276
4343
|
}
|
|
4277
|
-
function
|
|
4344
|
+
function makeAction3(type, payload) {
|
|
4278
4345
|
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4279
4346
|
}
|
|
4280
4347
|
function failedAction(reason) {
|
|
@@ -4449,385 +4516,21 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
4449
4516
|
);
|
|
4450
4517
|
};
|
|
4451
4518
|
|
|
4452
|
-
// src/scripts/
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
}
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
let [major, minor, patch] = [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
|
|
4467
|
-
if (bump === "major") {
|
|
4468
|
-
major++;
|
|
4469
|
-
minor = 0;
|
|
4470
|
-
patch = 0;
|
|
4471
|
-
} else if (bump === "minor") {
|
|
4472
|
-
minor++;
|
|
4473
|
-
patch = 0;
|
|
4474
|
-
} else patch++;
|
|
4475
|
-
return `${major}.${minor}.${patch}`;
|
|
4476
|
-
}
|
|
4477
|
-
function updateVersionInFile(file, newVersion, cwd) {
|
|
4478
|
-
const abs = path17.join(cwd, file);
|
|
4479
|
-
if (!fs19.existsSync(abs)) return false;
|
|
4480
|
-
const content = fs19.readFileSync(abs, "utf-8");
|
|
4481
|
-
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
4482
|
-
if (updated === content) return false;
|
|
4483
|
-
fs19.writeFileSync(abs, updated);
|
|
4484
|
-
return true;
|
|
4485
|
-
}
|
|
4486
|
-
var FIRST_RELEASE_COMMIT_CAP = 100;
|
|
4487
|
-
function generateChangelog(cwd, newVersion, lastTag) {
|
|
4488
|
-
const logArgs = ["log", "--pretty=format:%s||%h", "--no-merges"];
|
|
4489
|
-
if (lastTag) logArgs.splice(1, 0, `${lastTag}..HEAD`);
|
|
4490
|
-
else logArgs.splice(1, 0, `-n${FIRST_RELEASE_COMMIT_CAP}`, "HEAD");
|
|
4491
|
-
let log = "";
|
|
4492
|
-
try {
|
|
4493
|
-
log = execFileSync15("git", logArgs, {
|
|
4494
|
-
cwd,
|
|
4495
|
-
encoding: "utf-8",
|
|
4496
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4497
|
-
}).trim();
|
|
4498
|
-
} catch {
|
|
4499
|
-
}
|
|
4500
|
-
const commits = log.split("\n").filter((l) => l.length > 0).map((line) => {
|
|
4501
|
-
const [subject, sha] = line.split("||");
|
|
4502
|
-
return { subject: subject ?? "", sha: sha ?? "" };
|
|
4503
|
-
}).filter((c) => !/^chore:\s*release\s+v\d/i.test(c.subject));
|
|
4504
|
-
const groups = { feat: [], fix: [], perf: [], refactor: [], docs: [], chore: [], other: [] };
|
|
4505
|
-
for (const c of commits) {
|
|
4506
|
-
const m = c.subject.match(/^(\w+)(?:\(.*?\))?\s*:\s*(.+)$/);
|
|
4507
|
-
const type = m?.[1]?.toLowerCase() ?? "other";
|
|
4508
|
-
const msg = m?.[2] ?? c.subject;
|
|
4509
|
-
const bucket = groups[type] ?? groups.other;
|
|
4510
|
-
bucket.push(`- ${msg} (${c.sha})`);
|
|
4511
|
-
}
|
|
4512
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4513
|
-
const parts = [`## v${newVersion} \u2014 ${date}`, ""];
|
|
4514
|
-
const labels = [
|
|
4515
|
-
["feat", "Features"],
|
|
4516
|
-
["fix", "Fixes"],
|
|
4517
|
-
["perf", "Performance"],
|
|
4518
|
-
["refactor", "Refactoring"],
|
|
4519
|
-
["docs", "Docs"],
|
|
4520
|
-
["chore", "Chores"],
|
|
4521
|
-
["other", "Other"]
|
|
4522
|
-
];
|
|
4523
|
-
for (const [key, label] of labels) {
|
|
4524
|
-
const items = groups[key];
|
|
4525
|
-
if (!items || items.length === 0) continue;
|
|
4526
|
-
parts.push(`### ${label}`);
|
|
4527
|
-
parts.push(...items);
|
|
4528
|
-
parts.push("");
|
|
4529
|
-
}
|
|
4530
|
-
if (parts.length === 2) parts.push("_No notable commits since the last release._", "");
|
|
4531
|
-
return parts.join("\n");
|
|
4532
|
-
}
|
|
4533
|
-
function prependChangelog(cwd, entry) {
|
|
4534
|
-
const p = path17.join(cwd, "CHANGELOG.md");
|
|
4535
|
-
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
4536
|
-
if (fs19.existsSync(p)) {
|
|
4537
|
-
const prior = fs19.readFileSync(p, "utf-8");
|
|
4538
|
-
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
4539
|
-
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
4540
|
-
fs19.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
4541
|
-
${entry}${prior.slice(idx + 1)}`);
|
|
4542
|
-
} else {
|
|
4543
|
-
fs19.writeFileSync(p, `${header}${entry}${prior}`);
|
|
4544
|
-
}
|
|
4545
|
-
} else {
|
|
4546
|
-
fs19.writeFileSync(p, `${header}${entry}`);
|
|
4547
|
-
}
|
|
4548
|
-
}
|
|
4549
|
-
function git3(args, cwd, timeout = 6e4) {
|
|
4550
|
-
return execFileSync15("git", args, {
|
|
4551
|
-
encoding: "utf-8",
|
|
4552
|
-
timeout,
|
|
4553
|
-
cwd,
|
|
4554
|
-
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
4555
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4556
|
-
}).trim();
|
|
4557
|
-
}
|
|
4558
|
-
function lastReleaseTag(cwd) {
|
|
4559
|
-
try {
|
|
4560
|
-
return git3(["describe", "--tags", "--abbrev=0", "--match", "v*"], cwd);
|
|
4561
|
-
} catch {
|
|
4562
|
-
return null;
|
|
4563
|
-
}
|
|
4564
|
-
}
|
|
4565
|
-
function remoteBranchExists(branch, cwd) {
|
|
4566
|
-
try {
|
|
4567
|
-
const out = git3(["ls-remote", "--heads", "origin", branch], cwd, 3e4);
|
|
4568
|
-
return out.length > 0;
|
|
4569
|
-
} catch {
|
|
4570
|
-
return false;
|
|
4571
|
-
}
|
|
4572
|
-
}
|
|
4573
|
-
function findOpenPrForBranch(branch, cwd) {
|
|
4574
|
-
try {
|
|
4575
|
-
const out = gh2(["pr", "list", "--head", branch, "--state", "open", "--json", "url", "--limit", "1"], { cwd });
|
|
4576
|
-
const parsed = JSON.parse(out || "[]");
|
|
4577
|
-
const first = parsed[0];
|
|
4578
|
-
return first?.url ?? null;
|
|
4579
|
-
} catch {
|
|
4580
|
-
return null;
|
|
4581
|
-
}
|
|
4582
|
-
}
|
|
4583
|
-
function runShell(cmd, cwd, timeoutMs) {
|
|
4584
|
-
const r = spawnSync(cmd, {
|
|
4585
|
-
cwd,
|
|
4586
|
-
shell: true,
|
|
4587
|
-
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
|
|
4588
|
-
encoding: "utf-8",
|
|
4589
|
-
timeout: timeoutMs
|
|
4590
|
-
});
|
|
4591
|
-
return { exitCode: r.status ?? -1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
4592
|
-
}
|
|
4593
|
-
var releaseFlow = async (ctx) => {
|
|
4594
|
-
const mode = ctx.args.mode ?? "prepare";
|
|
4595
|
-
const bump = ctx.args.bump ?? "patch";
|
|
4596
|
-
const dryRun = ctx.args["dry-run"] === true || ctx.args.dryRun === true;
|
|
4597
|
-
const prefer = ctx.args.prefer ?? void 0;
|
|
4598
|
-
const issueNumber = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
|
|
4599
|
-
const cwd = ctx.cwd;
|
|
4600
|
-
const releaseCfg = ctx.config.release ?? {};
|
|
4601
|
-
const versionFiles = releaseCfg.versionFiles && releaseCfg.versionFiles.length > 0 ? releaseCfg.versionFiles : ["package.json"];
|
|
4602
|
-
const timeoutMs = releaseCfg.timeoutMs ?? 6e5;
|
|
4603
|
-
ctx.skipAgent = true;
|
|
4604
|
-
if (mode === "prepare") {
|
|
4605
|
-
await runPrepare({ cwd, bump, dryRun, prefer, versionFiles, ctx });
|
|
4606
|
-
} else if (mode === "finalize") {
|
|
4607
|
-
await runFinalize({ cwd, dryRun, timeoutMs, releaseCfg, ctx });
|
|
4608
|
-
} else {
|
|
4609
|
-
ctx.output.exitCode = 64;
|
|
4610
|
-
ctx.output.reason = `release: unknown mode '${mode}'`;
|
|
4611
|
-
}
|
|
4612
|
-
notifyIssue(issueNumber, buildIssueNotice(mode, dryRun, ctx), cwd);
|
|
4519
|
+
// src/scripts/recordOutcome.ts
|
|
4520
|
+
var recordOutcome = async (ctx, profile) => {
|
|
4521
|
+
const seg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
4522
|
+
const ok = (ctx.output.exitCode ?? 0) === 0;
|
|
4523
|
+
const action = {
|
|
4524
|
+
type: ok ? `${seg}_COMPLETED` : `${seg}_FAILED`,
|
|
4525
|
+
payload: {
|
|
4526
|
+
exitCode: ctx.output.exitCode ?? 0,
|
|
4527
|
+
reason: ctx.output.reason,
|
|
4528
|
+
prUrl: ctx.output.prUrl
|
|
4529
|
+
},
|
|
4530
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4531
|
+
};
|
|
4532
|
+
ctx.data.action = action;
|
|
4613
4533
|
};
|
|
4614
|
-
function buildIssueNotice(mode, dryRun, ctx) {
|
|
4615
|
-
const exit = ctx.output.exitCode ?? 0;
|
|
4616
|
-
const url = ctx.output.prUrl;
|
|
4617
|
-
const reason = ctx.output.reason;
|
|
4618
|
-
const label = mode === "finalize" ? "release finalize" : mode === "prepare" ? "release prepare" : `release ${mode}`;
|
|
4619
|
-
if (exit !== 0) {
|
|
4620
|
-
const suffix = url ? ` \u2014 ${url}` : "";
|
|
4621
|
-
return `\u26A0\uFE0F kody ${label} failed: ${truncate2(reason ?? "unknown error", 1500)}${suffix}`;
|
|
4622
|
-
}
|
|
4623
|
-
if (dryRun) {
|
|
4624
|
-
return `\u2139\uFE0F kody ${label} (dry-run): ${reason ?? "plan printed, no changes applied"}`;
|
|
4625
|
-
}
|
|
4626
|
-
if (mode === "prepare") {
|
|
4627
|
-
return url ? `\u2705 kody release PR opened: ${url}` : "\u2705 kody release prepared";
|
|
4628
|
-
}
|
|
4629
|
-
if (mode === "finalize") {
|
|
4630
|
-
return url ? `\u2705 kody release published: ${url}` : "\u2705 kody release finalized (tag pushed)";
|
|
4631
|
-
}
|
|
4632
|
-
return `\u2705 kody ${label} complete`;
|
|
4633
|
-
}
|
|
4634
|
-
async function runPrepare(args) {
|
|
4635
|
-
const { cwd, bump, dryRun, prefer, versionFiles, ctx } = args;
|
|
4636
|
-
const pkgPath = path17.join(cwd, "package.json");
|
|
4637
|
-
if (!fs19.existsSync(pkgPath)) {
|
|
4638
|
-
ctx.output.exitCode = 99;
|
|
4639
|
-
ctx.output.reason = "release prepare: package.json not found";
|
|
4640
|
-
return;
|
|
4641
|
-
}
|
|
4642
|
-
const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
|
|
4643
|
-
if (typeof pkg.version !== "string") {
|
|
4644
|
-
ctx.output.exitCode = 99;
|
|
4645
|
-
ctx.output.reason = "release prepare: package.json has no version";
|
|
4646
|
-
return;
|
|
4647
|
-
}
|
|
4648
|
-
const oldVersion = pkg.version;
|
|
4649
|
-
const newVersion = bumpVersion(oldVersion, bump);
|
|
4650
|
-
const tag = `v${newVersion}`;
|
|
4651
|
-
process.stdout.write(`\u2192 release prepare: ${oldVersion} \u2192 ${newVersion} (${bump})
|
|
4652
|
-
`);
|
|
4653
|
-
if (dryRun) {
|
|
4654
|
-
ctx.output.exitCode = 0;
|
|
4655
|
-
ctx.output.reason = `dry-run \u2014 would bump to ${newVersion}${prefer ? ` (--prefer ${prefer})` : ""}`;
|
|
4656
|
-
process.stdout.write(`RELEASE_PLAN=bump=${newVersion} tag=${tag}
|
|
4657
|
-
`);
|
|
4658
|
-
return;
|
|
4659
|
-
}
|
|
4660
|
-
const releaseBranch = `release/${tag}`;
|
|
4661
|
-
const collides = remoteBranchExists(releaseBranch, cwd);
|
|
4662
|
-
if (collides) {
|
|
4663
|
-
if (prefer === "theirs") {
|
|
4664
|
-
const existingPr = findOpenPrForBranch(releaseBranch, cwd);
|
|
4665
|
-
if (existingPr) {
|
|
4666
|
-
process.stdout.write(` reusing existing PR (--prefer theirs): ${existingPr}
|
|
4667
|
-
`);
|
|
4668
|
-
ctx.output.prUrl = existingPr;
|
|
4669
|
-
ctx.output.exitCode = 0;
|
|
4670
|
-
return;
|
|
4671
|
-
}
|
|
4672
|
-
ctx.output.exitCode = 4;
|
|
4673
|
-
ctx.output.reason = `release prepare --prefer theirs: ${releaseBranch} exists on remote but has no open PR \u2014 nothing to reuse`;
|
|
4674
|
-
return;
|
|
4675
|
-
}
|
|
4676
|
-
if (prefer !== "ours") {
|
|
4677
|
-
ctx.output.exitCode = 4;
|
|
4678
|
-
ctx.output.reason = `release prepare: branch ${releaseBranch} already exists on remote. Use --prefer ours to force-push, or --prefer theirs to reuse the existing PR.`;
|
|
4679
|
-
return;
|
|
4680
|
-
}
|
|
4681
|
-
process.stdout.write(` branch ${releaseBranch} exists on remote \u2014 will force-push (--prefer ours)
|
|
4682
|
-
`);
|
|
4683
|
-
}
|
|
4684
|
-
const touched = [];
|
|
4685
|
-
for (const f of versionFiles) {
|
|
4686
|
-
if (updateVersionInFile(f, newVersion, cwd)) touched.push(f);
|
|
4687
|
-
}
|
|
4688
|
-
if (touched.length === 0) {
|
|
4689
|
-
ctx.output.exitCode = 1;
|
|
4690
|
-
ctx.output.reason = `release prepare: no version strings updated (files: ${versionFiles.join(", ")})`;
|
|
4691
|
-
return;
|
|
4692
|
-
}
|
|
4693
|
-
process.stdout.write(` wrote ${touched.join(", ")}
|
|
4694
|
-
`);
|
|
4695
|
-
const entry = generateChangelog(cwd, newVersion, lastReleaseTag(cwd));
|
|
4696
|
-
prependChangelog(cwd, entry);
|
|
4697
|
-
process.stdout.write(` wrote CHANGELOG.md
|
|
4698
|
-
`);
|
|
4699
|
-
try {
|
|
4700
|
-
git3(["checkout", "-b", releaseBranch], cwd);
|
|
4701
|
-
for (const f of [...touched, "CHANGELOG.md"]) git3(["add", "--", f], cwd);
|
|
4702
|
-
git3(["commit", "--no-gpg-sign", "-m", `chore: release ${tag}`], cwd);
|
|
4703
|
-
const pushArgs = collides && prefer === "ours" ? ["push", "-u", "--force-with-lease", "origin", releaseBranch] : ["push", "-u", "origin", releaseBranch];
|
|
4704
|
-
git3(pushArgs, cwd, 12e4);
|
|
4705
|
-
} catch (err) {
|
|
4706
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4707
|
-
ctx.output.exitCode = 4;
|
|
4708
|
-
ctx.output.reason = `release prepare: git commit/push failed: ${msg}`;
|
|
4709
|
-
return;
|
|
4710
|
-
}
|
|
4711
|
-
const base = ctx.config.git.defaultBranch;
|
|
4712
|
-
const title = `chore: release ${tag}`;
|
|
4713
|
-
const bodyMax = 6e4;
|
|
4714
|
-
const rawEntry = entry.length > bodyMax ? `${entry.slice(0, bodyMax)}
|
|
4715
|
-
|
|
4716
|
-
_\u2026 truncated; see CHANGELOG.md_` : entry;
|
|
4717
|
-
const body = `Automated release PR opened by kody.
|
|
4718
|
-
|
|
4719
|
-
${rawEntry}
|
|
4720
|
-
|
|
4721
|
-
Merge this and then run \`kody release --mode finalize\`.`;
|
|
4722
|
-
let prUrl = "";
|
|
4723
|
-
const preexistingPr = collides && prefer === "ours" ? findOpenPrForBranch(releaseBranch, cwd) : null;
|
|
4724
|
-
if (preexistingPr) {
|
|
4725
|
-
process.stdout.write(` PR already open for ${releaseBranch}: ${preexistingPr}
|
|
4726
|
-
`);
|
|
4727
|
-
prUrl = preexistingPr;
|
|
4728
|
-
} else {
|
|
4729
|
-
try {
|
|
4730
|
-
prUrl = gh2(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
|
|
4731
|
-
input: body,
|
|
4732
|
-
cwd
|
|
4733
|
-
}).trim();
|
|
4734
|
-
} catch (err) {
|
|
4735
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4736
|
-
ctx.output.exitCode = 4;
|
|
4737
|
-
ctx.output.reason = `release prepare: gh pr create failed: ${msg}`;
|
|
4738
|
-
return;
|
|
4739
|
-
}
|
|
4740
|
-
}
|
|
4741
|
-
ctx.output.prUrl = prUrl;
|
|
4742
|
-
ctx.output.exitCode = 0;
|
|
4743
|
-
process.stdout.write(`RELEASE_PR=${prUrl}
|
|
4744
|
-
`);
|
|
4745
|
-
}
|
|
4746
|
-
async function runFinalize(args) {
|
|
4747
|
-
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
4748
|
-
const pkgPath = path17.join(cwd, "package.json");
|
|
4749
|
-
const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
|
|
4750
|
-
if (typeof pkg.version !== "string") {
|
|
4751
|
-
ctx.output.exitCode = 99;
|
|
4752
|
-
ctx.output.reason = "release finalize: package.json has no version";
|
|
4753
|
-
return;
|
|
4754
|
-
}
|
|
4755
|
-
const version = pkg.version;
|
|
4756
|
-
const tag = `v${version}`;
|
|
4757
|
-
process.stdout.write(`\u2192 release finalize: ${tag}
|
|
4758
|
-
`);
|
|
4759
|
-
try {
|
|
4760
|
-
git3(["rev-parse", "--verify", tag], cwd);
|
|
4761
|
-
ctx.output.exitCode = 1;
|
|
4762
|
-
ctx.output.reason = `release finalize: tag ${tag} already exists`;
|
|
4763
|
-
return;
|
|
4764
|
-
} catch {
|
|
4765
|
-
}
|
|
4766
|
-
if (dryRun) {
|
|
4767
|
-
ctx.output.exitCode = 0;
|
|
4768
|
-
ctx.output.reason = `dry-run \u2014 would tag + publish ${tag}`;
|
|
4769
|
-
return;
|
|
4770
|
-
}
|
|
4771
|
-
if (releaseCfg.e2eCommand && releaseCfg.e2eCommand.trim().length > 0) {
|
|
4772
|
-
const cmd = releaseCfg.e2eCommand.replace(/\$VERSION/g, version);
|
|
4773
|
-
process.stdout.write(` E2E gate: ${cmd}
|
|
4774
|
-
`);
|
|
4775
|
-
const r = runShell(cmd, cwd, timeoutMs);
|
|
4776
|
-
if (r.exitCode !== 0) {
|
|
4777
|
-
ctx.output.exitCode = 2;
|
|
4778
|
-
ctx.output.reason = `release finalize: E2E gate failed (exit ${r.exitCode}): ${truncate2(r.stderr, 600)}`;
|
|
4779
|
-
return;
|
|
4780
|
-
}
|
|
4781
|
-
}
|
|
4782
|
-
try {
|
|
4783
|
-
git3(["tag", "-a", tag, "-m", `Release ${tag}`], cwd);
|
|
4784
|
-
git3(["push", "origin", tag], cwd, 12e4);
|
|
4785
|
-
} catch (err) {
|
|
4786
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4787
|
-
ctx.output.exitCode = 4;
|
|
4788
|
-
ctx.output.reason = `release finalize: tag/push failed: ${msg}`;
|
|
4789
|
-
return;
|
|
4790
|
-
}
|
|
4791
|
-
let publishStatus = "skipped";
|
|
4792
|
-
if (releaseCfg.publishCommand && releaseCfg.publishCommand.trim().length > 0) {
|
|
4793
|
-
const cmd = releaseCfg.publishCommand.replace(/\$VERSION/g, version);
|
|
4794
|
-
process.stdout.write(` publish: ${cmd}
|
|
4795
|
-
`);
|
|
4796
|
-
const r = runShell(cmd, cwd, timeoutMs);
|
|
4797
|
-
publishStatus = r.exitCode === 0 ? "ok" : "failed";
|
|
4798
|
-
if (r.exitCode !== 0) {
|
|
4799
|
-
process.stderr.write(`[kody release] publishCommand exit ${r.exitCode}
|
|
4800
|
-
${truncate2(r.stderr, 2e3)}
|
|
4801
|
-
`);
|
|
4802
|
-
}
|
|
4803
|
-
}
|
|
4804
|
-
let releaseUrl = "";
|
|
4805
|
-
try {
|
|
4806
|
-
const releaseArgs = ["release", "create", tag, "--title", tag, "--notes", `Release ${tag} \u2014 automated by kody.`];
|
|
4807
|
-
if (releaseCfg.draftRelease) releaseArgs.push("--draft");
|
|
4808
|
-
releaseUrl = gh2(releaseArgs, { cwd }).trim();
|
|
4809
|
-
} catch (err) {
|
|
4810
|
-
process.stderr.write(
|
|
4811
|
-
`[kody release] gh release create failed: ${err instanceof Error ? err.message : String(err)}
|
|
4812
|
-
`
|
|
4813
|
-
);
|
|
4814
|
-
}
|
|
4815
|
-
if (releaseCfg.notifyCommand && releaseCfg.notifyCommand.trim().length > 0) {
|
|
4816
|
-
const cmd = releaseCfg.notifyCommand.replace(/\$VERSION/g, version);
|
|
4817
|
-
runShell(cmd, cwd, timeoutMs);
|
|
4818
|
-
}
|
|
4819
|
-
if (releaseUrl) ctx.output.prUrl = releaseUrl;
|
|
4820
|
-
if (publishStatus === "failed") {
|
|
4821
|
-
ctx.output.exitCode = 1;
|
|
4822
|
-
ctx.output.reason = `release finalize: tag + gh release created, but publishCommand failed`;
|
|
4823
|
-
return;
|
|
4824
|
-
}
|
|
4825
|
-
ctx.output.exitCode = 0;
|
|
4826
|
-
process.stdout.write(`RELEASE_TAG=${tag}
|
|
4827
|
-
`);
|
|
4828
|
-
if (releaseUrl) process.stdout.write(`RELEASE_URL=${releaseUrl}
|
|
4829
|
-
`);
|
|
4830
|
-
}
|
|
4831
4534
|
|
|
4832
4535
|
// src/scripts/requireFeedbackActions.ts
|
|
4833
4536
|
var MIN_ITEMS = 1;
|
|
@@ -5137,6 +4840,16 @@ function synthesizeAction(ctx) {
|
|
|
5137
4840
|
};
|
|
5138
4841
|
}
|
|
5139
4842
|
|
|
4843
|
+
// src/scripts/setCommentTarget.ts
|
|
4844
|
+
var setCommentTarget = async (ctx, _profile, args) => {
|
|
4845
|
+
const type = args?.type ?? "issue";
|
|
4846
|
+
const argName = type === "pr" ? "pr" : "issue";
|
|
4847
|
+
const num = ctx.args[argName];
|
|
4848
|
+
if (typeof num !== "number" || num <= 0) return;
|
|
4849
|
+
ctx.data.commentTargetType = type;
|
|
4850
|
+
ctx.data.commentTargetNumber = num;
|
|
4851
|
+
};
|
|
4852
|
+
|
|
5140
4853
|
// src/scripts/setLifecycleLabel.ts
|
|
5141
4854
|
var setLifecycleLabel = async (ctx, _profile, args) => {
|
|
5142
4855
|
const label = args?.label;
|
|
@@ -5188,7 +4901,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
5188
4901
|
|
|
5189
4902
|
// src/scripts/startFlow.ts
|
|
5190
4903
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
5191
|
-
var
|
|
4904
|
+
var API_TIMEOUT_MS8 = 3e4;
|
|
5192
4905
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
5193
4906
|
const entry = args?.entry;
|
|
5194
4907
|
if (!entry) {
|
|
@@ -5222,7 +4935,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
5222
4935
|
const body = `@kody ${next}`;
|
|
5223
4936
|
try {
|
|
5224
4937
|
execFileSync18("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
5225
|
-
timeout:
|
|
4938
|
+
timeout: API_TIMEOUT_MS8,
|
|
5226
4939
|
cwd,
|
|
5227
4940
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5228
4941
|
});
|
|
@@ -5508,7 +5221,7 @@ var writeIssueStateComment = async (ctx, _profile, _agentResult, args) => {
|
|
|
5508
5221
|
};
|
|
5509
5222
|
|
|
5510
5223
|
// src/scripts/writeRunSummary.ts
|
|
5511
|
-
import * as
|
|
5224
|
+
import * as fs19 from "fs";
|
|
5512
5225
|
var writeRunSummary = async (ctx, profile) => {
|
|
5513
5226
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
5514
5227
|
if (!summaryPath) return;
|
|
@@ -5530,7 +5243,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
5530
5243
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
5531
5244
|
lines.push("");
|
|
5532
5245
|
try {
|
|
5533
|
-
|
|
5246
|
+
fs19.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
5534
5247
|
`);
|
|
5535
5248
|
} catch {
|
|
5536
5249
|
}
|
|
@@ -5545,7 +5258,6 @@ var preflightScripts = {
|
|
|
5545
5258
|
reviewFlow,
|
|
5546
5259
|
syncFlow,
|
|
5547
5260
|
initFlow,
|
|
5548
|
-
releaseFlow,
|
|
5549
5261
|
watchStalePrsFlow,
|
|
5550
5262
|
loadTaskState,
|
|
5551
5263
|
loadIssueContext,
|
|
@@ -5559,6 +5271,7 @@ var preflightScripts = {
|
|
|
5559
5271
|
discoverQaContext,
|
|
5560
5272
|
resolvePreviewUrl,
|
|
5561
5273
|
composePrompt,
|
|
5274
|
+
setCommentTarget,
|
|
5562
5275
|
setLifecycleLabel,
|
|
5563
5276
|
skipAgent,
|
|
5564
5277
|
classifyByLabel,
|
|
@@ -5590,7 +5303,10 @@ var postflightScripts = {
|
|
|
5590
5303
|
finishFlow,
|
|
5591
5304
|
advanceFlow,
|
|
5592
5305
|
persistFlowState,
|
|
5593
|
-
postClassification
|
|
5306
|
+
postClassification,
|
|
5307
|
+
notifyTerminal,
|
|
5308
|
+
recordOutcome,
|
|
5309
|
+
mergeReleasePr
|
|
5594
5310
|
};
|
|
5595
5311
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
5596
5312
|
...Object.keys(preflightScripts),
|
|
@@ -5614,22 +5330,22 @@ function firstRequiredFailure(results, tools) {
|
|
|
5614
5330
|
}
|
|
5615
5331
|
function verifyOne(tool, cwd) {
|
|
5616
5332
|
const result = { name: tool.name, present: false, verified: false };
|
|
5617
|
-
let present =
|
|
5333
|
+
let present = runShell(tool.install.checkCommand, cwd);
|
|
5618
5334
|
if (!present && tool.install.installCommand) {
|
|
5619
|
-
|
|
5620
|
-
present =
|
|
5335
|
+
runShell(tool.install.installCommand, cwd, 12e4);
|
|
5336
|
+
present = runShell(tool.install.checkCommand, cwd);
|
|
5621
5337
|
}
|
|
5622
5338
|
result.present = present;
|
|
5623
5339
|
if (!present) {
|
|
5624
5340
|
result.error = `tool "${tool.name}" not on PATH (check: ${tool.install.checkCommand})`;
|
|
5625
5341
|
return result;
|
|
5626
5342
|
}
|
|
5627
|
-
const verified =
|
|
5343
|
+
const verified = runShell(tool.verify, cwd);
|
|
5628
5344
|
result.verified = verified;
|
|
5629
5345
|
if (!verified) result.error = `tool "${tool.name}" failed verify: ${tool.verify}`;
|
|
5630
5346
|
return result;
|
|
5631
5347
|
}
|
|
5632
|
-
function
|
|
5348
|
+
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
5633
5349
|
try {
|
|
5634
5350
|
execFileSync20("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
5635
5351
|
return true;
|
|
@@ -5699,9 +5415,9 @@ async function runExecutable(profileName, input) {
|
|
|
5699
5415
|
data: {},
|
|
5700
5416
|
output: { exitCode: 0 }
|
|
5701
5417
|
};
|
|
5702
|
-
const ndjsonDir =
|
|
5418
|
+
const ndjsonDir = path17.join(input.cwd, ".kody");
|
|
5703
5419
|
const invokeAgent = async (prompt) => {
|
|
5704
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
5420
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path17.isAbsolute(p) ? p : path17.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
5705
5421
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
5706
5422
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
5707
5423
|
return runAgent({
|
|
@@ -5777,17 +5493,17 @@ async function runExecutable(profileName, input) {
|
|
|
5777
5493
|
}
|
|
5778
5494
|
}
|
|
5779
5495
|
function resolveProfilePath(profileName) {
|
|
5780
|
-
const here =
|
|
5496
|
+
const here = path17.dirname(new URL(import.meta.url).pathname);
|
|
5781
5497
|
const candidates = [
|
|
5782
|
-
|
|
5498
|
+
path17.join(here, "executables", profileName, "profile.json"),
|
|
5783
5499
|
// same-dir sibling (dev)
|
|
5784
|
-
|
|
5500
|
+
path17.join(here, "..", "executables", profileName, "profile.json"),
|
|
5785
5501
|
// up one (prod: dist/bin → dist/executables)
|
|
5786
|
-
|
|
5502
|
+
path17.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
5787
5503
|
// fallback
|
|
5788
5504
|
];
|
|
5789
5505
|
for (const c of candidates) {
|
|
5790
|
-
if (
|
|
5506
|
+
if (fs20.existsSync(c)) return c;
|
|
5791
5507
|
}
|
|
5792
5508
|
return candidates[0];
|
|
5793
5509
|
}
|
|
@@ -5880,8 +5596,8 @@ function finish(out) {
|
|
|
5880
5596
|
var SHELL_TIMEOUT_MS = 3e5;
|
|
5881
5597
|
function runShellEntry(entry, ctx, profile) {
|
|
5882
5598
|
const shellName = entry.shell;
|
|
5883
|
-
const shellPath =
|
|
5884
|
-
if (!
|
|
5599
|
+
const shellPath = path17.join(profile.dir, shellName);
|
|
5600
|
+
if (!fs20.existsSync(shellPath)) {
|
|
5885
5601
|
ctx.skipAgent = true;
|
|
5886
5602
|
ctx.output.exitCode = 99;
|
|
5887
5603
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -5891,9 +5607,12 @@ function runShellEntry(entry, ctx, profile) {
|
|
|
5891
5607
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
5892
5608
|
for (const [k, v] of Object.entries(ctx.args)) {
|
|
5893
5609
|
if (v === void 0 || v === null) continue;
|
|
5894
|
-
env[`KODY_ARG_${k
|
|
5610
|
+
env[`KODY_ARG_${envKey(k)}`] = String(v);
|
|
5611
|
+
}
|
|
5612
|
+
for (const [k, v] of flattenConfig(ctx.config)) {
|
|
5613
|
+
env[`KODY_CFG_${k}`] = v;
|
|
5895
5614
|
}
|
|
5896
|
-
const r =
|
|
5615
|
+
const r = spawnSync("bash", [shellPath, ...positional], {
|
|
5897
5616
|
cwd: ctx.cwd,
|
|
5898
5617
|
encoding: "utf-8",
|
|
5899
5618
|
env,
|
|
@@ -5908,6 +5627,10 @@ function runShellEntry(entry, ctx, profile) {
|
|
|
5908
5627
|
ctx.skipAgent = true;
|
|
5909
5628
|
if (ctx.output.exitCode === void 0) ctx.output.exitCode = 0;
|
|
5910
5629
|
}
|
|
5630
|
+
const prUrlMatch = stdout.match(/^KODY_PR_URL=(.+)$/m);
|
|
5631
|
+
if (prUrlMatch?.[1]) ctx.output.prUrl = prUrlMatch[1].trim();
|
|
5632
|
+
const reasonMatch = stdout.match(/^KODY_REASON=(.+)$/m);
|
|
5633
|
+
if (reasonMatch?.[1]) ctx.output.reason = reasonMatch[1].trim();
|
|
5911
5634
|
const exit = r.status ?? -1;
|
|
5912
5635
|
if (exit !== 0) {
|
|
5913
5636
|
ctx.skipAgent = true;
|
|
@@ -5920,6 +5643,24 @@ function runShellEntry(entry, ctx, profile) {
|
|
|
5920
5643
|
}
|
|
5921
5644
|
}
|
|
5922
5645
|
}
|
|
5646
|
+
function envKey(name) {
|
|
5647
|
+
return name.toUpperCase().replace(/-/g, "_");
|
|
5648
|
+
}
|
|
5649
|
+
function flattenConfig(obj, prefix = "") {
|
|
5650
|
+
const out = [];
|
|
5651
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
5652
|
+
if (v === null || v === void 0) continue;
|
|
5653
|
+
const key = prefix ? `${prefix}_${envKey(k)}` : envKey(k);
|
|
5654
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
5655
|
+
out.push([key, String(v)]);
|
|
5656
|
+
} else if (Array.isArray(v)) {
|
|
5657
|
+
out.push([key, JSON.stringify(v)]);
|
|
5658
|
+
} else if (typeof v === "object") {
|
|
5659
|
+
out.push(...flattenConfig(v, key));
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
return out;
|
|
5663
|
+
}
|
|
5923
5664
|
|
|
5924
5665
|
// src/kody-cli.ts
|
|
5925
5666
|
var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
|
|
@@ -6006,9 +5747,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
6006
5747
|
return token;
|
|
6007
5748
|
}
|
|
6008
5749
|
function detectPackageManager2(cwd) {
|
|
6009
|
-
if (
|
|
6010
|
-
if (
|
|
6011
|
-
if (
|
|
5750
|
+
if (fs21.existsSync(path18.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5751
|
+
if (fs21.existsSync(path18.join(cwd, "yarn.lock"))) return "yarn";
|
|
5752
|
+
if (fs21.existsSync(path18.join(cwd, "bun.lockb"))) return "bun";
|
|
6012
5753
|
return "npm";
|
|
6013
5754
|
}
|
|
6014
5755
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -6088,11 +5829,11 @@ function configureGitIdentity(cwd) {
|
|
|
6088
5829
|
}
|
|
6089
5830
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
6090
5831
|
if (!issueNumber) return;
|
|
6091
|
-
const logPath =
|
|
5832
|
+
const logPath = path18.join(cwd, ".kody", "last-run.jsonl");
|
|
6092
5833
|
let tail = "";
|
|
6093
5834
|
try {
|
|
6094
|
-
if (
|
|
6095
|
-
const content =
|
|
5835
|
+
if (fs21.existsSync(logPath)) {
|
|
5836
|
+
const content = fs21.readFileSync(logPath, "utf-8");
|
|
6096
5837
|
tail = content.slice(-3e3);
|
|
6097
5838
|
}
|
|
6098
5839
|
} catch {
|
|
@@ -6117,7 +5858,7 @@ async function runCi(argv) {
|
|
|
6117
5858
|
return 0;
|
|
6118
5859
|
}
|
|
6119
5860
|
const args = parseCiArgs(argv);
|
|
6120
|
-
const cwd = args.cwd ?
|
|
5861
|
+
const cwd = args.cwd ? path18.resolve(args.cwd) : process.cwd();
|
|
6121
5862
|
let earlyConfig;
|
|
6122
5863
|
try {
|
|
6123
5864
|
earlyConfig = loadConfig(cwd);
|
|
@@ -6255,9 +5996,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
6255
5996
|
return result;
|
|
6256
5997
|
}
|
|
6257
5998
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
6258
|
-
const sessionFile =
|
|
6259
|
-
const eventsFile =
|
|
6260
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
5999
|
+
const sessionFile = path19.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
6000
|
+
const eventsFile = path19.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
6001
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs22.existsSync(path19.join(cwd, p)));
|
|
6261
6002
|
if (paths.length === 0) return;
|
|
6262
6003
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
6263
6004
|
try {
|
|
@@ -6295,7 +6036,7 @@ async function runChat(argv) {
|
|
|
6295
6036
|
${CHAT_HELP}`);
|
|
6296
6037
|
return 64;
|
|
6297
6038
|
}
|
|
6298
|
-
const cwd = args.cwd ?
|
|
6039
|
+
const cwd = args.cwd ? path19.resolve(args.cwd) : process.cwd();
|
|
6299
6040
|
const sessionId = args.sessionId;
|
|
6300
6041
|
const unpackedSecrets = unpackAllSecrets();
|
|
6301
6042
|
if (unpackedSecrets > 0) {
|