@kody-ade/kody-engine 0.4.132 → 0.4.134
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 +153 -13
- package/dist/executables/merge/profile.json +54 -0
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -880,7 +880,7 @@ var init_loadPriorArt = __esm({
|
|
|
880
880
|
// package.json
|
|
881
881
|
var package_default = {
|
|
882
882
|
name: "@kody-ade/kody-engine",
|
|
883
|
-
version: "0.4.
|
|
883
|
+
version: "0.4.134",
|
|
884
884
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
885
885
|
license: "MIT",
|
|
886
886
|
type: "module",
|
|
@@ -5736,6 +5736,14 @@ function closePr(prNumber, comment, cwd) {
|
|
|
5736
5736
|
return fail(err);
|
|
5737
5737
|
}
|
|
5738
5738
|
}
|
|
5739
|
+
function mergePrSquash(prNumber, cwd) {
|
|
5740
|
+
try {
|
|
5741
|
+
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
5742
|
+
return { ok: true };
|
|
5743
|
+
} catch (err) {
|
|
5744
|
+
return fail(err);
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5739
5747
|
function editPrBase(prNumber, baseBranch, cwd) {
|
|
5740
5748
|
try {
|
|
5741
5749
|
gh(
|
|
@@ -8616,23 +8624,34 @@ function readKodyVariables(cwd) {
|
|
|
8616
8624
|
|
|
8617
8625
|
// src/scripts/loadQaContext.ts
|
|
8618
8626
|
var PROFILE_DIR_REL_PATH = ".kody/profile";
|
|
8627
|
+
var QA_STAFF = "qa-engineer";
|
|
8628
|
+
var ALL_STAFF = "*";
|
|
8629
|
+
var LEGACY_AUDIENCE_TO_STAFF = { chat: "kody", qa: QA_STAFF };
|
|
8619
8630
|
var FRONTMATTER_RE2 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
8620
|
-
function
|
|
8631
|
+
function parseSlugList(value) {
|
|
8632
|
+
const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
|
8633
|
+
return inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, "").toLowerCase()).filter(Boolean);
|
|
8634
|
+
}
|
|
8635
|
+
function readProfileStaff(raw) {
|
|
8621
8636
|
const m = FRONTMATTER_RE2.exec(raw);
|
|
8622
|
-
if (!m) return {
|
|
8637
|
+
if (!m) return { staff: ["kody"], body: raw };
|
|
8623
8638
|
const body = raw.slice(m[0].length);
|
|
8639
|
+
let staff = null;
|
|
8640
|
+
let legacy = null;
|
|
8624
8641
|
for (const line of (m[1] ?? "").split(/\r?\n/)) {
|
|
8625
8642
|
const t = line.trim();
|
|
8626
8643
|
const c = t.indexOf(":");
|
|
8627
8644
|
if (c < 0) continue;
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8645
|
+
const key = t.slice(0, c).trim();
|
|
8646
|
+
const value = t.slice(c + 1).trim();
|
|
8647
|
+
if (key === "staff") {
|
|
8648
|
+
staff = parseSlugList(value);
|
|
8649
|
+
} else if (key === "audience" || key === "for") {
|
|
8650
|
+
const mapped = parseSlugList(value).map((tok) => LEGACY_AUDIENCE_TO_STAFF[tok]).filter(Boolean);
|
|
8651
|
+
if (mapped.length > 0) legacy = mapped;
|
|
8633
8652
|
}
|
|
8634
8653
|
}
|
|
8635
|
-
return {
|
|
8654
|
+
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
8636
8655
|
}
|
|
8637
8656
|
function readProfile(cwd) {
|
|
8638
8657
|
const dir = path33.join(cwd, PROFILE_DIR_REL_PATH);
|
|
@@ -8647,8 +8666,8 @@ function readProfile(cwd) {
|
|
|
8647
8666
|
for (const file of entries) {
|
|
8648
8667
|
try {
|
|
8649
8668
|
const raw = fs34.readFileSync(path33.join(dir, file), "utf-8");
|
|
8650
|
-
const {
|
|
8651
|
-
if (!
|
|
8669
|
+
const { staff, body } = readProfileStaff(raw);
|
|
8670
|
+
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
8652
8671
|
blocks.push(`## ${file}
|
|
8653
8672
|
|
|
8654
8673
|
${body.trim()}`);
|
|
@@ -8831,6 +8850,122 @@ var markFlowSuccess = async (ctx) => {
|
|
|
8831
8850
|
}
|
|
8832
8851
|
};
|
|
8833
8852
|
|
|
8853
|
+
// src/scripts/mergeFlow.ts
|
|
8854
|
+
init_issue();
|
|
8855
|
+
function readPr(prNumber, cwd) {
|
|
8856
|
+
const out = gh(
|
|
8857
|
+
[
|
|
8858
|
+
"pr",
|
|
8859
|
+
"view",
|
|
8860
|
+
String(prNumber),
|
|
8861
|
+
"--json",
|
|
8862
|
+
"state,isDraft,mergeable,mergeStateStatus,title,url"
|
|
8863
|
+
],
|
|
8864
|
+
{ cwd }
|
|
8865
|
+
);
|
|
8866
|
+
const p = JSON.parse(out);
|
|
8867
|
+
return {
|
|
8868
|
+
state: p.state ?? "UNKNOWN",
|
|
8869
|
+
isDraft: p.isDraft ?? false,
|
|
8870
|
+
mergeable: p.mergeable ?? "UNKNOWN",
|
|
8871
|
+
mergeStateStatus: p.mergeStateStatus ?? "UNKNOWN",
|
|
8872
|
+
title: p.title ?? "",
|
|
8873
|
+
url: p.url ?? ""
|
|
8874
|
+
};
|
|
8875
|
+
}
|
|
8876
|
+
function evaluateMergeGate(pr) {
|
|
8877
|
+
if (pr.state !== "OPEN") {
|
|
8878
|
+
return {
|
|
8879
|
+
ok: false,
|
|
8880
|
+
action: "MERGE_SKIPPED",
|
|
8881
|
+
reason: `PR is ${pr.state.toLowerCase()}, not open \u2014 nothing to merge.`
|
|
8882
|
+
};
|
|
8883
|
+
}
|
|
8884
|
+
if (pr.isDraft) {
|
|
8885
|
+
return { ok: false, action: "MERGE_BLOCKED", reason: "PR is still a draft." };
|
|
8886
|
+
}
|
|
8887
|
+
if (pr.mergeable === "CONFLICTING" || pr.mergeStateStatus === "DIRTY") {
|
|
8888
|
+
return {
|
|
8889
|
+
ok: false,
|
|
8890
|
+
action: "MERGE_BLOCKED",
|
|
8891
|
+
reason: "PR has conflicts with its base branch \u2014 resolve them first."
|
|
8892
|
+
};
|
|
8893
|
+
}
|
|
8894
|
+
if (pr.mergeable === "UNKNOWN" || pr.mergeStateStatus === "UNKNOWN") {
|
|
8895
|
+
return {
|
|
8896
|
+
ok: false,
|
|
8897
|
+
action: "MERGE_BLOCKED",
|
|
8898
|
+
reason: "GitHub is still computing mergeability \u2014 will retry next tick."
|
|
8899
|
+
};
|
|
8900
|
+
}
|
|
8901
|
+
if (pr.mergeStateStatus === "BEHIND") {
|
|
8902
|
+
return {
|
|
8903
|
+
ok: false,
|
|
8904
|
+
action: "MERGE_BLOCKED",
|
|
8905
|
+
reason: "PR branch is behind its base \u2014 update it before merging."
|
|
8906
|
+
};
|
|
8907
|
+
}
|
|
8908
|
+
if (pr.mergeStateStatus !== "CLEAN") {
|
|
8909
|
+
return {
|
|
8910
|
+
ok: false,
|
|
8911
|
+
action: "MERGE_BLOCKED",
|
|
8912
|
+
reason: `PR is not green (status: ${pr.mergeStateStatus}) \u2014 required checks or reviews are not satisfied.`
|
|
8913
|
+
};
|
|
8914
|
+
}
|
|
8915
|
+
return { ok: true };
|
|
8916
|
+
}
|
|
8917
|
+
var mergeFlow = async (ctx) => {
|
|
8918
|
+
ctx.skipAgent = true;
|
|
8919
|
+
const prNumber = Number(ctx.args.pr);
|
|
8920
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
8921
|
+
process.stderr.write(`[merge] invalid --pr value: ${String(ctx.args.pr)}
|
|
8922
|
+
`);
|
|
8923
|
+
ctx.data.mergeAction = "MERGE_BLOCKED";
|
|
8924
|
+
return;
|
|
8925
|
+
}
|
|
8926
|
+
let pr;
|
|
8927
|
+
try {
|
|
8928
|
+
pr = readPr(prNumber, ctx.cwd);
|
|
8929
|
+
} catch (err) {
|
|
8930
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8931
|
+
process.stderr.write(`[merge] could not read PR #${prNumber}: ${msg}
|
|
8932
|
+
`);
|
|
8933
|
+
ctx.data.mergeAction = "MERGE_BLOCKED";
|
|
8934
|
+
return;
|
|
8935
|
+
}
|
|
8936
|
+
const verdict = evaluateMergeGate(pr);
|
|
8937
|
+
if (!verdict.ok) {
|
|
8938
|
+
process.stdout.write(`[merge] PR #${prNumber} not merged \u2014 ${verdict.reason}
|
|
8939
|
+
`);
|
|
8940
|
+
ctx.data.mergeAction = verdict.action;
|
|
8941
|
+
if (verdict.action === "MERGE_BLOCKED") {
|
|
8942
|
+
commentOnIssue(
|
|
8943
|
+
prNumber,
|
|
8944
|
+
`\u{1F6A6} _Auto-merge held: ${verdict.reason} Kody will retry once the PR is CLEAN._`,
|
|
8945
|
+
ctx.cwd
|
|
8946
|
+
);
|
|
8947
|
+
}
|
|
8948
|
+
return;
|
|
8949
|
+
}
|
|
8950
|
+
process.stdout.write(`[merge] PR #${prNumber} is CLEAN \u2014 squash-merging
|
|
8951
|
+
`);
|
|
8952
|
+
const merged = mergePrSquash(prNumber, ctx.cwd);
|
|
8953
|
+
if (!merged.ok) {
|
|
8954
|
+
process.stderr.write(`[merge] squash-merge of PR #${prNumber} failed: ${merged.error}
|
|
8955
|
+
`);
|
|
8956
|
+
ctx.data.mergeAction = "MERGE_BLOCKED";
|
|
8957
|
+
commentOnIssue(
|
|
8958
|
+
prNumber,
|
|
8959
|
+
`\u{1F6A6} _Auto-merge attempted but failed: ${merged.error}. Kody will retry next tick._`,
|
|
8960
|
+
ctx.cwd
|
|
8961
|
+
);
|
|
8962
|
+
return;
|
|
8963
|
+
}
|
|
8964
|
+
process.stdout.write(`[merge] PR #${prNumber} merged \u2705
|
|
8965
|
+
`);
|
|
8966
|
+
ctx.data.mergeAction = "MERGE_COMPLETED";
|
|
8967
|
+
};
|
|
8968
|
+
|
|
8834
8969
|
// src/scripts/mergeReleasePr.ts
|
|
8835
8970
|
import { execFileSync as execFileSync20 } from "child_process";
|
|
8836
8971
|
var API_TIMEOUT_MS7 = 6e4;
|
|
@@ -11950,7 +12085,7 @@ function appendLine(owner, repo, cwd, record) {
|
|
|
11950
12085
|
let existing = "";
|
|
11951
12086
|
let sha;
|
|
11952
12087
|
try {
|
|
11953
|
-
const out = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}`], { cwd });
|
|
12088
|
+
const out = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
11954
12089
|
const json = JSON.parse(out);
|
|
11955
12090
|
if (json.sha) sha = json.sha;
|
|
11956
12091
|
if (json.content) existing = Buffer.from(json.content, "base64").toString("utf-8");
|
|
@@ -11960,9 +12095,12 @@ function appendLine(owner, repo, cwd, record) {
|
|
|
11960
12095
|
`;
|
|
11961
12096
|
const payload = {
|
|
11962
12097
|
message: `chore(activity): ${record.action}`,
|
|
11963
|
-
content: Buffer.from(body, "utf-8").toString("base64")
|
|
12098
|
+
content: Buffer.from(body, "utf-8").toString("base64"),
|
|
12099
|
+
// Keep this high-frequency feed off the default branch.
|
|
12100
|
+
branch: STATE_BRANCH
|
|
11964
12101
|
};
|
|
11965
12102
|
if (sha) payload.sha = sha;
|
|
12103
|
+
ensureStateBranch(owner, repo, cwd);
|
|
11966
12104
|
gh(
|
|
11967
12105
|
["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"],
|
|
11968
12106
|
{ cwd, input: JSON.stringify(payload) }
|
|
@@ -12252,6 +12390,7 @@ var preflightScripts = {
|
|
|
12252
12390
|
revertFlow,
|
|
12253
12391
|
reviewFlow,
|
|
12254
12392
|
syncFlow,
|
|
12393
|
+
mergeFlow,
|
|
12255
12394
|
initFlow,
|
|
12256
12395
|
loadTaskState,
|
|
12257
12396
|
loadTaskContext,
|
|
@@ -14028,6 +14167,7 @@ Usage:
|
|
|
14028
14167
|
kody-engine fix --pr <N> [--feedback "..."] [--cwd <path>] [--verbose|--quiet]
|
|
14029
14168
|
kody-engine fix-ci --pr <N> [--run-id <ID>] [--cwd <path>] [--verbose|--quiet]
|
|
14030
14169
|
kody-engine resolve --pr <N> [--cwd <path>] [--verbose|--quiet]
|
|
14170
|
+
kody-engine merge --pr <N> [--cwd <path>] [--verbose|--quiet]
|
|
14031
14171
|
kody-engine review --pr <N> [--cwd <path>] [--verbose|--quiet]
|
|
14032
14172
|
kody-engine <other> [--cwd <path>] [--verbose|--quiet]
|
|
14033
14173
|
kody-engine ci --issue <N> [preflight flags \u2014 see: kody-engine ci --help]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "merge",
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"kind": "oneshot",
|
|
5
|
+
"describe": "Self-gating squash-merge of an open PR into its base. Deterministic, no agent: merges only when GitHub reports the PR CLEAN (mergeable + required checks/reviews satisfied); otherwise refuses and comments why. Used by the CTO qa-merge duty once a QA-goal PR has graduated.",
|
|
6
|
+
"inputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "pr",
|
|
9
|
+
"flag": "--pr",
|
|
10
|
+
"type": "int",
|
|
11
|
+
"required": true,
|
|
12
|
+
"describe": "GitHub PR number to merge. No-op (MERGE_SKIPPED) if already closed/merged; refuses (MERGE_BLOCKED) if draft, conflicting, or not yet CLEAN."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"claudeCode": {
|
|
16
|
+
"model": "inherit",
|
|
17
|
+
"permissionMode": "default",
|
|
18
|
+
"maxTurns": null,
|
|
19
|
+
"maxThinkingTokens": null,
|
|
20
|
+
"systemPromptAppend": null,
|
|
21
|
+
"tools": [],
|
|
22
|
+
"hooks": [],
|
|
23
|
+
"skills": [],
|
|
24
|
+
"commands": [],
|
|
25
|
+
"subagents": [],
|
|
26
|
+
"plugins": [],
|
|
27
|
+
"mcpServers": []
|
|
28
|
+
},
|
|
29
|
+
"cliTools": [
|
|
30
|
+
{
|
|
31
|
+
"name": "gh",
|
|
32
|
+
"install": {
|
|
33
|
+
"required": true,
|
|
34
|
+
"checkCommand": "command -v gh"
|
|
35
|
+
},
|
|
36
|
+
"verify": "gh auth status",
|
|
37
|
+
"usage": "Reads PR mergeability (`gh pr view --json`) and merges (`gh pr merge --squash --delete-branch`).",
|
|
38
|
+
"allowedUses": ["pr", "api"]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"inputArtifacts": [],
|
|
42
|
+
"outputArtifacts": [],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"preflight": [
|
|
45
|
+
{
|
|
46
|
+
"script": "mergeFlow"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"postflight": []
|
|
50
|
+
},
|
|
51
|
+
"output": {
|
|
52
|
+
"actionTypes": ["MERGE_COMPLETED", "MERGE_BLOCKED", "MERGE_SKIPPED"]
|
|
53
|
+
}
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.134",
|
|
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",
|