@kody-ade/kody-engine 0.3.20 → 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 +206 -464
- 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";
|
|
@@ -2671,6 +2660,12 @@ function truncate2(s, maxBytes) {
|
|
|
2671
2660
|
if (s.length <= maxBytes) return s;
|
|
2672
2661
|
return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
|
|
2673
2662
|
}
|
|
2663
|
+
function parsePrNumber(url) {
|
|
2664
|
+
const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
2665
|
+
if (!m) return null;
|
|
2666
|
+
const n = parseInt(m[1], 10);
|
|
2667
|
+
return Number.isFinite(n) ? n : null;
|
|
2668
|
+
}
|
|
2674
2669
|
function getPr(prNumber, cwd) {
|
|
2675
2670
|
const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
|
|
2676
2671
|
cwd
|
|
@@ -3138,15 +3133,16 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
3138
3133
|
if (!issueNumber) return;
|
|
3139
3134
|
const label = typeof args?.label === "string" ? args.label : void 0;
|
|
3140
3135
|
if (label && label.startsWith(KODY_NAMESPACE)) {
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3136
|
+
const spec = {
|
|
3137
|
+
label,
|
|
3138
|
+
color: typeof args?.color === "string" ? args.color : void 0,
|
|
3139
|
+
description: typeof args?.description === "string" ? args.description : void 0
|
|
3140
|
+
};
|
|
3141
|
+
setKodyLabel(issueNumber, spec, ctx.cwd);
|
|
3142
|
+
const prNumber = state?.core.prUrl ? parsePrNumber(state.core.prUrl) : null;
|
|
3143
|
+
if (prNumber && prNumber !== issueNumber) {
|
|
3144
|
+
setKodyLabel(prNumber, spec, ctx.cwd);
|
|
3145
|
+
}
|
|
3150
3146
|
}
|
|
3151
3147
|
const icon = STATUS_ICON[reason] ?? "\u2139\uFE0F";
|
|
3152
3148
|
const prSuffix = state?.core.prUrl ? `
|
|
@@ -4037,6 +4033,53 @@ var loadTaskState = async (ctx) => {
|
|
|
4037
4033
|
ctx.data.taskState = readTaskState(target, number, ctx.cwd);
|
|
4038
4034
|
};
|
|
4039
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
|
+
|
|
4040
4083
|
// src/scripts/mirrorStateToPr.ts
|
|
4041
4084
|
var mirrorStateToPr = async (ctx) => {
|
|
4042
4085
|
const issueNumber = ctx.data.commentTargetNumber;
|
|
@@ -4044,7 +4087,7 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
4044
4087
|
if (!issueNumber || issueTarget !== "issue") return;
|
|
4045
4088
|
const prUrl = ctx.output.prUrl ?? ctx.data.prResult?.url;
|
|
4046
4089
|
if (!prUrl) return;
|
|
4047
|
-
const prNumber =
|
|
4090
|
+
const prNumber = parsePrNumber3(prUrl);
|
|
4048
4091
|
if (!prNumber) return;
|
|
4049
4092
|
let state;
|
|
4050
4093
|
try {
|
|
@@ -4062,18 +4105,49 @@ var mirrorStateToPr = async (ctx) => {
|
|
|
4062
4105
|
);
|
|
4063
4106
|
}
|
|
4064
4107
|
};
|
|
4065
|
-
function
|
|
4108
|
+
function parsePrNumber3(prUrl) {
|
|
4066
4109
|
const m = prUrl.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
4067
4110
|
if (!m) return null;
|
|
4068
4111
|
const n = parseInt(m[1], 10);
|
|
4069
4112
|
return Number.isFinite(n) ? n : null;
|
|
4070
4113
|
}
|
|
4071
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
|
+
|
|
4072
4146
|
// src/scripts/parseAgentResult.ts
|
|
4073
4147
|
var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
4074
4148
|
if (!agentResult) {
|
|
4075
4149
|
ctx.data.agentDone = false;
|
|
4076
|
-
ctx.data.action =
|
|
4150
|
+
ctx.data.action = makeAction2("AGENT_NOT_RUN", { reason: "no agent result" });
|
|
4077
4151
|
return;
|
|
4078
4152
|
}
|
|
4079
4153
|
const parsed = parseAgentResult(agentResult.finalText);
|
|
@@ -4088,17 +4162,17 @@ var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
|
4088
4162
|
ctx.data.agentError = agentResult.error;
|
|
4089
4163
|
const modeSeg = (ctx.args.mode ?? profile.name).replace(/-/g, "_").toUpperCase();
|
|
4090
4164
|
if (parsed.done) {
|
|
4091
|
-
ctx.data.action =
|
|
4165
|
+
ctx.data.action = makeAction2(`${modeSeg}_COMPLETED`, {
|
|
4092
4166
|
commitMessage: parsed.commitMessage,
|
|
4093
4167
|
prSummary: parsed.prSummary
|
|
4094
4168
|
});
|
|
4095
4169
|
} else {
|
|
4096
|
-
ctx.data.action =
|
|
4170
|
+
ctx.data.action = makeAction2(`${modeSeg}_FAILED`, {
|
|
4097
4171
|
reason: parsed.failureReason || agentResult.error || "unknown failure"
|
|
4098
4172
|
});
|
|
4099
4173
|
}
|
|
4100
4174
|
};
|
|
4101
|
-
function
|
|
4175
|
+
function makeAction2(type, payload) {
|
|
4102
4176
|
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4103
4177
|
}
|
|
4104
4178
|
|
|
@@ -4195,8 +4269,8 @@ var persistFlowState = async (ctx) => {
|
|
|
4195
4269
|
};
|
|
4196
4270
|
|
|
4197
4271
|
// src/scripts/postClassification.ts
|
|
4198
|
-
import { execFileSync as
|
|
4199
|
-
var
|
|
4272
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
4273
|
+
var API_TIMEOUT_MS7 = 3e4;
|
|
4200
4274
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
4201
4275
|
var postClassification = async (ctx) => {
|
|
4202
4276
|
const issueNumber = ctx.args.issue;
|
|
@@ -4225,9 +4299,9 @@ var postClassification = async (ctx) => {
|
|
|
4225
4299
|
ctx.cwd
|
|
4226
4300
|
);
|
|
4227
4301
|
try {
|
|
4228
|
-
|
|
4302
|
+
execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
4229
4303
|
cwd: ctx.cwd,
|
|
4230
|
-
timeout:
|
|
4304
|
+
timeout: API_TIMEOUT_MS7,
|
|
4231
4305
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4232
4306
|
});
|
|
4233
4307
|
} catch (err) {
|
|
@@ -4240,7 +4314,7 @@ var postClassification = async (ctx) => {
|
|
|
4240
4314
|
ctx.output.reason = "classify: dispatch failed";
|
|
4241
4315
|
return;
|
|
4242
4316
|
}
|
|
4243
|
-
ctx.data.action =
|
|
4317
|
+
ctx.data.action = makeAction3(`CLASSIFIED_AS_${classification.toUpperCase()}`, {
|
|
4244
4318
|
classification,
|
|
4245
4319
|
reason: reason ?? "",
|
|
4246
4320
|
source: ctx.data.classificationSource ?? "agent"
|
|
@@ -4259,15 +4333,15 @@ function parseClassification(prSummary) {
|
|
|
4259
4333
|
}
|
|
4260
4334
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
4261
4335
|
try {
|
|
4262
|
-
|
|
4336
|
+
execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
4263
4337
|
cwd,
|
|
4264
|
-
timeout:
|
|
4338
|
+
timeout: API_TIMEOUT_MS7,
|
|
4265
4339
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4266
4340
|
});
|
|
4267
4341
|
} catch {
|
|
4268
4342
|
}
|
|
4269
4343
|
}
|
|
4270
|
-
function
|
|
4344
|
+
function makeAction3(type, payload) {
|
|
4271
4345
|
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4272
4346
|
}
|
|
4273
4347
|
function failedAction(reason) {
|
|
@@ -4442,385 +4516,21 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
4442
4516
|
);
|
|
4443
4517
|
};
|
|
4444
4518
|
|
|
4445
|
-
// src/scripts/
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
}
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
let [major, minor, patch] = [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
|
|
4460
|
-
if (bump === "major") {
|
|
4461
|
-
major++;
|
|
4462
|
-
minor = 0;
|
|
4463
|
-
patch = 0;
|
|
4464
|
-
} else if (bump === "minor") {
|
|
4465
|
-
minor++;
|
|
4466
|
-
patch = 0;
|
|
4467
|
-
} else patch++;
|
|
4468
|
-
return `${major}.${minor}.${patch}`;
|
|
4469
|
-
}
|
|
4470
|
-
function updateVersionInFile(file, newVersion, cwd) {
|
|
4471
|
-
const abs = path17.join(cwd, file);
|
|
4472
|
-
if (!fs19.existsSync(abs)) return false;
|
|
4473
|
-
const content = fs19.readFileSync(abs, "utf-8");
|
|
4474
|
-
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
4475
|
-
if (updated === content) return false;
|
|
4476
|
-
fs19.writeFileSync(abs, updated);
|
|
4477
|
-
return true;
|
|
4478
|
-
}
|
|
4479
|
-
var FIRST_RELEASE_COMMIT_CAP = 100;
|
|
4480
|
-
function generateChangelog(cwd, newVersion, lastTag) {
|
|
4481
|
-
const logArgs = ["log", "--pretty=format:%s||%h", "--no-merges"];
|
|
4482
|
-
if (lastTag) logArgs.splice(1, 0, `${lastTag}..HEAD`);
|
|
4483
|
-
else logArgs.splice(1, 0, `-n${FIRST_RELEASE_COMMIT_CAP}`, "HEAD");
|
|
4484
|
-
let log = "";
|
|
4485
|
-
try {
|
|
4486
|
-
log = execFileSync15("git", logArgs, {
|
|
4487
|
-
cwd,
|
|
4488
|
-
encoding: "utf-8",
|
|
4489
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4490
|
-
}).trim();
|
|
4491
|
-
} catch {
|
|
4492
|
-
}
|
|
4493
|
-
const commits = log.split("\n").filter((l) => l.length > 0).map((line) => {
|
|
4494
|
-
const [subject, sha] = line.split("||");
|
|
4495
|
-
return { subject: subject ?? "", sha: sha ?? "" };
|
|
4496
|
-
}).filter((c) => !/^chore:\s*release\s+v\d/i.test(c.subject));
|
|
4497
|
-
const groups = { feat: [], fix: [], perf: [], refactor: [], docs: [], chore: [], other: [] };
|
|
4498
|
-
for (const c of commits) {
|
|
4499
|
-
const m = c.subject.match(/^(\w+)(?:\(.*?\))?\s*:\s*(.+)$/);
|
|
4500
|
-
const type = m?.[1]?.toLowerCase() ?? "other";
|
|
4501
|
-
const msg = m?.[2] ?? c.subject;
|
|
4502
|
-
const bucket = groups[type] ?? groups.other;
|
|
4503
|
-
bucket.push(`- ${msg} (${c.sha})`);
|
|
4504
|
-
}
|
|
4505
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4506
|
-
const parts = [`## v${newVersion} \u2014 ${date}`, ""];
|
|
4507
|
-
const labels = [
|
|
4508
|
-
["feat", "Features"],
|
|
4509
|
-
["fix", "Fixes"],
|
|
4510
|
-
["perf", "Performance"],
|
|
4511
|
-
["refactor", "Refactoring"],
|
|
4512
|
-
["docs", "Docs"],
|
|
4513
|
-
["chore", "Chores"],
|
|
4514
|
-
["other", "Other"]
|
|
4515
|
-
];
|
|
4516
|
-
for (const [key, label] of labels) {
|
|
4517
|
-
const items = groups[key];
|
|
4518
|
-
if (!items || items.length === 0) continue;
|
|
4519
|
-
parts.push(`### ${label}`);
|
|
4520
|
-
parts.push(...items);
|
|
4521
|
-
parts.push("");
|
|
4522
|
-
}
|
|
4523
|
-
if (parts.length === 2) parts.push("_No notable commits since the last release._", "");
|
|
4524
|
-
return parts.join("\n");
|
|
4525
|
-
}
|
|
4526
|
-
function prependChangelog(cwd, entry) {
|
|
4527
|
-
const p = path17.join(cwd, "CHANGELOG.md");
|
|
4528
|
-
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
4529
|
-
if (fs19.existsSync(p)) {
|
|
4530
|
-
const prior = fs19.readFileSync(p, "utf-8");
|
|
4531
|
-
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
4532
|
-
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
4533
|
-
fs19.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
4534
|
-
${entry}${prior.slice(idx + 1)}`);
|
|
4535
|
-
} else {
|
|
4536
|
-
fs19.writeFileSync(p, `${header}${entry}${prior}`);
|
|
4537
|
-
}
|
|
4538
|
-
} else {
|
|
4539
|
-
fs19.writeFileSync(p, `${header}${entry}`);
|
|
4540
|
-
}
|
|
4541
|
-
}
|
|
4542
|
-
function git3(args, cwd, timeout = 6e4) {
|
|
4543
|
-
return execFileSync15("git", args, {
|
|
4544
|
-
encoding: "utf-8",
|
|
4545
|
-
timeout,
|
|
4546
|
-
cwd,
|
|
4547
|
-
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
4548
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4549
|
-
}).trim();
|
|
4550
|
-
}
|
|
4551
|
-
function lastReleaseTag(cwd) {
|
|
4552
|
-
try {
|
|
4553
|
-
return git3(["describe", "--tags", "--abbrev=0", "--match", "v*"], cwd);
|
|
4554
|
-
} catch {
|
|
4555
|
-
return null;
|
|
4556
|
-
}
|
|
4557
|
-
}
|
|
4558
|
-
function remoteBranchExists(branch, cwd) {
|
|
4559
|
-
try {
|
|
4560
|
-
const out = git3(["ls-remote", "--heads", "origin", branch], cwd, 3e4);
|
|
4561
|
-
return out.length > 0;
|
|
4562
|
-
} catch {
|
|
4563
|
-
return false;
|
|
4564
|
-
}
|
|
4565
|
-
}
|
|
4566
|
-
function findOpenPrForBranch(branch, cwd) {
|
|
4567
|
-
try {
|
|
4568
|
-
const out = gh2(["pr", "list", "--head", branch, "--state", "open", "--json", "url", "--limit", "1"], { cwd });
|
|
4569
|
-
const parsed = JSON.parse(out || "[]");
|
|
4570
|
-
const first = parsed[0];
|
|
4571
|
-
return first?.url ?? null;
|
|
4572
|
-
} catch {
|
|
4573
|
-
return null;
|
|
4574
|
-
}
|
|
4575
|
-
}
|
|
4576
|
-
function runShell(cmd, cwd, timeoutMs) {
|
|
4577
|
-
const r = spawnSync(cmd, {
|
|
4578
|
-
cwd,
|
|
4579
|
-
shell: true,
|
|
4580
|
-
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
|
|
4581
|
-
encoding: "utf-8",
|
|
4582
|
-
timeout: timeoutMs
|
|
4583
|
-
});
|
|
4584
|
-
return { exitCode: r.status ?? -1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
4585
|
-
}
|
|
4586
|
-
var releaseFlow = async (ctx) => {
|
|
4587
|
-
const mode = ctx.args.mode ?? "prepare";
|
|
4588
|
-
const bump = ctx.args.bump ?? "patch";
|
|
4589
|
-
const dryRun = ctx.args["dry-run"] === true || ctx.args.dryRun === true;
|
|
4590
|
-
const prefer = ctx.args.prefer ?? void 0;
|
|
4591
|
-
const issueNumber = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
|
|
4592
|
-
const cwd = ctx.cwd;
|
|
4593
|
-
const releaseCfg = ctx.config.release ?? {};
|
|
4594
|
-
const versionFiles = releaseCfg.versionFiles && releaseCfg.versionFiles.length > 0 ? releaseCfg.versionFiles : ["package.json"];
|
|
4595
|
-
const timeoutMs = releaseCfg.timeoutMs ?? 6e5;
|
|
4596
|
-
ctx.skipAgent = true;
|
|
4597
|
-
if (mode === "prepare") {
|
|
4598
|
-
await runPrepare({ cwd, bump, dryRun, prefer, versionFiles, ctx });
|
|
4599
|
-
} else if (mode === "finalize") {
|
|
4600
|
-
await runFinalize({ cwd, dryRun, timeoutMs, releaseCfg, ctx });
|
|
4601
|
-
} else {
|
|
4602
|
-
ctx.output.exitCode = 64;
|
|
4603
|
-
ctx.output.reason = `release: unknown mode '${mode}'`;
|
|
4604
|
-
}
|
|
4605
|
-
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;
|
|
4606
4533
|
};
|
|
4607
|
-
function buildIssueNotice(mode, dryRun, ctx) {
|
|
4608
|
-
const exit = ctx.output.exitCode ?? 0;
|
|
4609
|
-
const url = ctx.output.prUrl;
|
|
4610
|
-
const reason = ctx.output.reason;
|
|
4611
|
-
const label = mode === "finalize" ? "release finalize" : mode === "prepare" ? "release prepare" : `release ${mode}`;
|
|
4612
|
-
if (exit !== 0) {
|
|
4613
|
-
const suffix = url ? ` \u2014 ${url}` : "";
|
|
4614
|
-
return `\u26A0\uFE0F kody ${label} failed: ${truncate2(reason ?? "unknown error", 1500)}${suffix}`;
|
|
4615
|
-
}
|
|
4616
|
-
if (dryRun) {
|
|
4617
|
-
return `\u2139\uFE0F kody ${label} (dry-run): ${reason ?? "plan printed, no changes applied"}`;
|
|
4618
|
-
}
|
|
4619
|
-
if (mode === "prepare") {
|
|
4620
|
-
return url ? `\u2705 kody release PR opened: ${url}` : "\u2705 kody release prepared";
|
|
4621
|
-
}
|
|
4622
|
-
if (mode === "finalize") {
|
|
4623
|
-
return url ? `\u2705 kody release published: ${url}` : "\u2705 kody release finalized (tag pushed)";
|
|
4624
|
-
}
|
|
4625
|
-
return `\u2705 kody ${label} complete`;
|
|
4626
|
-
}
|
|
4627
|
-
async function runPrepare(args) {
|
|
4628
|
-
const { cwd, bump, dryRun, prefer, versionFiles, ctx } = args;
|
|
4629
|
-
const pkgPath = path17.join(cwd, "package.json");
|
|
4630
|
-
if (!fs19.existsSync(pkgPath)) {
|
|
4631
|
-
ctx.output.exitCode = 99;
|
|
4632
|
-
ctx.output.reason = "release prepare: package.json not found";
|
|
4633
|
-
return;
|
|
4634
|
-
}
|
|
4635
|
-
const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
|
|
4636
|
-
if (typeof pkg.version !== "string") {
|
|
4637
|
-
ctx.output.exitCode = 99;
|
|
4638
|
-
ctx.output.reason = "release prepare: package.json has no version";
|
|
4639
|
-
return;
|
|
4640
|
-
}
|
|
4641
|
-
const oldVersion = pkg.version;
|
|
4642
|
-
const newVersion = bumpVersion(oldVersion, bump);
|
|
4643
|
-
const tag = `v${newVersion}`;
|
|
4644
|
-
process.stdout.write(`\u2192 release prepare: ${oldVersion} \u2192 ${newVersion} (${bump})
|
|
4645
|
-
`);
|
|
4646
|
-
if (dryRun) {
|
|
4647
|
-
ctx.output.exitCode = 0;
|
|
4648
|
-
ctx.output.reason = `dry-run \u2014 would bump to ${newVersion}${prefer ? ` (--prefer ${prefer})` : ""}`;
|
|
4649
|
-
process.stdout.write(`RELEASE_PLAN=bump=${newVersion} tag=${tag}
|
|
4650
|
-
`);
|
|
4651
|
-
return;
|
|
4652
|
-
}
|
|
4653
|
-
const releaseBranch = `release/${tag}`;
|
|
4654
|
-
const collides = remoteBranchExists(releaseBranch, cwd);
|
|
4655
|
-
if (collides) {
|
|
4656
|
-
if (prefer === "theirs") {
|
|
4657
|
-
const existingPr = findOpenPrForBranch(releaseBranch, cwd);
|
|
4658
|
-
if (existingPr) {
|
|
4659
|
-
process.stdout.write(` reusing existing PR (--prefer theirs): ${existingPr}
|
|
4660
|
-
`);
|
|
4661
|
-
ctx.output.prUrl = existingPr;
|
|
4662
|
-
ctx.output.exitCode = 0;
|
|
4663
|
-
return;
|
|
4664
|
-
}
|
|
4665
|
-
ctx.output.exitCode = 4;
|
|
4666
|
-
ctx.output.reason = `release prepare --prefer theirs: ${releaseBranch} exists on remote but has no open PR \u2014 nothing to reuse`;
|
|
4667
|
-
return;
|
|
4668
|
-
}
|
|
4669
|
-
if (prefer !== "ours") {
|
|
4670
|
-
ctx.output.exitCode = 4;
|
|
4671
|
-
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.`;
|
|
4672
|
-
return;
|
|
4673
|
-
}
|
|
4674
|
-
process.stdout.write(` branch ${releaseBranch} exists on remote \u2014 will force-push (--prefer ours)
|
|
4675
|
-
`);
|
|
4676
|
-
}
|
|
4677
|
-
const touched = [];
|
|
4678
|
-
for (const f of versionFiles) {
|
|
4679
|
-
if (updateVersionInFile(f, newVersion, cwd)) touched.push(f);
|
|
4680
|
-
}
|
|
4681
|
-
if (touched.length === 0) {
|
|
4682
|
-
ctx.output.exitCode = 1;
|
|
4683
|
-
ctx.output.reason = `release prepare: no version strings updated (files: ${versionFiles.join(", ")})`;
|
|
4684
|
-
return;
|
|
4685
|
-
}
|
|
4686
|
-
process.stdout.write(` wrote ${touched.join(", ")}
|
|
4687
|
-
`);
|
|
4688
|
-
const entry = generateChangelog(cwd, newVersion, lastReleaseTag(cwd));
|
|
4689
|
-
prependChangelog(cwd, entry);
|
|
4690
|
-
process.stdout.write(` wrote CHANGELOG.md
|
|
4691
|
-
`);
|
|
4692
|
-
try {
|
|
4693
|
-
git3(["checkout", "-b", releaseBranch], cwd);
|
|
4694
|
-
for (const f of [...touched, "CHANGELOG.md"]) git3(["add", "--", f], cwd);
|
|
4695
|
-
git3(["commit", "--no-gpg-sign", "-m", `chore: release ${tag}`], cwd);
|
|
4696
|
-
const pushArgs = collides && prefer === "ours" ? ["push", "-u", "--force-with-lease", "origin", releaseBranch] : ["push", "-u", "origin", releaseBranch];
|
|
4697
|
-
git3(pushArgs, cwd, 12e4);
|
|
4698
|
-
} catch (err) {
|
|
4699
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4700
|
-
ctx.output.exitCode = 4;
|
|
4701
|
-
ctx.output.reason = `release prepare: git commit/push failed: ${msg}`;
|
|
4702
|
-
return;
|
|
4703
|
-
}
|
|
4704
|
-
const base = ctx.config.git.defaultBranch;
|
|
4705
|
-
const title = `chore: release ${tag}`;
|
|
4706
|
-
const bodyMax = 6e4;
|
|
4707
|
-
const rawEntry = entry.length > bodyMax ? `${entry.slice(0, bodyMax)}
|
|
4708
|
-
|
|
4709
|
-
_\u2026 truncated; see CHANGELOG.md_` : entry;
|
|
4710
|
-
const body = `Automated release PR opened by kody.
|
|
4711
|
-
|
|
4712
|
-
${rawEntry}
|
|
4713
|
-
|
|
4714
|
-
Merge this and then run \`kody release --mode finalize\`.`;
|
|
4715
|
-
let prUrl = "";
|
|
4716
|
-
const preexistingPr = collides && prefer === "ours" ? findOpenPrForBranch(releaseBranch, cwd) : null;
|
|
4717
|
-
if (preexistingPr) {
|
|
4718
|
-
process.stdout.write(` PR already open for ${releaseBranch}: ${preexistingPr}
|
|
4719
|
-
`);
|
|
4720
|
-
prUrl = preexistingPr;
|
|
4721
|
-
} else {
|
|
4722
|
-
try {
|
|
4723
|
-
prUrl = gh2(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
|
|
4724
|
-
input: body,
|
|
4725
|
-
cwd
|
|
4726
|
-
}).trim();
|
|
4727
|
-
} catch (err) {
|
|
4728
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4729
|
-
ctx.output.exitCode = 4;
|
|
4730
|
-
ctx.output.reason = `release prepare: gh pr create failed: ${msg}`;
|
|
4731
|
-
return;
|
|
4732
|
-
}
|
|
4733
|
-
}
|
|
4734
|
-
ctx.output.prUrl = prUrl;
|
|
4735
|
-
ctx.output.exitCode = 0;
|
|
4736
|
-
process.stdout.write(`RELEASE_PR=${prUrl}
|
|
4737
|
-
`);
|
|
4738
|
-
}
|
|
4739
|
-
async function runFinalize(args) {
|
|
4740
|
-
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
4741
|
-
const pkgPath = path17.join(cwd, "package.json");
|
|
4742
|
-
const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
|
|
4743
|
-
if (typeof pkg.version !== "string") {
|
|
4744
|
-
ctx.output.exitCode = 99;
|
|
4745
|
-
ctx.output.reason = "release finalize: package.json has no version";
|
|
4746
|
-
return;
|
|
4747
|
-
}
|
|
4748
|
-
const version = pkg.version;
|
|
4749
|
-
const tag = `v${version}`;
|
|
4750
|
-
process.stdout.write(`\u2192 release finalize: ${tag}
|
|
4751
|
-
`);
|
|
4752
|
-
try {
|
|
4753
|
-
git3(["rev-parse", "--verify", tag], cwd);
|
|
4754
|
-
ctx.output.exitCode = 1;
|
|
4755
|
-
ctx.output.reason = `release finalize: tag ${tag} already exists`;
|
|
4756
|
-
return;
|
|
4757
|
-
} catch {
|
|
4758
|
-
}
|
|
4759
|
-
if (dryRun) {
|
|
4760
|
-
ctx.output.exitCode = 0;
|
|
4761
|
-
ctx.output.reason = `dry-run \u2014 would tag + publish ${tag}`;
|
|
4762
|
-
return;
|
|
4763
|
-
}
|
|
4764
|
-
if (releaseCfg.e2eCommand && releaseCfg.e2eCommand.trim().length > 0) {
|
|
4765
|
-
const cmd = releaseCfg.e2eCommand.replace(/\$VERSION/g, version);
|
|
4766
|
-
process.stdout.write(` E2E gate: ${cmd}
|
|
4767
|
-
`);
|
|
4768
|
-
const r = runShell(cmd, cwd, timeoutMs);
|
|
4769
|
-
if (r.exitCode !== 0) {
|
|
4770
|
-
ctx.output.exitCode = 2;
|
|
4771
|
-
ctx.output.reason = `release finalize: E2E gate failed (exit ${r.exitCode}): ${truncate2(r.stderr, 600)}`;
|
|
4772
|
-
return;
|
|
4773
|
-
}
|
|
4774
|
-
}
|
|
4775
|
-
try {
|
|
4776
|
-
git3(["tag", "-a", tag, "-m", `Release ${tag}`], cwd);
|
|
4777
|
-
git3(["push", "origin", tag], cwd, 12e4);
|
|
4778
|
-
} catch (err) {
|
|
4779
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4780
|
-
ctx.output.exitCode = 4;
|
|
4781
|
-
ctx.output.reason = `release finalize: tag/push failed: ${msg}`;
|
|
4782
|
-
return;
|
|
4783
|
-
}
|
|
4784
|
-
let publishStatus = "skipped";
|
|
4785
|
-
if (releaseCfg.publishCommand && releaseCfg.publishCommand.trim().length > 0) {
|
|
4786
|
-
const cmd = releaseCfg.publishCommand.replace(/\$VERSION/g, version);
|
|
4787
|
-
process.stdout.write(` publish: ${cmd}
|
|
4788
|
-
`);
|
|
4789
|
-
const r = runShell(cmd, cwd, timeoutMs);
|
|
4790
|
-
publishStatus = r.exitCode === 0 ? "ok" : "failed";
|
|
4791
|
-
if (r.exitCode !== 0) {
|
|
4792
|
-
process.stderr.write(`[kody release] publishCommand exit ${r.exitCode}
|
|
4793
|
-
${truncate2(r.stderr, 2e3)}
|
|
4794
|
-
`);
|
|
4795
|
-
}
|
|
4796
|
-
}
|
|
4797
|
-
let releaseUrl = "";
|
|
4798
|
-
try {
|
|
4799
|
-
const releaseArgs = ["release", "create", tag, "--title", tag, "--notes", `Release ${tag} \u2014 automated by kody.`];
|
|
4800
|
-
if (releaseCfg.draftRelease) releaseArgs.push("--draft");
|
|
4801
|
-
releaseUrl = gh2(releaseArgs, { cwd }).trim();
|
|
4802
|
-
} catch (err) {
|
|
4803
|
-
process.stderr.write(
|
|
4804
|
-
`[kody release] gh release create failed: ${err instanceof Error ? err.message : String(err)}
|
|
4805
|
-
`
|
|
4806
|
-
);
|
|
4807
|
-
}
|
|
4808
|
-
if (releaseCfg.notifyCommand && releaseCfg.notifyCommand.trim().length > 0) {
|
|
4809
|
-
const cmd = releaseCfg.notifyCommand.replace(/\$VERSION/g, version);
|
|
4810
|
-
runShell(cmd, cwd, timeoutMs);
|
|
4811
|
-
}
|
|
4812
|
-
if (releaseUrl) ctx.output.prUrl = releaseUrl;
|
|
4813
|
-
if (publishStatus === "failed") {
|
|
4814
|
-
ctx.output.exitCode = 1;
|
|
4815
|
-
ctx.output.reason = `release finalize: tag + gh release created, but publishCommand failed`;
|
|
4816
|
-
return;
|
|
4817
|
-
}
|
|
4818
|
-
ctx.output.exitCode = 0;
|
|
4819
|
-
process.stdout.write(`RELEASE_TAG=${tag}
|
|
4820
|
-
`);
|
|
4821
|
-
if (releaseUrl) process.stdout.write(`RELEASE_URL=${releaseUrl}
|
|
4822
|
-
`);
|
|
4823
|
-
}
|
|
4824
4534
|
|
|
4825
4535
|
// src/scripts/requireFeedbackActions.ts
|
|
4826
4536
|
var MIN_ITEMS = 1;
|
|
@@ -5130,6 +4840,16 @@ function synthesizeAction(ctx) {
|
|
|
5130
4840
|
};
|
|
5131
4841
|
}
|
|
5132
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
|
+
|
|
5133
4853
|
// src/scripts/setLifecycleLabel.ts
|
|
5134
4854
|
var setLifecycleLabel = async (ctx, _profile, args) => {
|
|
5135
4855
|
const label = args?.label;
|
|
@@ -5181,7 +4901,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
5181
4901
|
|
|
5182
4902
|
// src/scripts/startFlow.ts
|
|
5183
4903
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
5184
|
-
var
|
|
4904
|
+
var API_TIMEOUT_MS8 = 3e4;
|
|
5185
4905
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
5186
4906
|
const entry = args?.entry;
|
|
5187
4907
|
if (!entry) {
|
|
@@ -5210,12 +4930,12 @@ var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
|
5210
4930
|
postKodyComment(target, issueNumber, state, entry, ctx.cwd);
|
|
5211
4931
|
};
|
|
5212
4932
|
function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
5213
|
-
const targetNumber = target === "pr" && state?.core.prUrl ?
|
|
4933
|
+
const targetNumber = target === "pr" && state?.core.prUrl ? parsePrNumber(state.core.prUrl) ?? issueNumber : issueNumber;
|
|
5214
4934
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
5215
4935
|
const body = `@kody ${next}`;
|
|
5216
4936
|
try {
|
|
5217
4937
|
execFileSync18("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
5218
|
-
timeout:
|
|
4938
|
+
timeout: API_TIMEOUT_MS8,
|
|
5219
4939
|
cwd,
|
|
5220
4940
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5221
4941
|
});
|
|
@@ -5226,12 +4946,6 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
5226
4946
|
);
|
|
5227
4947
|
}
|
|
5228
4948
|
}
|
|
5229
|
-
function parsePr2(url) {
|
|
5230
|
-
const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
5231
|
-
if (!m) return null;
|
|
5232
|
-
const n = parseInt(m[1], 10);
|
|
5233
|
-
return Number.isFinite(n) ? n : null;
|
|
5234
|
-
}
|
|
5235
4949
|
|
|
5236
4950
|
// src/scripts/syncFlow.ts
|
|
5237
4951
|
import { execFileSync as execFileSync19 } from "child_process";
|
|
@@ -5507,7 +5221,7 @@ var writeIssueStateComment = async (ctx, _profile, _agentResult, args) => {
|
|
|
5507
5221
|
};
|
|
5508
5222
|
|
|
5509
5223
|
// src/scripts/writeRunSummary.ts
|
|
5510
|
-
import * as
|
|
5224
|
+
import * as fs19 from "fs";
|
|
5511
5225
|
var writeRunSummary = async (ctx, profile) => {
|
|
5512
5226
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
5513
5227
|
if (!summaryPath) return;
|
|
@@ -5529,7 +5243,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
5529
5243
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
5530
5244
|
lines.push("");
|
|
5531
5245
|
try {
|
|
5532
|
-
|
|
5246
|
+
fs19.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
5533
5247
|
`);
|
|
5534
5248
|
} catch {
|
|
5535
5249
|
}
|
|
@@ -5544,7 +5258,6 @@ var preflightScripts = {
|
|
|
5544
5258
|
reviewFlow,
|
|
5545
5259
|
syncFlow,
|
|
5546
5260
|
initFlow,
|
|
5547
|
-
releaseFlow,
|
|
5548
5261
|
watchStalePrsFlow,
|
|
5549
5262
|
loadTaskState,
|
|
5550
5263
|
loadIssueContext,
|
|
@@ -5558,6 +5271,7 @@ var preflightScripts = {
|
|
|
5558
5271
|
discoverQaContext,
|
|
5559
5272
|
resolvePreviewUrl,
|
|
5560
5273
|
composePrompt,
|
|
5274
|
+
setCommentTarget,
|
|
5561
5275
|
setLifecycleLabel,
|
|
5562
5276
|
skipAgent,
|
|
5563
5277
|
classifyByLabel,
|
|
@@ -5589,7 +5303,10 @@ var postflightScripts = {
|
|
|
5589
5303
|
finishFlow,
|
|
5590
5304
|
advanceFlow,
|
|
5591
5305
|
persistFlowState,
|
|
5592
|
-
postClassification
|
|
5306
|
+
postClassification,
|
|
5307
|
+
notifyTerminal,
|
|
5308
|
+
recordOutcome,
|
|
5309
|
+
mergeReleasePr
|
|
5593
5310
|
};
|
|
5594
5311
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
5595
5312
|
...Object.keys(preflightScripts),
|
|
@@ -5613,22 +5330,22 @@ function firstRequiredFailure(results, tools) {
|
|
|
5613
5330
|
}
|
|
5614
5331
|
function verifyOne(tool, cwd) {
|
|
5615
5332
|
const result = { name: tool.name, present: false, verified: false };
|
|
5616
|
-
let present =
|
|
5333
|
+
let present = runShell(tool.install.checkCommand, cwd);
|
|
5617
5334
|
if (!present && tool.install.installCommand) {
|
|
5618
|
-
|
|
5619
|
-
present =
|
|
5335
|
+
runShell(tool.install.installCommand, cwd, 12e4);
|
|
5336
|
+
present = runShell(tool.install.checkCommand, cwd);
|
|
5620
5337
|
}
|
|
5621
5338
|
result.present = present;
|
|
5622
5339
|
if (!present) {
|
|
5623
5340
|
result.error = `tool "${tool.name}" not on PATH (check: ${tool.install.checkCommand})`;
|
|
5624
5341
|
return result;
|
|
5625
5342
|
}
|
|
5626
|
-
const verified =
|
|
5343
|
+
const verified = runShell(tool.verify, cwd);
|
|
5627
5344
|
result.verified = verified;
|
|
5628
5345
|
if (!verified) result.error = `tool "${tool.name}" failed verify: ${tool.verify}`;
|
|
5629
5346
|
return result;
|
|
5630
5347
|
}
|
|
5631
|
-
function
|
|
5348
|
+
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
5632
5349
|
try {
|
|
5633
5350
|
execFileSync20("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
5634
5351
|
return true;
|
|
@@ -5698,9 +5415,9 @@ async function runExecutable(profileName, input) {
|
|
|
5698
5415
|
data: {},
|
|
5699
5416
|
output: { exitCode: 0 }
|
|
5700
5417
|
};
|
|
5701
|
-
const ndjsonDir =
|
|
5418
|
+
const ndjsonDir = path17.join(input.cwd, ".kody");
|
|
5702
5419
|
const invokeAgent = async (prompt) => {
|
|
5703
|
-
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);
|
|
5704
5421
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
5705
5422
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
5706
5423
|
return runAgent({
|
|
@@ -5776,17 +5493,17 @@ async function runExecutable(profileName, input) {
|
|
|
5776
5493
|
}
|
|
5777
5494
|
}
|
|
5778
5495
|
function resolveProfilePath(profileName) {
|
|
5779
|
-
const here =
|
|
5496
|
+
const here = path17.dirname(new URL(import.meta.url).pathname);
|
|
5780
5497
|
const candidates = [
|
|
5781
|
-
|
|
5498
|
+
path17.join(here, "executables", profileName, "profile.json"),
|
|
5782
5499
|
// same-dir sibling (dev)
|
|
5783
|
-
|
|
5500
|
+
path17.join(here, "..", "executables", profileName, "profile.json"),
|
|
5784
5501
|
// up one (prod: dist/bin → dist/executables)
|
|
5785
|
-
|
|
5502
|
+
path17.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
5786
5503
|
// fallback
|
|
5787
5504
|
];
|
|
5788
5505
|
for (const c of candidates) {
|
|
5789
|
-
if (
|
|
5506
|
+
if (fs20.existsSync(c)) return c;
|
|
5790
5507
|
}
|
|
5791
5508
|
return candidates[0];
|
|
5792
5509
|
}
|
|
@@ -5879,8 +5596,8 @@ function finish(out) {
|
|
|
5879
5596
|
var SHELL_TIMEOUT_MS = 3e5;
|
|
5880
5597
|
function runShellEntry(entry, ctx, profile) {
|
|
5881
5598
|
const shellName = entry.shell;
|
|
5882
|
-
const shellPath =
|
|
5883
|
-
if (!
|
|
5599
|
+
const shellPath = path17.join(profile.dir, shellName);
|
|
5600
|
+
if (!fs20.existsSync(shellPath)) {
|
|
5884
5601
|
ctx.skipAgent = true;
|
|
5885
5602
|
ctx.output.exitCode = 99;
|
|
5886
5603
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -5890,9 +5607,12 @@ function runShellEntry(entry, ctx, profile) {
|
|
|
5890
5607
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
5891
5608
|
for (const [k, v] of Object.entries(ctx.args)) {
|
|
5892
5609
|
if (v === void 0 || v === null) continue;
|
|
5893
|
-
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;
|
|
5894
5614
|
}
|
|
5895
|
-
const r =
|
|
5615
|
+
const r = spawnSync("bash", [shellPath, ...positional], {
|
|
5896
5616
|
cwd: ctx.cwd,
|
|
5897
5617
|
encoding: "utf-8",
|
|
5898
5618
|
env,
|
|
@@ -5907,6 +5627,10 @@ function runShellEntry(entry, ctx, profile) {
|
|
|
5907
5627
|
ctx.skipAgent = true;
|
|
5908
5628
|
if (ctx.output.exitCode === void 0) ctx.output.exitCode = 0;
|
|
5909
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();
|
|
5910
5634
|
const exit = r.status ?? -1;
|
|
5911
5635
|
if (exit !== 0) {
|
|
5912
5636
|
ctx.skipAgent = true;
|
|
@@ -5919,6 +5643,24 @@ function runShellEntry(entry, ctx, profile) {
|
|
|
5919
5643
|
}
|
|
5920
5644
|
}
|
|
5921
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
|
+
}
|
|
5922
5664
|
|
|
5923
5665
|
// src/kody-cli.ts
|
|
5924
5666
|
var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
|
|
@@ -6005,9 +5747,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
6005
5747
|
return token;
|
|
6006
5748
|
}
|
|
6007
5749
|
function detectPackageManager2(cwd) {
|
|
6008
|
-
if (
|
|
6009
|
-
if (
|
|
6010
|
-
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";
|
|
6011
5753
|
return "npm";
|
|
6012
5754
|
}
|
|
6013
5755
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -6087,11 +5829,11 @@ function configureGitIdentity(cwd) {
|
|
|
6087
5829
|
}
|
|
6088
5830
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
6089
5831
|
if (!issueNumber) return;
|
|
6090
|
-
const logPath =
|
|
5832
|
+
const logPath = path18.join(cwd, ".kody", "last-run.jsonl");
|
|
6091
5833
|
let tail = "";
|
|
6092
5834
|
try {
|
|
6093
|
-
if (
|
|
6094
|
-
const content =
|
|
5835
|
+
if (fs21.existsSync(logPath)) {
|
|
5836
|
+
const content = fs21.readFileSync(logPath, "utf-8");
|
|
6095
5837
|
tail = content.slice(-3e3);
|
|
6096
5838
|
}
|
|
6097
5839
|
} catch {
|
|
@@ -6116,7 +5858,7 @@ async function runCi(argv) {
|
|
|
6116
5858
|
return 0;
|
|
6117
5859
|
}
|
|
6118
5860
|
const args = parseCiArgs(argv);
|
|
6119
|
-
const cwd = args.cwd ?
|
|
5861
|
+
const cwd = args.cwd ? path18.resolve(args.cwd) : process.cwd();
|
|
6120
5862
|
let earlyConfig;
|
|
6121
5863
|
try {
|
|
6122
5864
|
earlyConfig = loadConfig(cwd);
|
|
@@ -6254,9 +5996,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
6254
5996
|
return result;
|
|
6255
5997
|
}
|
|
6256
5998
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
6257
|
-
const sessionFile =
|
|
6258
|
-
const eventsFile =
|
|
6259
|
-
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)));
|
|
6260
6002
|
if (paths.length === 0) return;
|
|
6261
6003
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
6262
6004
|
try {
|
|
@@ -6294,7 +6036,7 @@ async function runChat(argv) {
|
|
|
6294
6036
|
${CHAT_HELP}`);
|
|
6295
6037
|
return 64;
|
|
6296
6038
|
}
|
|
6297
|
-
const cwd = args.cwd ?
|
|
6039
|
+
const cwd = args.cwd ? path19.resolve(args.cwd) : process.cwd();
|
|
6298
6040
|
const sessionId = args.sessionId;
|
|
6299
6041
|
const unpackedSecrets = unpackAllSecrets();
|
|
6300
6042
|
if (unpackedSecrets > 0) {
|