@kody-ade/kody-engine 0.2.11 → 0.2.13
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/README.md +5 -4
- package/dist/bin/kody2.js +309 -243
- package/dist/executables/fix/profile.json +93 -0
- package/dist/executables/fix-ci/profile.json +93 -0
- 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 +80 -0
- package/dist/executables/review/profile.json +19 -10
- package/dist/executables/run/profile.json +86 -0
- 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 +17 -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/executables/build/profile.json +0 -99
- package/dist/executables/orchestrator/prompts/orchestrator.md +0 -56
- package/dist/executables/plan/prompts/plan.md +0 -42
- /package/dist/executables/{build/prompts/fix.md → fix/prompt.md} +0 -0
- /package/dist/executables/{build/prompts/fix-ci.md → fix-ci/prompt.md} +0 -0
- /package/dist/executables/{build/prompts/resolve.md → resolve/prompt.md} +0 -0
- /package/dist/executables/{build/prompts/run.md → run/prompt.md} +0 -0
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.13",
|
|
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",
|
|
@@ -49,6 +49,15 @@ var package_default = {
|
|
|
49
49
|
bugs: "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
// src/executor.ts
|
|
53
|
+
import * as fs14 from "fs";
|
|
54
|
+
import * as path12 from "path";
|
|
55
|
+
|
|
56
|
+
// src/agent.ts
|
|
57
|
+
import * as fs2 from "fs";
|
|
58
|
+
import * as path2 from "path";
|
|
59
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
60
|
+
|
|
52
61
|
// src/config.ts
|
|
53
62
|
import * as fs from "fs";
|
|
54
63
|
import * as path from "path";
|
|
@@ -151,15 +160,6 @@ function getAnthropicApiKeyOrDummy() {
|
|
|
151
160
|
return process.env.ANTHROPIC_API_KEY || `sk-ant-api03-${"0".repeat(64)}`;
|
|
152
161
|
}
|
|
153
162
|
|
|
154
|
-
// src/executor.ts
|
|
155
|
-
import * as fs13 from "fs";
|
|
156
|
-
import * as path11 from "path";
|
|
157
|
-
|
|
158
|
-
// src/agent.ts
|
|
159
|
-
import * as fs2 from "fs";
|
|
160
|
-
import * as path2 from "path";
|
|
161
|
-
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
162
|
-
|
|
163
163
|
// src/format.ts
|
|
164
164
|
function renderEvent(msg, opts = {}) {
|
|
165
165
|
if (opts.quiet) {
|
|
@@ -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
|
}
|
|
@@ -919,13 +1015,14 @@ function hasCommitsAhead(branch, defaultBranch, cwd) {
|
|
|
919
1015
|
}
|
|
920
1016
|
|
|
921
1017
|
// src/scripts/commitAndPush.ts
|
|
922
|
-
var commitAndPush2 = async (ctx) => {
|
|
1018
|
+
var commitAndPush2 = async (ctx, profile) => {
|
|
923
1019
|
const branch = ctx.data.branch;
|
|
924
1020
|
if (!branch) {
|
|
925
1021
|
ctx.data.commitResult = { committed: false, pushed: false };
|
|
926
1022
|
return;
|
|
927
1023
|
}
|
|
928
|
-
|
|
1024
|
+
const kind = profile.name;
|
|
1025
|
+
if (kind === "resolve") {
|
|
929
1026
|
try {
|
|
930
1027
|
execFileSync4("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
|
|
931
1028
|
} catch {
|
|
@@ -937,7 +1034,7 @@ var commitAndPush2 = async (ctx) => {
|
|
|
937
1034
|
`);
|
|
938
1035
|
}
|
|
939
1036
|
}
|
|
940
|
-
const fallbackMsg = defaultCommitMessage(
|
|
1037
|
+
const fallbackMsg = defaultCommitMessage(kind, ctx.data);
|
|
941
1038
|
const message = ctx.data.commitMessage || fallbackMsg;
|
|
942
1039
|
try {
|
|
943
1040
|
const result = commitAndPush(branch, message, ctx.cwd);
|
|
@@ -965,20 +1062,20 @@ function defaultCommitMessage(mode, data) {
|
|
|
965
1062
|
}
|
|
966
1063
|
|
|
967
1064
|
// src/scripts/composePrompt.ts
|
|
968
|
-
import * as
|
|
969
|
-
import * as
|
|
1065
|
+
import * as fs8 from "fs";
|
|
1066
|
+
import * as path8 from "path";
|
|
970
1067
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
971
1068
|
var composePrompt = async (ctx, profile) => {
|
|
972
1069
|
const explicit = ctx.data.promptTemplate;
|
|
973
1070
|
const mode = ctx.args.mode;
|
|
974
1071
|
const candidates = [
|
|
975
|
-
explicit ?
|
|
976
|
-
mode ?
|
|
977
|
-
|
|
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")
|
|
978
1075
|
].filter(Boolean);
|
|
979
1076
|
let templatePath = "";
|
|
980
1077
|
for (const c of candidates) {
|
|
981
|
-
if (
|
|
1078
|
+
if (fs8.existsSync(c)) {
|
|
982
1079
|
templatePath = c;
|
|
983
1080
|
break;
|
|
984
1081
|
}
|
|
@@ -986,7 +1083,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
986
1083
|
if (!templatePath) {
|
|
987
1084
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
988
1085
|
}
|
|
989
|
-
const template =
|
|
1086
|
+
const template = fs8.readFileSync(templatePath, "utf-8");
|
|
990
1087
|
const tokens = {
|
|
991
1088
|
...stringifyAll(ctx.args, "args."),
|
|
992
1089
|
...stringifyAll(ctx.data, ""),
|
|
@@ -1458,7 +1555,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
1458
1555
|
|
|
1459
1556
|
// src/gha.ts
|
|
1460
1557
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
1461
|
-
import * as
|
|
1558
|
+
import * as fs9 from "fs";
|
|
1462
1559
|
function getRunUrl() {
|
|
1463
1560
|
const server = process.env.GITHUB_SERVER_URL;
|
|
1464
1561
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -1469,10 +1566,10 @@ function getRunUrl() {
|
|
|
1469
1566
|
function reactToTriggerComment(cwd) {
|
|
1470
1567
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
1471
1568
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
1472
|
-
if (!eventPath || !
|
|
1569
|
+
if (!eventPath || !fs9.existsSync(eventPath)) return;
|
|
1473
1570
|
let event = null;
|
|
1474
1571
|
try {
|
|
1475
|
-
event = JSON.parse(
|
|
1572
|
+
event = JSON.parse(fs9.readFileSync(eventPath, "utf-8"));
|
|
1476
1573
|
} catch {
|
|
1477
1574
|
return;
|
|
1478
1575
|
}
|
|
@@ -1671,35 +1768,35 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
1671
1768
|
|
|
1672
1769
|
// src/scripts/initFlow.ts
|
|
1673
1770
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
1674
|
-
import * as
|
|
1675
|
-
import * as
|
|
1771
|
+
import * as fs11 from "fs";
|
|
1772
|
+
import * as path10 from "path";
|
|
1676
1773
|
|
|
1677
1774
|
// src/registry.ts
|
|
1678
|
-
import * as
|
|
1679
|
-
import * as
|
|
1775
|
+
import * as fs10 from "fs";
|
|
1776
|
+
import * as path9 from "path";
|
|
1680
1777
|
function getExecutablesRoot() {
|
|
1681
|
-
const here =
|
|
1778
|
+
const here = path9.dirname(new URL(import.meta.url).pathname);
|
|
1682
1779
|
const candidates = [
|
|
1683
|
-
|
|
1780
|
+
path9.join(here, "executables"),
|
|
1684
1781
|
// dev: src/
|
|
1685
|
-
|
|
1782
|
+
path9.join(here, "..", "executables"),
|
|
1686
1783
|
// built: dist/bin → dist/executables
|
|
1687
|
-
|
|
1784
|
+
path9.join(here, "..", "src", "executables")
|
|
1688
1785
|
// fallback
|
|
1689
1786
|
];
|
|
1690
1787
|
for (const c of candidates) {
|
|
1691
|
-
if (
|
|
1788
|
+
if (fs10.existsSync(c) && fs10.statSync(c).isDirectory()) return c;
|
|
1692
1789
|
}
|
|
1693
1790
|
return candidates[0];
|
|
1694
1791
|
}
|
|
1695
1792
|
function listExecutables(root = getExecutablesRoot()) {
|
|
1696
|
-
if (!
|
|
1697
|
-
const entries =
|
|
1793
|
+
if (!fs10.existsSync(root)) return [];
|
|
1794
|
+
const entries = fs10.readdirSync(root, { withFileTypes: true });
|
|
1698
1795
|
const out = [];
|
|
1699
1796
|
for (const ent of entries) {
|
|
1700
1797
|
if (!ent.isDirectory()) continue;
|
|
1701
|
-
const profilePath =
|
|
1702
|
-
if (
|
|
1798
|
+
const profilePath = path9.join(root, ent.name, "profile.json");
|
|
1799
|
+
if (fs10.existsSync(profilePath) && fs10.statSync(profilePath).isFile()) {
|
|
1703
1800
|
out.push({ name: ent.name, profilePath });
|
|
1704
1801
|
}
|
|
1705
1802
|
}
|
|
@@ -1707,8 +1804,8 @@ function listExecutables(root = getExecutablesRoot()) {
|
|
|
1707
1804
|
}
|
|
1708
1805
|
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
1709
1806
|
if (!isSafeName(name)) return false;
|
|
1710
|
-
const profilePath =
|
|
1711
|
-
return
|
|
1807
|
+
const profilePath = path9.join(root, name, "profile.json");
|
|
1808
|
+
return fs10.existsSync(profilePath) && fs10.statSync(profilePath).isFile();
|
|
1712
1809
|
}
|
|
1713
1810
|
function isSafeName(name) {
|
|
1714
1811
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
@@ -1724,11 +1821,11 @@ function parseGenericFlags(argv) {
|
|
|
1724
1821
|
}
|
|
1725
1822
|
const key = arg.slice(2);
|
|
1726
1823
|
const next = argv[i + 1];
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
args[
|
|
1824
|
+
const value = next !== void 0 && !next.startsWith("--") ? (i++, next) : true;
|
|
1825
|
+
args[key] = value;
|
|
1826
|
+
if (key.includes("-")) {
|
|
1827
|
+
const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1828
|
+
if (camel !== key && args[camel] === void 0) args[camel] = value;
|
|
1732
1829
|
}
|
|
1733
1830
|
}
|
|
1734
1831
|
if (positional.length > 0) args._ = positional;
|
|
@@ -1737,9 +1834,9 @@ function parseGenericFlags(argv) {
|
|
|
1737
1834
|
|
|
1738
1835
|
// src/scripts/initFlow.ts
|
|
1739
1836
|
function detectPackageManager(cwd) {
|
|
1740
|
-
if (
|
|
1741
|
-
if (
|
|
1742
|
-
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";
|
|
1743
1840
|
return "npm";
|
|
1744
1841
|
}
|
|
1745
1842
|
function qualityCommandsFor(pm) {
|
|
@@ -1860,22 +1957,22 @@ function performInit(cwd, force) {
|
|
|
1860
1957
|
const pm = detectPackageManager(cwd);
|
|
1861
1958
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
1862
1959
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
1863
|
-
const configPath =
|
|
1864
|
-
if (
|
|
1960
|
+
const configPath = path10.join(cwd, "kody.config.json");
|
|
1961
|
+
if (fs11.existsSync(configPath) && !force) {
|
|
1865
1962
|
skipped.push("kody.config.json");
|
|
1866
1963
|
} else {
|
|
1867
1964
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
1868
|
-
|
|
1965
|
+
fs11.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
1869
1966
|
`);
|
|
1870
1967
|
wrote.push("kody.config.json");
|
|
1871
1968
|
}
|
|
1872
|
-
const workflowDir =
|
|
1873
|
-
const workflowPath =
|
|
1874
|
-
if (
|
|
1969
|
+
const workflowDir = path10.join(cwd, ".github", "workflows");
|
|
1970
|
+
const workflowPath = path10.join(workflowDir, "kody2.yml");
|
|
1971
|
+
if (fs11.existsSync(workflowPath) && !force) {
|
|
1875
1972
|
skipped.push(".github/workflows/kody2.yml");
|
|
1876
1973
|
} else {
|
|
1877
|
-
|
|
1878
|
-
|
|
1974
|
+
fs11.mkdirSync(workflowDir, { recursive: true });
|
|
1975
|
+
fs11.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
1879
1976
|
wrote.push(".github/workflows/kody2.yml");
|
|
1880
1977
|
}
|
|
1881
1978
|
for (const exe of listExecutables()) {
|
|
@@ -1886,12 +1983,12 @@ function performInit(cwd, force) {
|
|
|
1886
1983
|
continue;
|
|
1887
1984
|
}
|
|
1888
1985
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
1889
|
-
const target =
|
|
1890
|
-
if (
|
|
1986
|
+
const target = path10.join(workflowDir, `kody2-${exe.name}.yml`);
|
|
1987
|
+
if (fs11.existsSync(target) && !force) {
|
|
1891
1988
|
skipped.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
1892
1989
|
continue;
|
|
1893
1990
|
}
|
|
1894
|
-
|
|
1991
|
+
fs11.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
1895
1992
|
wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
1896
1993
|
}
|
|
1897
1994
|
return { wrote, skipped };
|
|
@@ -2307,8 +2404,8 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
2307
2404
|
|
|
2308
2405
|
// src/scripts/releaseFlow.ts
|
|
2309
2406
|
import { execFileSync as execFileSync11, spawnSync } from "child_process";
|
|
2310
|
-
import * as
|
|
2311
|
-
import * as
|
|
2407
|
+
import * as fs12 from "fs";
|
|
2408
|
+
import * as path11 from "path";
|
|
2312
2409
|
function bumpVersion(current, bump) {
|
|
2313
2410
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
2314
2411
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -2324,12 +2421,12 @@ function bumpVersion(current, bump) {
|
|
|
2324
2421
|
return `${major}.${minor}.${patch}`;
|
|
2325
2422
|
}
|
|
2326
2423
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
2327
|
-
const abs =
|
|
2328
|
-
if (!
|
|
2329
|
-
const content =
|
|
2424
|
+
const abs = path11.join(cwd, file);
|
|
2425
|
+
if (!fs12.existsSync(abs)) return false;
|
|
2426
|
+
const content = fs12.readFileSync(abs, "utf-8");
|
|
2330
2427
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
2331
2428
|
if (updated === content) return false;
|
|
2332
|
-
|
|
2429
|
+
fs12.writeFileSync(abs, updated);
|
|
2333
2430
|
return true;
|
|
2334
2431
|
}
|
|
2335
2432
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
@@ -2377,19 +2474,19 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
2377
2474
|
return parts.join("\n");
|
|
2378
2475
|
}
|
|
2379
2476
|
function prependChangelog(cwd, entry) {
|
|
2380
|
-
const p =
|
|
2477
|
+
const p = path11.join(cwd, "CHANGELOG.md");
|
|
2381
2478
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
2382
|
-
if (
|
|
2383
|
-
const prior =
|
|
2479
|
+
if (fs12.existsSync(p)) {
|
|
2480
|
+
const prior = fs12.readFileSync(p, "utf-8");
|
|
2384
2481
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
2385
2482
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
2386
|
-
|
|
2483
|
+
fs12.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
2387
2484
|
${entry}${prior.slice(idx + 1)}`);
|
|
2388
2485
|
} else {
|
|
2389
|
-
|
|
2486
|
+
fs12.writeFileSync(p, `${header}${entry}${prior}`);
|
|
2390
2487
|
}
|
|
2391
2488
|
} else {
|
|
2392
|
-
|
|
2489
|
+
fs12.writeFileSync(p, `${header}${entry}`);
|
|
2393
2490
|
}
|
|
2394
2491
|
}
|
|
2395
2492
|
function git3(args, cwd, timeout = 6e4) {
|
|
@@ -2440,13 +2537,13 @@ var releaseFlow = async (ctx) => {
|
|
|
2440
2537
|
};
|
|
2441
2538
|
async function runPrepare(args) {
|
|
2442
2539
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
2443
|
-
const pkgPath =
|
|
2444
|
-
if (!
|
|
2540
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
2541
|
+
if (!fs12.existsSync(pkgPath)) {
|
|
2445
2542
|
ctx.output.exitCode = 99;
|
|
2446
2543
|
ctx.output.reason = "release prepare: package.json not found";
|
|
2447
2544
|
return;
|
|
2448
2545
|
}
|
|
2449
|
-
const pkg = JSON.parse(
|
|
2546
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
2450
2547
|
if (typeof pkg.version !== "string") {
|
|
2451
2548
|
ctx.output.exitCode = 99;
|
|
2452
2549
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -2517,8 +2614,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
2517
2614
|
}
|
|
2518
2615
|
async function runFinalize(args) {
|
|
2519
2616
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
2520
|
-
const pkgPath =
|
|
2521
|
-
const pkg = JSON.parse(
|
|
2617
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
2618
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
2522
2619
|
if (typeof pkg.version !== "string") {
|
|
2523
2620
|
ctx.output.exitCode = 99;
|
|
2524
2621
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -2789,7 +2886,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
2789
2886
|
var TAIL_CHARS = 4e3;
|
|
2790
2887
|
var COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2791
2888
|
function runCommand(command, cwd) {
|
|
2792
|
-
return new Promise((
|
|
2889
|
+
return new Promise((resolve3) => {
|
|
2793
2890
|
const start = Date.now();
|
|
2794
2891
|
const child = spawn2(command, {
|
|
2795
2892
|
cwd,
|
|
@@ -2818,11 +2915,11 @@ function runCommand(command, cwd) {
|
|
|
2818
2915
|
child.on("exit", (code) => {
|
|
2819
2916
|
clearTimeout(timer);
|
|
2820
2917
|
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
2821
|
-
|
|
2918
|
+
resolve3({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
2822
2919
|
});
|
|
2823
2920
|
child.on("error", (err) => {
|
|
2824
2921
|
clearTimeout(timer);
|
|
2825
|
-
|
|
2922
|
+
resolve3({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
2826
2923
|
});
|
|
2827
2924
|
});
|
|
2828
2925
|
}
|
|
@@ -2948,11 +3045,11 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
2948
3045
|
};
|
|
2949
3046
|
|
|
2950
3047
|
// src/scripts/writeRunSummary.ts
|
|
2951
|
-
import * as
|
|
2952
|
-
var writeRunSummary = async (ctx) => {
|
|
3048
|
+
import * as fs13 from "fs";
|
|
3049
|
+
var writeRunSummary = async (ctx, profile) => {
|
|
2953
3050
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
2954
3051
|
if (!summaryPath) return;
|
|
2955
|
-
const
|
|
3052
|
+
const executable = profile.name;
|
|
2956
3053
|
const issue = ctx.args.issue;
|
|
2957
3054
|
const pr = ctx.args.pr;
|
|
2958
3055
|
const target = issue ? `issue #${issue}` : pr ? `PR #${pr}` : "(unknown)";
|
|
@@ -2961,16 +3058,16 @@ var writeRunSummary = async (ctx) => {
|
|
|
2961
3058
|
const reason = ctx.output.reason;
|
|
2962
3059
|
const status = exitCode === 0 ? "\u2705 success" : exitCode === 3 ? "\u23ED\uFE0F no-op" : "\u26A0\uFE0F failed";
|
|
2963
3060
|
const lines = [];
|
|
2964
|
-
lines.push(`## kody2
|
|
3061
|
+
lines.push(`## kody2 ${executable} \u2014 ${status}`);
|
|
2965
3062
|
lines.push("");
|
|
2966
|
-
lines.push(`- **
|
|
3063
|
+
lines.push(`- **Executable:** \`${executable}\``);
|
|
2967
3064
|
lines.push(`- **Target:** ${target}`);
|
|
2968
3065
|
if (prUrl) lines.push(`- **PR:** ${prUrl}`);
|
|
2969
3066
|
lines.push(`- **Exit code:** ${exitCode}`);
|
|
2970
3067
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
2971
3068
|
lines.push("");
|
|
2972
3069
|
try {
|
|
2973
|
-
|
|
3070
|
+
fs13.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
2974
3071
|
`);
|
|
2975
3072
|
} catch {
|
|
2976
3073
|
}
|
|
@@ -2990,6 +3087,7 @@ var preflightScripts = {
|
|
|
2990
3087
|
loadIssueContext,
|
|
2991
3088
|
loadConventions,
|
|
2992
3089
|
loadCoverageRules,
|
|
3090
|
+
buildSyntheticPlugin,
|
|
2993
3091
|
composePrompt
|
|
2994
3092
|
};
|
|
2995
3093
|
var postflightScripts = {
|
|
@@ -3068,7 +3166,24 @@ async function runExecutable(profileName, input) {
|
|
|
3068
3166
|
if (firstFail) {
|
|
3069
3167
|
return finish({ exitCode: 99, reason: `required CLI tool check failed: ${firstFail.error}` });
|
|
3070
3168
|
}
|
|
3071
|
-
|
|
3169
|
+
let config;
|
|
3170
|
+
if (input.config) {
|
|
3171
|
+
config = input.config;
|
|
3172
|
+
} else if (input.skipConfig) {
|
|
3173
|
+
config = {
|
|
3174
|
+
quality: { typecheck: "", lint: "", testUnit: "" },
|
|
3175
|
+
git: { defaultBranch: "main" },
|
|
3176
|
+
github: { owner: "", repo: "" },
|
|
3177
|
+
agent: { model: "claude/claude-haiku-4-5-20251001" }
|
|
3178
|
+
};
|
|
3179
|
+
} else {
|
|
3180
|
+
try {
|
|
3181
|
+
config = loadConfig(input.cwd);
|
|
3182
|
+
} catch (err) {
|
|
3183
|
+
return finish({ exitCode: 99, reason: `config error: ${err instanceof Error ? err.message : String(err)}` });
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
const modelSpec = profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
|
|
3072
3187
|
let model;
|
|
3073
3188
|
try {
|
|
3074
3189
|
model = parseProviderModel(modelSpec);
|
|
@@ -3087,25 +3202,33 @@ async function runExecutable(profileName, input) {
|
|
|
3087
3202
|
const ctx = {
|
|
3088
3203
|
args,
|
|
3089
3204
|
cwd: input.cwd,
|
|
3090
|
-
config
|
|
3205
|
+
config,
|
|
3091
3206
|
verbose: input.verbose,
|
|
3092
3207
|
quiet: input.quiet,
|
|
3093
3208
|
data: {},
|
|
3094
3209
|
output: { exitCode: 0 }
|
|
3095
3210
|
};
|
|
3096
|
-
const ndjsonDir =
|
|
3097
|
-
const invokeAgent = async (prompt) =>
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
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
|
+
};
|
|
3109
3232
|
ctx.data.__invokeAgent = invokeAgent;
|
|
3110
3233
|
try {
|
|
3111
3234
|
for (const entry of profile.scripts.preflight) {
|
|
@@ -3152,22 +3275,36 @@ async function runExecutable(profileName, input) {
|
|
|
3152
3275
|
}
|
|
3153
3276
|
}
|
|
3154
3277
|
function resolveProfilePath(profileName) {
|
|
3155
|
-
const here =
|
|
3278
|
+
const here = path12.dirname(new URL(import.meta.url).pathname);
|
|
3156
3279
|
const candidates = [
|
|
3157
|
-
|
|
3280
|
+
path12.join(here, "executables", profileName, "profile.json"),
|
|
3158
3281
|
// same-dir sibling (dev)
|
|
3159
|
-
|
|
3282
|
+
path12.join(here, "..", "executables", profileName, "profile.json"),
|
|
3160
3283
|
// up one (prod: dist/bin → dist/executables)
|
|
3161
|
-
|
|
3284
|
+
path12.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
3162
3285
|
// fallback
|
|
3163
3286
|
];
|
|
3164
3287
|
for (const c of candidates) {
|
|
3165
|
-
if (
|
|
3288
|
+
if (fs14.existsSync(c)) return c;
|
|
3166
3289
|
}
|
|
3167
3290
|
return candidates[0];
|
|
3168
3291
|
}
|
|
3169
3292
|
function validateInputs(specs, raw) {
|
|
3170
3293
|
const out = {};
|
|
3294
|
+
const allowedKeys = /* @__PURE__ */ new Set(["_", "cwd", "verbose", "quiet"]);
|
|
3295
|
+
for (const spec of specs) {
|
|
3296
|
+
const flagKey = spec.flag.replace(/^--/, "");
|
|
3297
|
+
allowedKeys.add(spec.name);
|
|
3298
|
+
allowedKeys.add(flagKey);
|
|
3299
|
+
if (flagKey.includes("-")) {
|
|
3300
|
+
allowedKeys.add(flagKey.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase()));
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
for (const key of Object.keys(raw)) {
|
|
3304
|
+
if (!allowedKeys.has(key)) {
|
|
3305
|
+
throw new Error(`unknown arg: --${key}`);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3171
3308
|
for (const spec of specs) {
|
|
3172
3309
|
const v = raw[spec.name];
|
|
3173
3310
|
if (v === void 0 || v === null) continue;
|
|
@@ -3241,33 +3378,33 @@ function finish(out) {
|
|
|
3241
3378
|
|
|
3242
3379
|
// src/kody2-cli.ts
|
|
3243
3380
|
import { execFileSync as execFileSync14 } from "child_process";
|
|
3244
|
-
import * as
|
|
3245
|
-
import * as
|
|
3381
|
+
import * as fs16 from "fs";
|
|
3382
|
+
import * as path13 from "path";
|
|
3246
3383
|
|
|
3247
3384
|
// src/dispatch.ts
|
|
3248
|
-
import * as
|
|
3385
|
+
import * as fs15 from "fs";
|
|
3249
3386
|
function autoDispatch(opts) {
|
|
3250
3387
|
const explicit = opts?.explicit;
|
|
3251
3388
|
if (explicit?.issueNumber && explicit.issueNumber > 0) {
|
|
3252
3389
|
return {
|
|
3253
|
-
executable: "
|
|
3254
|
-
cliArgs: {
|
|
3390
|
+
executable: "run",
|
|
3391
|
+
cliArgs: { issue: explicit.issueNumber },
|
|
3255
3392
|
target: explicit.issueNumber
|
|
3256
3393
|
};
|
|
3257
3394
|
}
|
|
3258
3395
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
3259
3396
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
3260
|
-
if (!eventName || !eventPath || !
|
|
3397
|
+
if (!eventName || !eventPath || !fs15.existsSync(eventPath)) return null;
|
|
3261
3398
|
let event = {};
|
|
3262
3399
|
try {
|
|
3263
|
-
event = JSON.parse(
|
|
3400
|
+
event = JSON.parse(fs15.readFileSync(eventPath, "utf-8"));
|
|
3264
3401
|
} catch {
|
|
3265
3402
|
return null;
|
|
3266
3403
|
}
|
|
3267
3404
|
if (eventName === "workflow_dispatch") {
|
|
3268
3405
|
const n = parseInt(String(event.inputs?.issue_number ?? ""), 10);
|
|
3269
3406
|
if (!Number.isNaN(n) && n > 0) {
|
|
3270
|
-
return { executable: "
|
|
3407
|
+
return { executable: "run", cliArgs: { issue: n }, target: n };
|
|
3271
3408
|
}
|
|
3272
3409
|
return null;
|
|
3273
3410
|
}
|
|
@@ -3279,35 +3416,35 @@ function autoDispatch(opts) {
|
|
|
3279
3416
|
const afterTag = extractAfterTag(body);
|
|
3280
3417
|
if (isPr) {
|
|
3281
3418
|
if (/\bfix-ci\b/.test(afterTag)) {
|
|
3282
|
-
return { executable: "
|
|
3419
|
+
return { executable: "fix-ci", cliArgs: { pr: targetNum }, target: targetNum };
|
|
3283
3420
|
}
|
|
3284
3421
|
if (/\bresolve\b/.test(afterTag)) {
|
|
3285
|
-
return { executable: "
|
|
3422
|
+
return { executable: "resolve", cliArgs: { pr: targetNum }, target: targetNum };
|
|
3423
|
+
}
|
|
3424
|
+
if (/\breview\b/.test(afterTag)) {
|
|
3425
|
+
return { executable: "review", cliArgs: { pr: targetNum }, target: targetNum };
|
|
3286
3426
|
}
|
|
3287
3427
|
const feedback = extractFeedback(afterTag);
|
|
3288
3428
|
return {
|
|
3289
|
-
executable: "
|
|
3290
|
-
cliArgs: {
|
|
3429
|
+
executable: "fix",
|
|
3430
|
+
cliArgs: { pr: targetNum, ...feedback ? { feedback } : {} },
|
|
3291
3431
|
target: targetNum
|
|
3292
3432
|
};
|
|
3293
3433
|
}
|
|
3294
3434
|
const sub = extractSubcommand(afterTag);
|
|
3295
|
-
const defaultExec = opts?.config?.defaultExecutable ?? "
|
|
3435
|
+
const defaultExec = opts?.config?.defaultExecutable ?? "run";
|
|
3296
3436
|
if (!sub) {
|
|
3297
3437
|
return asDispatch(defaultExec, targetNum);
|
|
3298
3438
|
}
|
|
3299
|
-
if (sub === "build") {
|
|
3300
|
-
return { executable: "build", cliArgs: { mode: "run", issue: targetNum }, target: targetNum };
|
|
3301
|
-
}
|
|
3302
3439
|
if (sub === "orchestrate" || sub === "orchestrator") {
|
|
3303
3440
|
return { executable: "orchestrator", cliArgs: { issue: targetNum }, target: targetNum };
|
|
3304
3441
|
}
|
|
3442
|
+
if (sub === "build") {
|
|
3443
|
+
return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
|
|
3444
|
+
}
|
|
3305
3445
|
return asDispatch(sub, targetNum);
|
|
3306
3446
|
}
|
|
3307
3447
|
function asDispatch(executable, target) {
|
|
3308
|
-
if (executable === "build") {
|
|
3309
|
-
return { executable, cliArgs: { mode: "run", issue: target }, target };
|
|
3310
|
-
}
|
|
3311
3448
|
return { executable, cliArgs: { issue: target }, target };
|
|
3312
3449
|
}
|
|
3313
3450
|
function extractAfterTag(body) {
|
|
@@ -3410,9 +3547,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
3410
3547
|
return token;
|
|
3411
3548
|
}
|
|
3412
3549
|
function detectPackageManager2(cwd) {
|
|
3413
|
-
if (
|
|
3414
|
-
if (
|
|
3415
|
-
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";
|
|
3416
3553
|
return "npm";
|
|
3417
3554
|
}
|
|
3418
3555
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -3489,11 +3626,11 @@ function configureGitIdentity(cwd) {
|
|
|
3489
3626
|
}
|
|
3490
3627
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
3491
3628
|
if (!issueNumber) return;
|
|
3492
|
-
const logPath =
|
|
3629
|
+
const logPath = path13.join(cwd, ".kody2", "last-run.jsonl");
|
|
3493
3630
|
let tail = "";
|
|
3494
3631
|
try {
|
|
3495
|
-
if (
|
|
3496
|
-
const content =
|
|
3632
|
+
if (fs16.existsSync(logPath)) {
|
|
3633
|
+
const content = fs16.readFileSync(logPath, "utf-8");
|
|
3497
3634
|
tail = content.slice(-3e3);
|
|
3498
3635
|
}
|
|
3499
3636
|
} catch {
|
|
@@ -3518,7 +3655,7 @@ async function runCi(argv) {
|
|
|
3518
3655
|
return 0;
|
|
3519
3656
|
}
|
|
3520
3657
|
const args = parseCiArgs(argv);
|
|
3521
|
-
const cwd = args.cwd ?
|
|
3658
|
+
const cwd = args.cwd ? path13.resolve(args.cwd) : process.cwd();
|
|
3522
3659
|
let earlyConfig;
|
|
3523
3660
|
try {
|
|
3524
3661
|
earlyConfig = loadConfig(cwd);
|
|
@@ -3537,8 +3674,8 @@ ${CI_HELP}`);
|
|
|
3537
3674
|
return 64;
|
|
3538
3675
|
}
|
|
3539
3676
|
const dispatch = autoFallback ?? {
|
|
3540
|
-
executable: "
|
|
3541
|
-
cliArgs: {
|
|
3677
|
+
executable: "run",
|
|
3678
|
+
cliArgs: { issue: args.issueNumber },
|
|
3542
3679
|
target: args.issueNumber
|
|
3543
3680
|
};
|
|
3544
3681
|
const issueNumber = dispatch.target;
|
|
@@ -3610,16 +3747,19 @@ ${CI_HELP}`);
|
|
|
3610
3747
|
var HELP_TEXT = `kody2 \u2014 single-session autonomous engineer
|
|
3611
3748
|
|
|
3612
3749
|
Usage:
|
|
3613
|
-
kody2 run --issue <N> [--cwd <path>] [--verbose|--quiet]
|
|
3614
|
-
kody2 ci --issue <N> [preflight flags \u2014 see: kody2 ci --help]
|
|
3750
|
+
kody2 run --issue <N> [--cwd <path>] [--verbose|--quiet]
|
|
3615
3751
|
kody2 fix --pr <N> [--feedback "..."] [--cwd <path>] [--verbose|--quiet]
|
|
3616
3752
|
kody2 fix-ci --pr <N> [--run-id <ID>] [--cwd <path>] [--verbose|--quiet]
|
|
3617
3753
|
kody2 resolve --pr <N> [--cwd <path>] [--verbose|--quiet]
|
|
3754
|
+
kody2 review --pr <N> [--cwd <path>] [--verbose|--quiet]
|
|
3755
|
+
kody2 <other> [--cwd <path>] [--verbose|--quiet]
|
|
3756
|
+
kody2 ci --issue <N> [preflight flags \u2014 see: kody2 ci --help]
|
|
3618
3757
|
kody2 help
|
|
3619
3758
|
kody2 version
|
|
3620
3759
|
|
|
3621
|
-
|
|
3622
|
-
executable
|
|
3760
|
+
Each top-level command (run, fix, fix-ci, resolve, review, \u2026) is a discovered
|
|
3761
|
+
executable under \`src/executables/<name>/profile.json\`. Drop in a new
|
|
3762
|
+
directory to add a new command.
|
|
3623
3763
|
|
|
3624
3764
|
Exit codes:
|
|
3625
3765
|
0 success (PR opened, verify passed \u2014 or resolve produced a merge commit)
|
|
@@ -3640,11 +3780,6 @@ function parseArgs(argv) {
|
|
|
3640
3780
|
if (cmd === "ci") {
|
|
3641
3781
|
return { ...result, command: "ci", ciArgv: argv.slice(1) };
|
|
3642
3782
|
}
|
|
3643
|
-
if (cmd === "run" || cmd === "fix" || cmd === "fix-ci" || cmd === "resolve") {
|
|
3644
|
-
result.command = cmd;
|
|
3645
|
-
parseCommandArgs(cmd, argv.slice(1), result);
|
|
3646
|
-
return result;
|
|
3647
|
-
}
|
|
3648
3783
|
if (hasExecutable(cmd)) {
|
|
3649
3784
|
result.command = "__executable__";
|
|
3650
3785
|
result.executableName = cmd;
|
|
@@ -3654,38 +3789,11 @@ function parseArgs(argv) {
|
|
|
3654
3789
|
if (result.cliArgs.quiet === true) result.quiet = true;
|
|
3655
3790
|
return result;
|
|
3656
3791
|
}
|
|
3657
|
-
const discovered = listExecutables().map((e) => e.name)
|
|
3658
|
-
const available = ["
|
|
3792
|
+
const discovered = listExecutables().map((e) => e.name);
|
|
3793
|
+
const available = ["ci", "help", "version", ...discovered];
|
|
3659
3794
|
result.errors.push(`unknown command: ${cmd} (available: ${available.join(", ")})`);
|
|
3660
3795
|
return result;
|
|
3661
3796
|
}
|
|
3662
|
-
function parseCommandArgs(cmd, rest, result) {
|
|
3663
|
-
for (let i = 0; i < rest.length; i++) {
|
|
3664
|
-
const arg = rest[i];
|
|
3665
|
-
if (arg === "--issue") {
|
|
3666
|
-
const n = parseInt(rest[++i] ?? "", 10);
|
|
3667
|
-
if (Number.isNaN(n) || n <= 0) result.errors.push("--issue requires a positive integer");
|
|
3668
|
-
else result.issueNumber = n;
|
|
3669
|
-
} else if (arg === "--pr") {
|
|
3670
|
-
const n = parseInt(rest[++i] ?? "", 10);
|
|
3671
|
-
if (Number.isNaN(n) || n <= 0) result.errors.push("--pr requires a positive integer");
|
|
3672
|
-
else result.prNumber = n;
|
|
3673
|
-
} else if (arg === "--feedback") {
|
|
3674
|
-
result.feedback = rest[++i];
|
|
3675
|
-
} else if (arg === "--run-id") {
|
|
3676
|
-
result.runId = rest[++i];
|
|
3677
|
-
} else if (arg === "--cwd") {
|
|
3678
|
-
result.cwd = rest[++i];
|
|
3679
|
-
} else if (arg === "--verbose") result.verbose = true;
|
|
3680
|
-
else if (arg === "--quiet") result.quiet = true;
|
|
3681
|
-
else if (arg === "--dry-run") result.dryRun = true;
|
|
3682
|
-
else result.errors.push(`unknown arg: ${arg}`);
|
|
3683
|
-
}
|
|
3684
|
-
if (cmd === "run" && !result.issueNumber) result.errors.push("--issue <N> is required for run");
|
|
3685
|
-
if (cmd === "fix" && !result.prNumber) result.errors.push("--pr <N> is required for fix");
|
|
3686
|
-
if (cmd === "fix-ci" && !result.prNumber) result.errors.push("--pr <N> is required for fix-ci");
|
|
3687
|
-
if (cmd === "resolve" && !result.prNumber) result.errors.push("--pr <N> is required for resolve");
|
|
3688
|
-
}
|
|
3689
3797
|
async function main(argv = process.argv.slice(2)) {
|
|
3690
3798
|
const args = parseArgs(argv);
|
|
3691
3799
|
if (args.errors.length > 0) {
|
|
@@ -3718,69 +3826,27 @@ ${HELP_TEXT}`);
|
|
|
3718
3826
|
}
|
|
3719
3827
|
const cwd = args.cwd ?? process.cwd();
|
|
3720
3828
|
const configlessCommands = /* @__PURE__ */ new Set(["init"]);
|
|
3721
|
-
const
|
|
3722
|
-
let config;
|
|
3723
|
-
if (needsConfig) {
|
|
3724
|
-
try {
|
|
3725
|
-
config = loadConfig(cwd);
|
|
3726
|
-
} catch (err) {
|
|
3727
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3728
|
-
process.stderr.write(`[kody2] config error: ${msg}
|
|
3729
|
-
`);
|
|
3730
|
-
process.stdout.write(`PR_URL=FAILED: config error: ${msg}
|
|
3731
|
-
`);
|
|
3732
|
-
return 99;
|
|
3733
|
-
}
|
|
3734
|
-
} else {
|
|
3735
|
-
config = {
|
|
3736
|
-
quality: { typecheck: "", lint: "", testUnit: "" },
|
|
3737
|
-
git: { defaultBranch: "main" },
|
|
3738
|
-
github: { owner: "", repo: "" },
|
|
3739
|
-
agent: { model: "claude/claude-haiku-4-5-20251001" }
|
|
3740
|
-
};
|
|
3741
|
-
}
|
|
3742
|
-
if (args.command === "__executable__") {
|
|
3743
|
-
try {
|
|
3744
|
-
const result = await runExecutable(args.executableName, {
|
|
3745
|
-
cliArgs: args.cliArgs ?? {},
|
|
3746
|
-
cwd,
|
|
3747
|
-
config,
|
|
3748
|
-
verbose: args.verbose,
|
|
3749
|
-
quiet: args.quiet
|
|
3750
|
-
});
|
|
3751
|
-
return result.exitCode;
|
|
3752
|
-
} catch (err) {
|
|
3753
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3754
|
-
process.stderr.write(`[kody2] ${args.executableName} crashed: ${msg}
|
|
3755
|
-
`);
|
|
3756
|
-
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
3757
|
-
`);
|
|
3758
|
-
process.stdout.write(`PR_URL=FAILED: ${args.executableName} crashed: ${msg}
|
|
3759
|
-
`);
|
|
3760
|
-
return 99;
|
|
3761
|
-
}
|
|
3762
|
-
}
|
|
3763
|
-
const cliArgs = { mode: args.command };
|
|
3764
|
-
if (args.issueNumber !== void 0) cliArgs.issue = args.issueNumber;
|
|
3765
|
-
if (args.prNumber !== void 0) cliArgs.pr = args.prNumber;
|
|
3766
|
-
if (args.feedback !== void 0) cliArgs.feedback = args.feedback;
|
|
3767
|
-
if (args.runId !== void 0) cliArgs.runId = args.runId;
|
|
3829
|
+
const skipConfig = configlessCommands.has(args.executableName ?? "");
|
|
3768
3830
|
try {
|
|
3769
|
-
const result = await runExecutable(
|
|
3770
|
-
cliArgs,
|
|
3831
|
+
const result = await runExecutable(args.executableName, {
|
|
3832
|
+
cliArgs: args.cliArgs ?? {},
|
|
3771
3833
|
cwd,
|
|
3772
|
-
|
|
3834
|
+
skipConfig,
|
|
3773
3835
|
verbose: args.verbose,
|
|
3774
3836
|
quiet: args.quiet
|
|
3775
3837
|
});
|
|
3838
|
+
if (result.exitCode !== 0 && result.reason) {
|
|
3839
|
+
process.stderr.write(`error: ${result.reason}
|
|
3840
|
+
`);
|
|
3841
|
+
}
|
|
3776
3842
|
return result.exitCode;
|
|
3777
3843
|
} catch (err) {
|
|
3778
3844
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3779
|
-
process.stderr.write(`[kody2]
|
|
3845
|
+
process.stderr.write(`[kody2] ${args.executableName} crashed: ${msg}
|
|
3780
3846
|
`);
|
|
3781
3847
|
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
3782
3848
|
`);
|
|
3783
|
-
process.stdout.write(`PR_URL=FAILED:
|
|
3849
|
+
process.stdout.write(`PR_URL=FAILED: ${args.executableName} crashed: ${msg}
|
|
3784
3850
|
`);
|
|
3785
3851
|
return 99;
|
|
3786
3852
|
}
|