@kody-ade/kody-engine 0.2.12 → 0.2.14
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/kody2.js +221 -116
- package/dist/executables/fix/profile.json +48 -23
- package/dist/executables/fix-ci/profile.json +48 -23
- package/dist/executables/init/profile.json +4 -6
- package/dist/executables/orchestrator/profile.json +28 -15
- package/dist/executables/plan/profile.json +33 -16
- package/dist/executables/plan-verify/profile.json +37 -0
- package/dist/executables/plan-verify/prompt.md +53 -0
- package/dist/executables/release/profile.json +13 -8
- package/dist/executables/resolve/profile.json +42 -21
- package/dist/executables/review/profile.json +19 -10
- package/dist/executables/run/profile.json +48 -23
- package/dist/executables/types.ts +13 -11
- package/dist/executables/watch-stale-prs/profile.json +4 -7
- package/dist/plugins/commands/kody-live-probe.md +9 -0
- package/dist/plugins/hooks/kody-live-trace.json +16 -0
- package/dist/plugins/skills/kody-live-marker/SKILL.md +18 -0
- package/dist/plugins/test-plugin/.claude-plugin/plugin.json +6 -0
- package/dist/plugins/test-plugin/skills/kody-plugin-marker/SKILL.md +16 -0
- package/package.json +2 -2
package/dist/bin/kody2.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.2.
|
|
6
|
+
version: "0.2.14",
|
|
7
7
|
description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -17,7 +17,7 @@ var package_default = {
|
|
|
17
17
|
],
|
|
18
18
|
scripts: {
|
|
19
19
|
kody2: "tsx bin/kody2.ts",
|
|
20
|
-
build:
|
|
20
|
+
build: "tsup && node scripts/copy-assets.cjs",
|
|
21
21
|
test: "vitest run tests/unit tests/int --no-coverage",
|
|
22
22
|
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
23
23
|
"test:all": "vitest run tests --no-coverage",
|
|
@@ -50,8 +50,8 @@ var package_default = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
// src/executor.ts
|
|
53
|
-
import * as
|
|
54
|
-
import * as
|
|
53
|
+
import * as fs14 from "fs";
|
|
54
|
+
import * as path12 from "path";
|
|
55
55
|
|
|
56
56
|
// src/agent.ts
|
|
57
57
|
import * as fs2 from "fs";
|
|
@@ -289,6 +289,15 @@ async function runAgent(opts) {
|
|
|
289
289
|
if (opts.mcpServers && opts.mcpServers.length > 0) {
|
|
290
290
|
queryOptions.mcpServers = opts.mcpServers;
|
|
291
291
|
}
|
|
292
|
+
if (opts.pluginPaths && opts.pluginPaths.length > 0) {
|
|
293
|
+
queryOptions.plugins = opts.pluginPaths.map((p) => ({ type: "local", path: p }));
|
|
294
|
+
}
|
|
295
|
+
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
296
|
+
queryOptions.maxTurns = opts.maxTurns;
|
|
297
|
+
}
|
|
298
|
+
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
299
|
+
queryOptions.systemPrompt = { type: "preset", preset: "claude_code", append: opts.systemPromptAppend };
|
|
300
|
+
}
|
|
292
301
|
const result = query({
|
|
293
302
|
prompt: opts.prompt,
|
|
294
303
|
// biome-ignore lint/suspicious/noExplicitAny: SDK options type is narrow; mcpServers is runtime-passthrough.
|
|
@@ -540,19 +549,13 @@ function parseClaudeCode(p, raw) {
|
|
|
540
549
|
throw new ProfileError(p, `claudeCode.permissionMode must be one of default|acceptEdits|plan|bypassPermissions`);
|
|
541
550
|
}
|
|
542
551
|
const tools = Array.isArray(r.tools) ? r.tools : [];
|
|
543
|
-
const hooksRaw = r.hooks ?? {};
|
|
544
|
-
const hooks = {
|
|
545
|
-
PreToolUse: Array.isArray(hooksRaw.PreToolUse) ? hooksRaw.PreToolUse : [],
|
|
546
|
-
PostToolUse: Array.isArray(hooksRaw.PostToolUse) ? hooksRaw.PostToolUse : [],
|
|
547
|
-
Stop: Array.isArray(hooksRaw.Stop) ? hooksRaw.Stop : []
|
|
548
|
-
};
|
|
549
552
|
return {
|
|
550
553
|
model: typeof r.model === "string" ? r.model : "inherit",
|
|
551
554
|
permissionMode,
|
|
552
555
|
maxTurns: typeof r.maxTurns === "number" ? r.maxTurns : null,
|
|
553
556
|
systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
|
|
554
557
|
tools,
|
|
555
|
-
hooks,
|
|
558
|
+
hooks: Array.isArray(r.hooks) ? r.hooks : [],
|
|
556
559
|
skills: Array.isArray(r.skills) ? r.skills : [],
|
|
557
560
|
commands: Array.isArray(r.commands) ? r.commands : [],
|
|
558
561
|
subagents: Array.isArray(r.subagents) ? r.subagents : [],
|
|
@@ -617,6 +620,99 @@ function parseScriptList(p, key, raw) {
|
|
|
617
620
|
return out;
|
|
618
621
|
}
|
|
619
622
|
|
|
623
|
+
// src/scripts/buildSyntheticPlugin.ts
|
|
624
|
+
import * as fs5 from "fs";
|
|
625
|
+
import * as os2 from "os";
|
|
626
|
+
import * as path5 from "path";
|
|
627
|
+
function getPluginsCatalogRoot() {
|
|
628
|
+
const here = path5.dirname(new URL(import.meta.url).pathname);
|
|
629
|
+
const candidates = [
|
|
630
|
+
path5.join(here, "..", "plugins"),
|
|
631
|
+
// dev: src/scripts → src/plugins
|
|
632
|
+
path5.join(here, "..", "..", "plugins"),
|
|
633
|
+
// built: dist/scripts → dist/plugins
|
|
634
|
+
path5.join(here, "..", "..", "src", "plugins")
|
|
635
|
+
// fallback
|
|
636
|
+
];
|
|
637
|
+
for (const c of candidates) {
|
|
638
|
+
if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) return c;
|
|
639
|
+
}
|
|
640
|
+
return candidates[0];
|
|
641
|
+
}
|
|
642
|
+
var buildSyntheticPlugin = async (ctx, profile) => {
|
|
643
|
+
const cc = profile.claudeCode;
|
|
644
|
+
const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0 || cc.subagents.length > 0;
|
|
645
|
+
if (!needsSynthetic) return;
|
|
646
|
+
const catalog = getPluginsCatalogRoot();
|
|
647
|
+
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
648
|
+
const root = path5.join(os2.tmpdir(), `kody2-synth-${runId}`);
|
|
649
|
+
fs5.mkdirSync(path5.join(root, ".claude-plugin"), { recursive: true });
|
|
650
|
+
if (cc.skills.length > 0) {
|
|
651
|
+
const dst = path5.join(root, "skills");
|
|
652
|
+
fs5.mkdirSync(dst, { recursive: true });
|
|
653
|
+
for (const name of cc.skills) {
|
|
654
|
+
const src = path5.join(catalog, "skills", name);
|
|
655
|
+
if (!fs5.existsSync(src)) throw new Error(`buildSyntheticPlugin: skill not found in catalog: ${name}`);
|
|
656
|
+
copyDir(src, path5.join(dst, name));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (cc.commands.length > 0) {
|
|
660
|
+
const dst = path5.join(root, "commands");
|
|
661
|
+
fs5.mkdirSync(dst, { recursive: true });
|
|
662
|
+
for (const name of cc.commands) {
|
|
663
|
+
const src = path5.join(catalog, "commands", `${name}.md`);
|
|
664
|
+
if (!fs5.existsSync(src)) throw new Error(`buildSyntheticPlugin: command not found in catalog: ${name}`);
|
|
665
|
+
fs5.copyFileSync(src, path5.join(dst, `${name}.md`));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (cc.subagents.length > 0) {
|
|
669
|
+
const dst = path5.join(root, "agents");
|
|
670
|
+
fs5.mkdirSync(dst, { recursive: true });
|
|
671
|
+
for (const name of cc.subagents) {
|
|
672
|
+
const src = path5.join(catalog, "agents", `${name}.md`);
|
|
673
|
+
if (!fs5.existsSync(src)) throw new Error(`buildSyntheticPlugin: subagent not found in catalog: ${name}`);
|
|
674
|
+
fs5.copyFileSync(src, path5.join(dst, `${name}.md`));
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (cc.hooks.length > 0) {
|
|
678
|
+
const dst = path5.join(root, "hooks");
|
|
679
|
+
fs5.mkdirSync(dst, { recursive: true });
|
|
680
|
+
const merged = { hooks: {} };
|
|
681
|
+
for (const name of cc.hooks) {
|
|
682
|
+
const src = path5.join(catalog, "hooks", `${name}.json`);
|
|
683
|
+
if (!fs5.existsSync(src)) throw new Error(`buildSyntheticPlugin: hook not found in catalog: ${name}`);
|
|
684
|
+
const parsed = JSON.parse(fs5.readFileSync(src, "utf-8"));
|
|
685
|
+
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
686
|
+
if (!Array.isArray(entries)) continue;
|
|
687
|
+
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
688
|
+
merged.hooks[event].push(...entries);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
fs5.writeFileSync(path5.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
692
|
+
`);
|
|
693
|
+
}
|
|
694
|
+
const manifest = {
|
|
695
|
+
name: `kody2-synth-${profile.name}`,
|
|
696
|
+
version: "1.0.0",
|
|
697
|
+
description: `Synthetic plugin assembled by Kody2 for profile '${profile.name}' at runtime.`
|
|
698
|
+
};
|
|
699
|
+
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
700
|
+
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
701
|
+
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
702
|
+
fs5.writeFileSync(path5.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
703
|
+
`);
|
|
704
|
+
ctx.data.syntheticPluginPath = root;
|
|
705
|
+
};
|
|
706
|
+
function copyDir(src, dst) {
|
|
707
|
+
fs5.mkdirSync(dst, { recursive: true });
|
|
708
|
+
for (const ent of fs5.readdirSync(src, { withFileTypes: true })) {
|
|
709
|
+
const s = path5.join(src, ent.name);
|
|
710
|
+
const d = path5.join(dst, ent.name);
|
|
711
|
+
if (ent.isDirectory()) copyDir(s, d);
|
|
712
|
+
else if (ent.isFile()) fs5.copyFileSync(s, d);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
620
716
|
// src/coverage.ts
|
|
621
717
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
622
718
|
function patternToRegex(pattern) {
|
|
@@ -679,18 +775,18 @@ function formatMissesForFeedback(misses) {
|
|
|
679
775
|
}
|
|
680
776
|
|
|
681
777
|
// src/prompt.ts
|
|
682
|
-
import * as
|
|
683
|
-
import * as
|
|
778
|
+
import * as fs6 from "fs";
|
|
779
|
+
import * as path6 from "path";
|
|
684
780
|
var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
685
781
|
var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
686
782
|
function loadProjectConventions(projectDir) {
|
|
687
783
|
const out = [];
|
|
688
784
|
for (const rel of CONVENTION_FILES) {
|
|
689
|
-
const abs =
|
|
690
|
-
if (!
|
|
785
|
+
const abs = path6.join(projectDir, rel);
|
|
786
|
+
if (!fs6.existsSync(abs)) continue;
|
|
691
787
|
let content;
|
|
692
788
|
try {
|
|
693
|
-
content =
|
|
789
|
+
content = fs6.readFileSync(abs, "utf-8");
|
|
694
790
|
} catch {
|
|
695
791
|
continue;
|
|
696
792
|
}
|
|
@@ -767,8 +863,8 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
767
863
|
|
|
768
864
|
// src/commit.ts
|
|
769
865
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
770
|
-
import * as
|
|
771
|
-
import * as
|
|
866
|
+
import * as fs7 from "fs";
|
|
867
|
+
import * as path7 from "path";
|
|
772
868
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
773
869
|
".kody/",
|
|
774
870
|
".kody-engine/",
|
|
@@ -823,18 +919,18 @@ function tryGit(args, cwd) {
|
|
|
823
919
|
}
|
|
824
920
|
function abortUnfinishedGitOps(cwd) {
|
|
825
921
|
const aborted = [];
|
|
826
|
-
const gitDir =
|
|
827
|
-
if (!
|
|
828
|
-
if (
|
|
922
|
+
const gitDir = path7.join(cwd ?? process.cwd(), ".git");
|
|
923
|
+
if (!fs7.existsSync(gitDir)) return aborted;
|
|
924
|
+
if (fs7.existsSync(path7.join(gitDir, "MERGE_HEAD"))) {
|
|
829
925
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
830
926
|
}
|
|
831
|
-
if (
|
|
927
|
+
if (fs7.existsSync(path7.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
832
928
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
833
929
|
}
|
|
834
|
-
if (
|
|
930
|
+
if (fs7.existsSync(path7.join(gitDir, "REVERT_HEAD"))) {
|
|
835
931
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
836
932
|
}
|
|
837
|
-
if (
|
|
933
|
+
if (fs7.existsSync(path7.join(gitDir, "rebase-merge")) || fs7.existsSync(path7.join(gitDir, "rebase-apply"))) {
|
|
838
934
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
839
935
|
}
|
|
840
936
|
try {
|
|
@@ -876,7 +972,7 @@ function normalizeCommitMessage(raw) {
|
|
|
876
972
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
877
973
|
const allChanged = listChangedFiles(cwd);
|
|
878
974
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
879
|
-
const mergeHeadExists =
|
|
975
|
+
const mergeHeadExists = fs7.existsSync(path7.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
880
976
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
881
977
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
882
978
|
}
|
|
@@ -966,20 +1062,20 @@ function defaultCommitMessage(mode, data) {
|
|
|
966
1062
|
}
|
|
967
1063
|
|
|
968
1064
|
// src/scripts/composePrompt.ts
|
|
969
|
-
import * as
|
|
970
|
-
import * as
|
|
1065
|
+
import * as fs8 from "fs";
|
|
1066
|
+
import * as path8 from "path";
|
|
971
1067
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
972
1068
|
var composePrompt = async (ctx, profile) => {
|
|
973
1069
|
const explicit = ctx.data.promptTemplate;
|
|
974
1070
|
const mode = ctx.args.mode;
|
|
975
1071
|
const candidates = [
|
|
976
|
-
explicit ?
|
|
977
|
-
mode ?
|
|
978
|
-
|
|
1072
|
+
explicit ? path8.join(profile.dir, explicit) : null,
|
|
1073
|
+
mode ? path8.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
1074
|
+
path8.join(profile.dir, "prompt.md")
|
|
979
1075
|
].filter(Boolean);
|
|
980
1076
|
let templatePath = "";
|
|
981
1077
|
for (const c of candidates) {
|
|
982
|
-
if (
|
|
1078
|
+
if (fs8.existsSync(c)) {
|
|
983
1079
|
templatePath = c;
|
|
984
1080
|
break;
|
|
985
1081
|
}
|
|
@@ -987,7 +1083,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
987
1083
|
if (!templatePath) {
|
|
988
1084
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
989
1085
|
}
|
|
990
|
-
const template =
|
|
1086
|
+
const template = fs8.readFileSync(templatePath, "utf-8");
|
|
991
1087
|
const tokens = {
|
|
992
1088
|
...stringifyAll(ctx.args, "args."),
|
|
993
1089
|
...stringifyAll(ctx.data, ""),
|
|
@@ -1459,7 +1555,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
1459
1555
|
|
|
1460
1556
|
// src/gha.ts
|
|
1461
1557
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
1462
|
-
import * as
|
|
1558
|
+
import * as fs9 from "fs";
|
|
1463
1559
|
function getRunUrl() {
|
|
1464
1560
|
const server = process.env.GITHUB_SERVER_URL;
|
|
1465
1561
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -1470,10 +1566,10 @@ function getRunUrl() {
|
|
|
1470
1566
|
function reactToTriggerComment(cwd) {
|
|
1471
1567
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
1472
1568
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
1473
|
-
if (!eventPath || !
|
|
1569
|
+
if (!eventPath || !fs9.existsSync(eventPath)) return;
|
|
1474
1570
|
let event = null;
|
|
1475
1571
|
try {
|
|
1476
|
-
event = JSON.parse(
|
|
1572
|
+
event = JSON.parse(fs9.readFileSync(eventPath, "utf-8"));
|
|
1477
1573
|
} catch {
|
|
1478
1574
|
return;
|
|
1479
1575
|
}
|
|
@@ -1672,35 +1768,35 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
1672
1768
|
|
|
1673
1769
|
// src/scripts/initFlow.ts
|
|
1674
1770
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
1675
|
-
import * as
|
|
1676
|
-
import * as
|
|
1771
|
+
import * as fs11 from "fs";
|
|
1772
|
+
import * as path10 from "path";
|
|
1677
1773
|
|
|
1678
1774
|
// src/registry.ts
|
|
1679
|
-
import * as
|
|
1680
|
-
import * as
|
|
1775
|
+
import * as fs10 from "fs";
|
|
1776
|
+
import * as path9 from "path";
|
|
1681
1777
|
function getExecutablesRoot() {
|
|
1682
|
-
const here =
|
|
1778
|
+
const here = path9.dirname(new URL(import.meta.url).pathname);
|
|
1683
1779
|
const candidates = [
|
|
1684
|
-
|
|
1780
|
+
path9.join(here, "executables"),
|
|
1685
1781
|
// dev: src/
|
|
1686
|
-
|
|
1782
|
+
path9.join(here, "..", "executables"),
|
|
1687
1783
|
// built: dist/bin → dist/executables
|
|
1688
|
-
|
|
1784
|
+
path9.join(here, "..", "src", "executables")
|
|
1689
1785
|
// fallback
|
|
1690
1786
|
];
|
|
1691
1787
|
for (const c of candidates) {
|
|
1692
|
-
if (
|
|
1788
|
+
if (fs10.existsSync(c) && fs10.statSync(c).isDirectory()) return c;
|
|
1693
1789
|
}
|
|
1694
1790
|
return candidates[0];
|
|
1695
1791
|
}
|
|
1696
1792
|
function listExecutables(root = getExecutablesRoot()) {
|
|
1697
|
-
if (!
|
|
1698
|
-
const entries =
|
|
1793
|
+
if (!fs10.existsSync(root)) return [];
|
|
1794
|
+
const entries = fs10.readdirSync(root, { withFileTypes: true });
|
|
1699
1795
|
const out = [];
|
|
1700
1796
|
for (const ent of entries) {
|
|
1701
1797
|
if (!ent.isDirectory()) continue;
|
|
1702
|
-
const profilePath =
|
|
1703
|
-
if (
|
|
1798
|
+
const profilePath = path9.join(root, ent.name, "profile.json");
|
|
1799
|
+
if (fs10.existsSync(profilePath) && fs10.statSync(profilePath).isFile()) {
|
|
1704
1800
|
out.push({ name: ent.name, profilePath });
|
|
1705
1801
|
}
|
|
1706
1802
|
}
|
|
@@ -1708,8 +1804,8 @@ function listExecutables(root = getExecutablesRoot()) {
|
|
|
1708
1804
|
}
|
|
1709
1805
|
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
1710
1806
|
if (!isSafeName(name)) return false;
|
|
1711
|
-
const profilePath =
|
|
1712
|
-
return
|
|
1807
|
+
const profilePath = path9.join(root, name, "profile.json");
|
|
1808
|
+
return fs10.existsSync(profilePath) && fs10.statSync(profilePath).isFile();
|
|
1713
1809
|
}
|
|
1714
1810
|
function isSafeName(name) {
|
|
1715
1811
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
@@ -1738,9 +1834,9 @@ function parseGenericFlags(argv) {
|
|
|
1738
1834
|
|
|
1739
1835
|
// src/scripts/initFlow.ts
|
|
1740
1836
|
function detectPackageManager(cwd) {
|
|
1741
|
-
if (
|
|
1742
|
-
if (
|
|
1743
|
-
if (
|
|
1837
|
+
if (fs11.existsSync(path10.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1838
|
+
if (fs11.existsSync(path10.join(cwd, "yarn.lock"))) return "yarn";
|
|
1839
|
+
if (fs11.existsSync(path10.join(cwd, "bun.lockb"))) return "bun";
|
|
1744
1840
|
return "npm";
|
|
1745
1841
|
}
|
|
1746
1842
|
function qualityCommandsFor(pm) {
|
|
@@ -1861,22 +1957,22 @@ function performInit(cwd, force) {
|
|
|
1861
1957
|
const pm = detectPackageManager(cwd);
|
|
1862
1958
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
1863
1959
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
1864
|
-
const configPath =
|
|
1865
|
-
if (
|
|
1960
|
+
const configPath = path10.join(cwd, "kody.config.json");
|
|
1961
|
+
if (fs11.existsSync(configPath) && !force) {
|
|
1866
1962
|
skipped.push("kody.config.json");
|
|
1867
1963
|
} else {
|
|
1868
1964
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
1869
|
-
|
|
1965
|
+
fs11.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
1870
1966
|
`);
|
|
1871
1967
|
wrote.push("kody.config.json");
|
|
1872
1968
|
}
|
|
1873
|
-
const workflowDir =
|
|
1874
|
-
const workflowPath =
|
|
1875
|
-
if (
|
|
1969
|
+
const workflowDir = path10.join(cwd, ".github", "workflows");
|
|
1970
|
+
const workflowPath = path10.join(workflowDir, "kody2.yml");
|
|
1971
|
+
if (fs11.existsSync(workflowPath) && !force) {
|
|
1876
1972
|
skipped.push(".github/workflows/kody2.yml");
|
|
1877
1973
|
} else {
|
|
1878
|
-
|
|
1879
|
-
|
|
1974
|
+
fs11.mkdirSync(workflowDir, { recursive: true });
|
|
1975
|
+
fs11.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
1880
1976
|
wrote.push(".github/workflows/kody2.yml");
|
|
1881
1977
|
}
|
|
1882
1978
|
for (const exe of listExecutables()) {
|
|
@@ -1887,12 +1983,12 @@ function performInit(cwd, force) {
|
|
|
1887
1983
|
continue;
|
|
1888
1984
|
}
|
|
1889
1985
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
1890
|
-
const target =
|
|
1891
|
-
if (
|
|
1986
|
+
const target = path10.join(workflowDir, `kody2-${exe.name}.yml`);
|
|
1987
|
+
if (fs11.existsSync(target) && !force) {
|
|
1892
1988
|
skipped.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
1893
1989
|
continue;
|
|
1894
1990
|
}
|
|
1895
|
-
|
|
1991
|
+
fs11.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
1896
1992
|
wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
1897
1993
|
}
|
|
1898
1994
|
return { wrote, skipped };
|
|
@@ -2308,8 +2404,8 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
2308
2404
|
|
|
2309
2405
|
// src/scripts/releaseFlow.ts
|
|
2310
2406
|
import { execFileSync as execFileSync11, spawnSync } from "child_process";
|
|
2311
|
-
import * as
|
|
2312
|
-
import * as
|
|
2407
|
+
import * as fs12 from "fs";
|
|
2408
|
+
import * as path11 from "path";
|
|
2313
2409
|
function bumpVersion(current, bump) {
|
|
2314
2410
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
2315
2411
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -2325,12 +2421,12 @@ function bumpVersion(current, bump) {
|
|
|
2325
2421
|
return `${major}.${minor}.${patch}`;
|
|
2326
2422
|
}
|
|
2327
2423
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
2328
|
-
const abs =
|
|
2329
|
-
if (!
|
|
2330
|
-
const content =
|
|
2424
|
+
const abs = path11.join(cwd, file);
|
|
2425
|
+
if (!fs12.existsSync(abs)) return false;
|
|
2426
|
+
const content = fs12.readFileSync(abs, "utf-8");
|
|
2331
2427
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
2332
2428
|
if (updated === content) return false;
|
|
2333
|
-
|
|
2429
|
+
fs12.writeFileSync(abs, updated);
|
|
2334
2430
|
return true;
|
|
2335
2431
|
}
|
|
2336
2432
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
@@ -2378,19 +2474,19 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
2378
2474
|
return parts.join("\n");
|
|
2379
2475
|
}
|
|
2380
2476
|
function prependChangelog(cwd, entry) {
|
|
2381
|
-
const p =
|
|
2477
|
+
const p = path11.join(cwd, "CHANGELOG.md");
|
|
2382
2478
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
2383
|
-
if (
|
|
2384
|
-
const prior =
|
|
2479
|
+
if (fs12.existsSync(p)) {
|
|
2480
|
+
const prior = fs12.readFileSync(p, "utf-8");
|
|
2385
2481
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
2386
2482
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
2387
|
-
|
|
2483
|
+
fs12.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
2388
2484
|
${entry}${prior.slice(idx + 1)}`);
|
|
2389
2485
|
} else {
|
|
2390
|
-
|
|
2486
|
+
fs12.writeFileSync(p, `${header}${entry}${prior}`);
|
|
2391
2487
|
}
|
|
2392
2488
|
} else {
|
|
2393
|
-
|
|
2489
|
+
fs12.writeFileSync(p, `${header}${entry}`);
|
|
2394
2490
|
}
|
|
2395
2491
|
}
|
|
2396
2492
|
function git3(args, cwd, timeout = 6e4) {
|
|
@@ -2441,13 +2537,13 @@ var releaseFlow = async (ctx) => {
|
|
|
2441
2537
|
};
|
|
2442
2538
|
async function runPrepare(args) {
|
|
2443
2539
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
2444
|
-
const pkgPath =
|
|
2445
|
-
if (!
|
|
2540
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
2541
|
+
if (!fs12.existsSync(pkgPath)) {
|
|
2446
2542
|
ctx.output.exitCode = 99;
|
|
2447
2543
|
ctx.output.reason = "release prepare: package.json not found";
|
|
2448
2544
|
return;
|
|
2449
2545
|
}
|
|
2450
|
-
const pkg = JSON.parse(
|
|
2546
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
2451
2547
|
if (typeof pkg.version !== "string") {
|
|
2452
2548
|
ctx.output.exitCode = 99;
|
|
2453
2549
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -2518,8 +2614,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
2518
2614
|
}
|
|
2519
2615
|
async function runFinalize(args) {
|
|
2520
2616
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
2521
|
-
const pkgPath =
|
|
2522
|
-
const pkg = JSON.parse(
|
|
2617
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
2618
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
2523
2619
|
if (typeof pkg.version !== "string") {
|
|
2524
2620
|
ctx.output.exitCode = 99;
|
|
2525
2621
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -2790,7 +2886,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
2790
2886
|
var TAIL_CHARS = 4e3;
|
|
2791
2887
|
var COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2792
2888
|
function runCommand(command, cwd) {
|
|
2793
|
-
return new Promise((
|
|
2889
|
+
return new Promise((resolve3) => {
|
|
2794
2890
|
const start = Date.now();
|
|
2795
2891
|
const child = spawn2(command, {
|
|
2796
2892
|
cwd,
|
|
@@ -2819,11 +2915,11 @@ function runCommand(command, cwd) {
|
|
|
2819
2915
|
child.on("exit", (code) => {
|
|
2820
2916
|
clearTimeout(timer);
|
|
2821
2917
|
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
2822
|
-
|
|
2918
|
+
resolve3({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
2823
2919
|
});
|
|
2824
2920
|
child.on("error", (err) => {
|
|
2825
2921
|
clearTimeout(timer);
|
|
2826
|
-
|
|
2922
|
+
resolve3({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
2827
2923
|
});
|
|
2828
2924
|
});
|
|
2829
2925
|
}
|
|
@@ -2949,7 +3045,7 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
2949
3045
|
};
|
|
2950
3046
|
|
|
2951
3047
|
// src/scripts/writeRunSummary.ts
|
|
2952
|
-
import * as
|
|
3048
|
+
import * as fs13 from "fs";
|
|
2953
3049
|
var writeRunSummary = async (ctx, profile) => {
|
|
2954
3050
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
2955
3051
|
if (!summaryPath) return;
|
|
@@ -2971,7 +3067,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
2971
3067
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
2972
3068
|
lines.push("");
|
|
2973
3069
|
try {
|
|
2974
|
-
|
|
3070
|
+
fs13.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
2975
3071
|
`);
|
|
2976
3072
|
} catch {
|
|
2977
3073
|
}
|
|
@@ -2991,6 +3087,7 @@ var preflightScripts = {
|
|
|
2991
3087
|
loadIssueContext,
|
|
2992
3088
|
loadConventions,
|
|
2993
3089
|
loadCoverageRules,
|
|
3090
|
+
buildSyntheticPlugin,
|
|
2994
3091
|
composePrompt
|
|
2995
3092
|
};
|
|
2996
3093
|
var postflightScripts = {
|
|
@@ -3111,19 +3208,27 @@ async function runExecutable(profileName, input) {
|
|
|
3111
3208
|
data: {},
|
|
3112
3209
|
output: { exitCode: 0 }
|
|
3113
3210
|
};
|
|
3114
|
-
const ndjsonDir =
|
|
3115
|
-
const invokeAgent = async (prompt) =>
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3211
|
+
const ndjsonDir = path12.join(input.cwd, ".kody2");
|
|
3212
|
+
const invokeAgent = async (prompt) => {
|
|
3213
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path12.isAbsolute(p) ? p : path12.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
3214
|
+
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
3215
|
+
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
3216
|
+
return runAgent({
|
|
3217
|
+
prompt,
|
|
3218
|
+
model,
|
|
3219
|
+
cwd: input.cwd,
|
|
3220
|
+
litellmUrl: litellm?.url ?? null,
|
|
3221
|
+
verbose: input.verbose,
|
|
3222
|
+
quiet: input.quiet,
|
|
3223
|
+
ndjsonDir,
|
|
3224
|
+
allowedToolsOverride: profile.claudeCode.tools,
|
|
3225
|
+
permissionModeOverride: profile.claudeCode.permissionMode,
|
|
3226
|
+
mcpServers: profile.claudeCode.mcpServers,
|
|
3227
|
+
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
3228
|
+
maxTurns: profile.claudeCode.maxTurns,
|
|
3229
|
+
systemPromptAppend: profile.claudeCode.systemPromptAppend
|
|
3230
|
+
});
|
|
3231
|
+
};
|
|
3127
3232
|
ctx.data.__invokeAgent = invokeAgent;
|
|
3128
3233
|
try {
|
|
3129
3234
|
for (const entry of profile.scripts.preflight) {
|
|
@@ -3170,17 +3275,17 @@ async function runExecutable(profileName, input) {
|
|
|
3170
3275
|
}
|
|
3171
3276
|
}
|
|
3172
3277
|
function resolveProfilePath(profileName) {
|
|
3173
|
-
const here =
|
|
3278
|
+
const here = path12.dirname(new URL(import.meta.url).pathname);
|
|
3174
3279
|
const candidates = [
|
|
3175
|
-
|
|
3280
|
+
path12.join(here, "executables", profileName, "profile.json"),
|
|
3176
3281
|
// same-dir sibling (dev)
|
|
3177
|
-
|
|
3282
|
+
path12.join(here, "..", "executables", profileName, "profile.json"),
|
|
3178
3283
|
// up one (prod: dist/bin → dist/executables)
|
|
3179
|
-
|
|
3284
|
+
path12.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
3180
3285
|
// fallback
|
|
3181
3286
|
];
|
|
3182
3287
|
for (const c of candidates) {
|
|
3183
|
-
if (
|
|
3288
|
+
if (fs14.existsSync(c)) return c;
|
|
3184
3289
|
}
|
|
3185
3290
|
return candidates[0];
|
|
3186
3291
|
}
|
|
@@ -3273,11 +3378,11 @@ function finish(out) {
|
|
|
3273
3378
|
|
|
3274
3379
|
// src/kody2-cli.ts
|
|
3275
3380
|
import { execFileSync as execFileSync14 } from "child_process";
|
|
3276
|
-
import * as
|
|
3277
|
-
import * as
|
|
3381
|
+
import * as fs16 from "fs";
|
|
3382
|
+
import * as path13 from "path";
|
|
3278
3383
|
|
|
3279
3384
|
// src/dispatch.ts
|
|
3280
|
-
import * as
|
|
3385
|
+
import * as fs15 from "fs";
|
|
3281
3386
|
function autoDispatch(opts) {
|
|
3282
3387
|
const explicit = opts?.explicit;
|
|
3283
3388
|
if (explicit?.issueNumber && explicit.issueNumber > 0) {
|
|
@@ -3289,10 +3394,10 @@ function autoDispatch(opts) {
|
|
|
3289
3394
|
}
|
|
3290
3395
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
3291
3396
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
3292
|
-
if (!eventName || !eventPath || !
|
|
3397
|
+
if (!eventName || !eventPath || !fs15.existsSync(eventPath)) return null;
|
|
3293
3398
|
let event = {};
|
|
3294
3399
|
try {
|
|
3295
|
-
event = JSON.parse(
|
|
3400
|
+
event = JSON.parse(fs15.readFileSync(eventPath, "utf-8"));
|
|
3296
3401
|
} catch {
|
|
3297
3402
|
return null;
|
|
3298
3403
|
}
|
|
@@ -3442,9 +3547,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
3442
3547
|
return token;
|
|
3443
3548
|
}
|
|
3444
3549
|
function detectPackageManager2(cwd) {
|
|
3445
|
-
if (
|
|
3446
|
-
if (
|
|
3447
|
-
if (
|
|
3550
|
+
if (fs16.existsSync(path13.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
3551
|
+
if (fs16.existsSync(path13.join(cwd, "yarn.lock"))) return "yarn";
|
|
3552
|
+
if (fs16.existsSync(path13.join(cwd, "bun.lockb"))) return "bun";
|
|
3448
3553
|
return "npm";
|
|
3449
3554
|
}
|
|
3450
3555
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -3521,11 +3626,11 @@ function configureGitIdentity(cwd) {
|
|
|
3521
3626
|
}
|
|
3522
3627
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
3523
3628
|
if (!issueNumber) return;
|
|
3524
|
-
const logPath =
|
|
3629
|
+
const logPath = path13.join(cwd, ".kody2", "last-run.jsonl");
|
|
3525
3630
|
let tail = "";
|
|
3526
3631
|
try {
|
|
3527
|
-
if (
|
|
3528
|
-
const content =
|
|
3632
|
+
if (fs16.existsSync(logPath)) {
|
|
3633
|
+
const content = fs16.readFileSync(logPath, "utf-8");
|
|
3529
3634
|
tail = content.slice(-3e3);
|
|
3530
3635
|
}
|
|
3531
3636
|
} catch {
|
|
@@ -3550,7 +3655,7 @@ async function runCi(argv) {
|
|
|
3550
3655
|
return 0;
|
|
3551
3656
|
}
|
|
3552
3657
|
const args = parseCiArgs(argv);
|
|
3553
|
-
const cwd = args.cwd ?
|
|
3658
|
+
const cwd = args.cwd ? path13.resolve(args.cwd) : process.cwd();
|
|
3554
3659
|
let earlyConfig;
|
|
3555
3660
|
try {
|
|
3556
3661
|
earlyConfig = loadConfig(cwd);
|