@kody-ade/kody-engine 0.3.9 → 0.3.10
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.10",
|
|
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",
|
|
@@ -4327,6 +4327,24 @@ function lastReleaseTag(cwd) {
|
|
|
4327
4327
|
return null;
|
|
4328
4328
|
}
|
|
4329
4329
|
}
|
|
4330
|
+
function remoteBranchExists(branch, cwd) {
|
|
4331
|
+
try {
|
|
4332
|
+
const out = git3(["ls-remote", "--heads", "origin", branch], cwd, 3e4);
|
|
4333
|
+
return out.length > 0;
|
|
4334
|
+
} catch {
|
|
4335
|
+
return false;
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
function findOpenPrForBranch(branch, cwd) {
|
|
4339
|
+
try {
|
|
4340
|
+
const out = gh2(["pr", "list", "--head", branch, "--state", "open", "--json", "url", "--limit", "1"], { cwd });
|
|
4341
|
+
const parsed = JSON.parse(out || "[]");
|
|
4342
|
+
const first = parsed[0];
|
|
4343
|
+
return first?.url ?? null;
|
|
4344
|
+
} catch {
|
|
4345
|
+
return null;
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4330
4348
|
function runShell(cmd, cwd, timeoutMs) {
|
|
4331
4349
|
const r = spawnSync(cmd, {
|
|
4332
4350
|
cwd,
|
|
@@ -4341,6 +4359,7 @@ var releaseFlow = async (ctx) => {
|
|
|
4341
4359
|
const mode = ctx.args.mode ?? "prepare";
|
|
4342
4360
|
const bump = ctx.args.bump ?? "patch";
|
|
4343
4361
|
const dryRun = ctx.args["dry-run"] === true || ctx.args.dryRun === true;
|
|
4362
|
+
const prefer = ctx.args.prefer ?? void 0;
|
|
4344
4363
|
const issueNumber = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
|
|
4345
4364
|
const cwd = ctx.cwd;
|
|
4346
4365
|
const releaseCfg = ctx.config.release ?? {};
|
|
@@ -4348,7 +4367,7 @@ var releaseFlow = async (ctx) => {
|
|
|
4348
4367
|
const timeoutMs = releaseCfg.timeoutMs ?? 6e5;
|
|
4349
4368
|
ctx.skipAgent = true;
|
|
4350
4369
|
if (mode === "prepare") {
|
|
4351
|
-
await runPrepare({ cwd, bump, dryRun, versionFiles, ctx });
|
|
4370
|
+
await runPrepare({ cwd, bump, dryRun, prefer, versionFiles, ctx });
|
|
4352
4371
|
} else if (mode === "finalize") {
|
|
4353
4372
|
await runFinalize({ cwd, dryRun, timeoutMs, releaseCfg, ctx });
|
|
4354
4373
|
} else {
|
|
@@ -4378,7 +4397,7 @@ function buildIssueNotice(mode, dryRun, ctx) {
|
|
|
4378
4397
|
return `\u2705 kody ${label} complete`;
|
|
4379
4398
|
}
|
|
4380
4399
|
async function runPrepare(args) {
|
|
4381
|
-
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
4400
|
+
const { cwd, bump, dryRun, prefer, versionFiles, ctx } = args;
|
|
4382
4401
|
const pkgPath = path17.join(cwd, "package.json");
|
|
4383
4402
|
if (!fs19.existsSync(pkgPath)) {
|
|
4384
4403
|
ctx.output.exitCode = 99;
|
|
@@ -4398,11 +4417,35 @@ async function runPrepare(args) {
|
|
|
4398
4417
|
`);
|
|
4399
4418
|
if (dryRun) {
|
|
4400
4419
|
ctx.output.exitCode = 0;
|
|
4401
|
-
ctx.output.reason = `dry-run \u2014 would bump to ${newVersion}`;
|
|
4420
|
+
ctx.output.reason = `dry-run \u2014 would bump to ${newVersion}${prefer ? ` (--prefer ${prefer})` : ""}`;
|
|
4402
4421
|
process.stdout.write(`RELEASE_PLAN=bump=${newVersion} tag=${tag}
|
|
4403
4422
|
`);
|
|
4404
4423
|
return;
|
|
4405
4424
|
}
|
|
4425
|
+
const releaseBranch = `release/${tag}`;
|
|
4426
|
+
const collides = remoteBranchExists(releaseBranch, cwd);
|
|
4427
|
+
if (collides) {
|
|
4428
|
+
if (prefer === "theirs") {
|
|
4429
|
+
const existingPr = findOpenPrForBranch(releaseBranch, cwd);
|
|
4430
|
+
if (existingPr) {
|
|
4431
|
+
process.stdout.write(` reusing existing PR (--prefer theirs): ${existingPr}
|
|
4432
|
+
`);
|
|
4433
|
+
ctx.output.prUrl = existingPr;
|
|
4434
|
+
ctx.output.exitCode = 0;
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
ctx.output.exitCode = 4;
|
|
4438
|
+
ctx.output.reason = `release prepare --prefer theirs: ${releaseBranch} exists on remote but has no open PR \u2014 nothing to reuse`;
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
4441
|
+
if (prefer !== "ours") {
|
|
4442
|
+
ctx.output.exitCode = 4;
|
|
4443
|
+
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.`;
|
|
4444
|
+
return;
|
|
4445
|
+
}
|
|
4446
|
+
process.stdout.write(` branch ${releaseBranch} exists on remote \u2014 will force-push (--prefer ours)
|
|
4447
|
+
`);
|
|
4448
|
+
}
|
|
4406
4449
|
const touched = [];
|
|
4407
4450
|
for (const f of versionFiles) {
|
|
4408
4451
|
if (updateVersionInFile(f, newVersion, cwd)) touched.push(f);
|
|
@@ -4418,12 +4461,12 @@ async function runPrepare(args) {
|
|
|
4418
4461
|
prependChangelog(cwd, entry);
|
|
4419
4462
|
process.stdout.write(` wrote CHANGELOG.md
|
|
4420
4463
|
`);
|
|
4421
|
-
const releaseBranch = `release/${tag}`;
|
|
4422
4464
|
try {
|
|
4423
4465
|
git3(["checkout", "-b", releaseBranch], cwd);
|
|
4424
4466
|
for (const f of [...touched, "CHANGELOG.md"]) git3(["add", "--", f], cwd);
|
|
4425
4467
|
git3(["commit", "--no-gpg-sign", "-m", `chore: release ${tag}`], cwd);
|
|
4426
|
-
|
|
4468
|
+
const pushArgs = collides && prefer === "ours" ? ["push", "-u", "--force-with-lease", "origin", releaseBranch] : ["push", "-u", "origin", releaseBranch];
|
|
4469
|
+
git3(pushArgs, cwd, 12e4);
|
|
4427
4470
|
} catch (err) {
|
|
4428
4471
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4429
4472
|
ctx.output.exitCode = 4;
|
|
@@ -4442,16 +4485,23 @@ ${rawEntry}
|
|
|
4442
4485
|
|
|
4443
4486
|
Merge this and then run \`kody release --mode finalize\`.`;
|
|
4444
4487
|
let prUrl = "";
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
}
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4488
|
+
const preexistingPr = collides && prefer === "ours" ? findOpenPrForBranch(releaseBranch, cwd) : null;
|
|
4489
|
+
if (preexistingPr) {
|
|
4490
|
+
process.stdout.write(` PR already open for ${releaseBranch}: ${preexistingPr}
|
|
4491
|
+
`);
|
|
4492
|
+
prUrl = preexistingPr;
|
|
4493
|
+
} else {
|
|
4494
|
+
try {
|
|
4495
|
+
prUrl = gh2(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
|
|
4496
|
+
input: body,
|
|
4497
|
+
cwd
|
|
4498
|
+
}).trim();
|
|
4499
|
+
} catch (err) {
|
|
4500
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4501
|
+
ctx.output.exitCode = 4;
|
|
4502
|
+
ctx.output.reason = `release prepare: gh pr create failed: ${msg}`;
|
|
4503
|
+
return;
|
|
4504
|
+
}
|
|
4455
4505
|
}
|
|
4456
4506
|
ctx.output.prUrl = prUrl;
|
|
4457
4507
|
ctx.output.exitCode = 0;
|
|
@@ -4681,6 +4731,7 @@ var resolveFlow = async (ctx) => {
|
|
|
4681
4731
|
}
|
|
4682
4732
|
ctx.data.conflictedFiles = conflictedFiles;
|
|
4683
4733
|
ctx.data.conflictMarkersPreview = getConflictMarkersPreview(conflictedFiles, ctx.cwd);
|
|
4734
|
+
ctx.data.preferBlock = buildPreferBlock(ctx.args.prefer, baseBranch);
|
|
4684
4735
|
const runUrl = getRunUrl();
|
|
4685
4736
|
const runSuffix = runUrl ? `, run ${runUrl}` : "";
|
|
4686
4737
|
tryPostPr3(
|
|
@@ -4689,6 +4740,23 @@ var resolveFlow = async (ctx) => {
|
|
|
4689
4740
|
ctx.cwd
|
|
4690
4741
|
);
|
|
4691
4742
|
};
|
|
4743
|
+
function buildPreferBlock(prefer, baseBranch) {
|
|
4744
|
+
if (prefer !== "ours" && prefer !== "theirs") return "";
|
|
4745
|
+
const keepSide = prefer === "ours" ? "HEAD (this PR branch)" : `origin/${baseBranch} (base branch)`;
|
|
4746
|
+
const keepMarkers = prefer === "ours" ? "content between `<<<<<<< HEAD` and `=======`" : `content between \`=======\` and \`>>>>>>> origin/${baseBranch}\``;
|
|
4747
|
+
const dropSide = prefer === "ours" ? `origin/${baseBranch}` : "HEAD";
|
|
4748
|
+
return [
|
|
4749
|
+
"# Conflict resolution directive (AUTHORITATIVE \u2014 overrides defaults below)",
|
|
4750
|
+
"",
|
|
4751
|
+
`The user requested \`--prefer ${prefer}\`. For **every** conflict in **every** file:`,
|
|
4752
|
+
"",
|
|
4753
|
+
`- Keep the **${prefer}** side: ${keepSide} \u2014 ${keepMarkers}.`,
|
|
4754
|
+
`- Discard the **${prefer === "ours" ? "theirs" : "ours"}** side (from ${dropSide}) entirely.`,
|
|
4755
|
+
"- Remove all `<<<<<<<`, `=======`, `>>>>>>>` markers.",
|
|
4756
|
+
"- Do NOT attempt to merge the two sides or apply judgement.",
|
|
4757
|
+
""
|
|
4758
|
+
].join("\n");
|
|
4759
|
+
}
|
|
4692
4760
|
function getConflictedFiles(cwd) {
|
|
4693
4761
|
try {
|
|
4694
4762
|
const out = execFileSync17("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
@@ -40,6 +40,17 @@
|
|
|
40
40
|
"type": "int",
|
|
41
41
|
"required": false,
|
|
42
42
|
"describe": "Issue number to post success/failure follow-up on. Auto-populated by dispatch when triggered via @kody comment."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "prefer",
|
|
46
|
+
"flag": "--prefer",
|
|
47
|
+
"type": "enum",
|
|
48
|
+
"values": [
|
|
49
|
+
"ours",
|
|
50
|
+
"theirs"
|
|
51
|
+
],
|
|
52
|
+
"required": false,
|
|
53
|
+
"describe": "On release/vX.Y.Z branch collision (prepare mode): 'ours' force-pushes over the remote branch; 'theirs' reuses the existing branch and its open PR. Default (unset): refuse on non-ff."
|
|
43
54
|
}
|
|
44
55
|
],
|
|
45
56
|
"claudeCode": {
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
"type": "int",
|
|
10
10
|
"required": true,
|
|
11
11
|
"describe": "GitHub PR number whose branch has conflicts with the default branch."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "prefer",
|
|
15
|
+
"flag": "--prefer",
|
|
16
|
+
"type": "enum",
|
|
17
|
+
"values": ["ours", "theirs"],
|
|
18
|
+
"required": false,
|
|
19
|
+
"describe": "Force one side for every conflict: 'ours' = PR branch (HEAD), 'theirs' = base branch. Omit to let the agent merge by judgement."
|
|
12
20
|
}
|
|
13
21
|
],
|
|
14
22
|
"claudeCode": {
|
|
@@ -9,13 +9,13 @@ You are Kody, an autonomous engineer. A `git merge origin/{{baseBranch}}` into P
|
|
|
9
9
|
|
|
10
10
|
{{conflictedFiles}}
|
|
11
11
|
|
|
12
|
-
{{conventionsBlock}}{{toolsUsage}}# Working-tree conflict markers (truncated)
|
|
12
|
+
{{preferBlock}}{{conventionsBlock}}{{toolsUsage}}# Working-tree conflict markers (truncated)
|
|
13
13
|
|
|
14
14
|
{{conflictMarkersPreview}}
|
|
15
15
|
|
|
16
16
|
# Required steps
|
|
17
17
|
1. For each conflicted file: read it, understand both sides of the `<<<<<<<` / `=======` / `>>>>>>>` markers, and produce the correct merged content. Remove all conflict markers.
|
|
18
|
-
2.
|
|
18
|
+
2. If a conflict resolution directive is given above, follow it exactly — take the specified side for every conflict, no judgement. Otherwise, preserve the PR's intent (the HEAD side) unless `origin/{{baseBranch}}` made a change that should be preserved (e.g. security fix, renamed API), and use judgement.
|
|
19
19
|
3. After resolving, run the quality commands with Bash and fix any issues YOUR resolution introduced.
|
|
20
20
|
4. Final message format (or `FAILED: <reason>` on failure):
|
|
21
21
|
|
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.10",
|
|
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",
|