@kody-ade/kody-engine 0.3.1 → 0.3.2
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 +310 -228
- package/dist/executables/fix/profile.json +3 -2
- package/dist/executables/types.ts +7 -0
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.3.
|
|
6
|
+
version: "0.3.2",
|
|
7
7
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -182,10 +182,26 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
182
182
|
issueContext: parseIssueContext(raw.issueContext),
|
|
183
183
|
testRequirements: parseTestRequirements(raw.testRequirements),
|
|
184
184
|
defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable : "classify",
|
|
185
|
+
defaultPrExecutable: typeof raw.defaultPrExecutable === "string" && raw.defaultPrExecutable.length > 0 ? raw.defaultPrExecutable : "fix",
|
|
186
|
+
aliases: mergeAliases(raw.aliases),
|
|
185
187
|
classify: parseClassifyConfig(raw.classify),
|
|
186
188
|
release: parseReleaseConfig(raw.release)
|
|
187
189
|
};
|
|
188
190
|
}
|
|
191
|
+
var BUILTIN_ALIASES = {
|
|
192
|
+
build: "run",
|
|
193
|
+
orchestrate: "bug",
|
|
194
|
+
orchestrator: "bug"
|
|
195
|
+
};
|
|
196
|
+
function mergeAliases(raw) {
|
|
197
|
+
const out = { ...BUILTIN_ALIASES };
|
|
198
|
+
if (raw && typeof raw === "object") {
|
|
199
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
200
|
+
if (typeof v === "string" && v.length > 0) out[k.toLowerCase()] = v;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
189
205
|
function parseClassifyConfig(raw) {
|
|
190
206
|
if (!raw || typeof raw !== "object") return void 0;
|
|
191
207
|
const r = raw;
|
|
@@ -557,22 +573,92 @@ import * as fs21 from "fs";
|
|
|
557
573
|
import * as path18 from "path";
|
|
558
574
|
|
|
559
575
|
// src/dispatch.ts
|
|
576
|
+
import * as fs6 from "fs";
|
|
577
|
+
|
|
578
|
+
// src/registry.ts
|
|
560
579
|
import * as fs5 from "fs";
|
|
580
|
+
import * as path5 from "path";
|
|
581
|
+
function getExecutablesRoot() {
|
|
582
|
+
const here = path5.dirname(new URL(import.meta.url).pathname);
|
|
583
|
+
const candidates = [
|
|
584
|
+
path5.join(here, "executables"),
|
|
585
|
+
// dev: src/
|
|
586
|
+
path5.join(here, "..", "executables"),
|
|
587
|
+
// built: dist/bin → dist/executables
|
|
588
|
+
path5.join(here, "..", "src", "executables")
|
|
589
|
+
// fallback
|
|
590
|
+
];
|
|
591
|
+
for (const c of candidates) {
|
|
592
|
+
if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) return c;
|
|
593
|
+
}
|
|
594
|
+
return candidates[0];
|
|
595
|
+
}
|
|
596
|
+
function listExecutables(root = getExecutablesRoot()) {
|
|
597
|
+
if (!fs5.existsSync(root)) return [];
|
|
598
|
+
const entries = fs5.readdirSync(root, { withFileTypes: true });
|
|
599
|
+
const out = [];
|
|
600
|
+
for (const ent of entries) {
|
|
601
|
+
if (!ent.isDirectory()) continue;
|
|
602
|
+
const profilePath = path5.join(root, ent.name, "profile.json");
|
|
603
|
+
if (fs5.existsSync(profilePath) && fs5.statSync(profilePath).isFile()) {
|
|
604
|
+
out.push({ name: ent.name, profilePath });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
608
|
+
}
|
|
609
|
+
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
610
|
+
if (!isSafeName(name)) return false;
|
|
611
|
+
const profilePath = path5.join(root, name, "profile.json");
|
|
612
|
+
return fs5.existsSync(profilePath) && fs5.statSync(profilePath).isFile();
|
|
613
|
+
}
|
|
614
|
+
function isSafeName(name) {
|
|
615
|
+
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
616
|
+
}
|
|
617
|
+
function getProfileInputs(name, root = getExecutablesRoot()) {
|
|
618
|
+
if (!hasExecutable(name, root)) return null;
|
|
619
|
+
const profilePath = path5.join(root, name, "profile.json");
|
|
620
|
+
try {
|
|
621
|
+
const raw = JSON.parse(fs5.readFileSync(profilePath, "utf-8"));
|
|
622
|
+
if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
|
|
623
|
+
return raw.inputs;
|
|
624
|
+
} catch {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function parseGenericFlags(argv) {
|
|
629
|
+
const args = {};
|
|
630
|
+
const positional = [];
|
|
631
|
+
for (let i = 0; i < argv.length; i++) {
|
|
632
|
+
const arg = argv[i];
|
|
633
|
+
if (!arg.startsWith("--")) {
|
|
634
|
+
positional.push(arg);
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const key = arg.slice(2);
|
|
638
|
+
const next = argv[i + 1];
|
|
639
|
+
const value = next !== void 0 && !next.startsWith("--") ? (i++, next) : true;
|
|
640
|
+
args[key] = value;
|
|
641
|
+
if (key.includes("-")) {
|
|
642
|
+
const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
643
|
+
if (camel !== key && args[camel] === void 0) args[camel] = value;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (positional.length > 0) args._ = positional;
|
|
647
|
+
return args;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/dispatch.ts
|
|
561
651
|
function autoDispatch(opts) {
|
|
562
652
|
const explicit = opts?.explicit;
|
|
563
653
|
if (explicit?.issueNumber && explicit.issueNumber > 0) {
|
|
564
|
-
return {
|
|
565
|
-
executable: "run",
|
|
566
|
-
cliArgs: { issue: explicit.issueNumber },
|
|
567
|
-
target: explicit.issueNumber
|
|
568
|
-
};
|
|
654
|
+
return { executable: "run", cliArgs: { issue: explicit.issueNumber }, target: explicit.issueNumber };
|
|
569
655
|
}
|
|
570
656
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
571
657
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
572
|
-
if (!eventName || !eventPath || !
|
|
658
|
+
if (!eventName || !eventPath || !fs6.existsSync(eventPath)) return null;
|
|
573
659
|
let event = {};
|
|
574
660
|
try {
|
|
575
|
-
event = JSON.parse(
|
|
661
|
+
event = JSON.parse(fs6.readFileSync(eventPath, "utf-8"));
|
|
576
662
|
} catch {
|
|
577
663
|
return null;
|
|
578
664
|
}
|
|
@@ -589,45 +675,34 @@ function autoDispatch(opts) {
|
|
|
589
675
|
const isPr = !!event.issue?.pull_request;
|
|
590
676
|
if (!targetNum) return null;
|
|
591
677
|
const afterTag = extractAfterTag(body);
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
const
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
if (sub === "build") {
|
|
622
|
-
return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
|
|
623
|
-
}
|
|
624
|
-
if (sub === "orchestrate" || sub === "orchestrator") {
|
|
625
|
-
return { executable: "bug", cliArgs: { issue: targetNum }, target: targetNum };
|
|
626
|
-
}
|
|
627
|
-
return asDispatch(sub, targetNum);
|
|
628
|
-
}
|
|
629
|
-
function asDispatch(executable, target) {
|
|
630
|
-
return { executable, cliArgs: { issue: target }, target };
|
|
678
|
+
const firstToken = extractSubcommand(afterTag);
|
|
679
|
+
const aliases = opts?.config?.aliases ?? BUILTIN_ALIASES;
|
|
680
|
+
const aliased = firstToken ? aliases[firstToken] ?? firstToken : null;
|
|
681
|
+
let executable = null;
|
|
682
|
+
let consumedFirstToken = false;
|
|
683
|
+
if (aliased && getProfileInputs(aliased) !== null) {
|
|
684
|
+
executable = aliased;
|
|
685
|
+
consumedFirstToken = true;
|
|
686
|
+
}
|
|
687
|
+
if (!executable) {
|
|
688
|
+
executable = isPr ? opts?.config?.defaultPrExecutable ?? "fix" : opts?.config?.defaultExecutable ?? null;
|
|
689
|
+
}
|
|
690
|
+
if (!executable) return null;
|
|
691
|
+
const inputs = getProfileInputs(executable);
|
|
692
|
+
const effectiveInputs = inputs ?? [];
|
|
693
|
+
const unknownProfile = inputs === null;
|
|
694
|
+
const rest = extractCommentRest(afterTag, consumedFirstToken ? firstToken : null);
|
|
695
|
+
const { args, leftover } = parseCommentArgs(rest, effectiveInputs);
|
|
696
|
+
if (isPr && (unknownProfile || effectiveInputs.some((s) => s.name === "pr"))) {
|
|
697
|
+
args.pr = targetNum;
|
|
698
|
+
} else if (!isPr && (unknownProfile || effectiveInputs.some((s) => s.name === "issue"))) {
|
|
699
|
+
args.issue = targetNum;
|
|
700
|
+
}
|
|
701
|
+
const restInput = effectiveInputs.find((s) => s.bindsCommentRest === true);
|
|
702
|
+
if (restInput && leftover.length > 0 && args[restInput.name] === void 0) {
|
|
703
|
+
args[restInput.name] = leftover;
|
|
704
|
+
}
|
|
705
|
+
return { executable, cliArgs: args, target: targetNum };
|
|
631
706
|
}
|
|
632
707
|
function extractAfterTag(body) {
|
|
633
708
|
const idx = body.indexOf("@kody");
|
|
@@ -636,12 +711,79 @@ function extractAfterTag(body) {
|
|
|
636
711
|
}
|
|
637
712
|
function extractSubcommand(afterTag) {
|
|
638
713
|
const match = afterTag.match(/^([a-z][a-z0-9-]{1,40})\b/);
|
|
639
|
-
|
|
640
|
-
|
|
714
|
+
return match ? match[1] : null;
|
|
715
|
+
}
|
|
716
|
+
function extractCommentRest(afterTag, consumedToken) {
|
|
717
|
+
let rest = afterTag;
|
|
718
|
+
if (consumedToken) {
|
|
719
|
+
const re = new RegExp(`^${consumedToken.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}\\b`, "i");
|
|
720
|
+
rest = rest.replace(re, "");
|
|
721
|
+
}
|
|
722
|
+
rest = rest.replace(/^(please|kindly)(?:[\s:,.-]+|$)/i, "");
|
|
723
|
+
return rest.replace(/^[\s:,.-]+/, "").trim();
|
|
724
|
+
}
|
|
725
|
+
function parseCommentArgs(rest, inputs) {
|
|
726
|
+
const tokens = rest.length === 0 ? [] : rest.split(/\s+/).filter((t) => t.length > 0);
|
|
727
|
+
const args = {};
|
|
728
|
+
const unmatched = [];
|
|
729
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
730
|
+
const t = tokens[i];
|
|
731
|
+
if (t.startsWith("--")) {
|
|
732
|
+
const eq = t.indexOf("=");
|
|
733
|
+
const key = eq >= 0 ? t.slice(2, eq) : t.slice(2);
|
|
734
|
+
const inlineValue = eq >= 0 ? t.slice(eq + 1) : void 0;
|
|
735
|
+
const spec = findInputByFlag(inputs, key);
|
|
736
|
+
if (!spec) {
|
|
737
|
+
unmatched.push(t);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (spec.type === "bool") {
|
|
741
|
+
args[spec.name] = true;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
const value = inlineValue ?? tokens[i + 1];
|
|
745
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
746
|
+
unmatched.push(t);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
args[spec.name] = coerceBare(spec, value);
|
|
750
|
+
if (inlineValue === void 0) i++;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
const enumHit = inputs.find((s) => s.type === "enum" && s.values?.includes(t) && args[s.name] === void 0);
|
|
754
|
+
if (enumHit) {
|
|
755
|
+
args[enumHit.name] = t;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (/^-?\d+$/.test(t)) {
|
|
759
|
+
const intHit = inputs.find((s) => s.type === "int" && args[s.name] === void 0);
|
|
760
|
+
if (intHit) {
|
|
761
|
+
args[intHit.name] = parseInt(t, 10);
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const boolHit = inputs.find((s) => s.type === "bool" && s.flag === `--${t}` && args[s.name] === void 0);
|
|
766
|
+
if (boolHit) {
|
|
767
|
+
args[boolHit.name] = true;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
unmatched.push(t);
|
|
771
|
+
}
|
|
772
|
+
return { args, leftover: unmatched.join(" ") };
|
|
773
|
+
}
|
|
774
|
+
function findInputByFlag(inputs, key) {
|
|
775
|
+
return inputs.find((s) => s.name === key || s.flag === `--${key}`);
|
|
641
776
|
}
|
|
642
|
-
function
|
|
643
|
-
|
|
644
|
-
|
|
777
|
+
function coerceBare(spec, value) {
|
|
778
|
+
if (spec.type === "int") {
|
|
779
|
+
const n = parseInt(value, 10);
|
|
780
|
+
return Number.isNaN(n) ? value : n;
|
|
781
|
+
}
|
|
782
|
+
if (spec.type === "bool") {
|
|
783
|
+
const v = value.toLowerCase();
|
|
784
|
+
return v === "true" || v === "1" || v === "yes";
|
|
785
|
+
}
|
|
786
|
+
return value;
|
|
645
787
|
}
|
|
646
788
|
|
|
647
789
|
// src/executor.ts
|
|
@@ -650,9 +792,9 @@ import * as path17 from "path";
|
|
|
650
792
|
|
|
651
793
|
// src/litellm.ts
|
|
652
794
|
import { execFileSync, spawn } from "child_process";
|
|
653
|
-
import * as
|
|
795
|
+
import * as fs7 from "fs";
|
|
654
796
|
import * as os from "os";
|
|
655
|
-
import * as
|
|
797
|
+
import * as path6 from "path";
|
|
656
798
|
async function checkLitellmHealth(url) {
|
|
657
799
|
try {
|
|
658
800
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -692,20 +834,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
692
834
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
693
835
|
}
|
|
694
836
|
}
|
|
695
|
-
const configPath =
|
|
696
|
-
|
|
837
|
+
const configPath = path6.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
838
|
+
fs7.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
697
839
|
const portMatch = url.match(/:(\d+)/);
|
|
698
840
|
const port = portMatch ? portMatch[1] : "4000";
|
|
699
841
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
700
842
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
701
|
-
const logPath =
|
|
702
|
-
const outFd =
|
|
843
|
+
const logPath = path6.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
844
|
+
const outFd = fs7.openSync(logPath, "w");
|
|
703
845
|
const child = spawn(cmd, args, {
|
|
704
846
|
stdio: ["ignore", outFd, outFd],
|
|
705
847
|
detached: true,
|
|
706
848
|
env: stripBlockingEnv({ ...process.env, ...dotenvVars })
|
|
707
849
|
});
|
|
708
|
-
|
|
850
|
+
fs7.closeSync(outFd);
|
|
709
851
|
for (let i = 0; i < 30; i++) {
|
|
710
852
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
711
853
|
if (await checkLitellmHealth(url)) {
|
|
@@ -722,7 +864,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
722
864
|
}
|
|
723
865
|
let logTail = "";
|
|
724
866
|
try {
|
|
725
|
-
logTail =
|
|
867
|
+
logTail = fs7.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
726
868
|
} catch {
|
|
727
869
|
}
|
|
728
870
|
try {
|
|
@@ -733,10 +875,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
733
875
|
${logTail}`);
|
|
734
876
|
}
|
|
735
877
|
function readDotenvApiKeys(projectDir) {
|
|
736
|
-
const dotenvPath =
|
|
737
|
-
if (!
|
|
878
|
+
const dotenvPath = path6.join(projectDir, ".env");
|
|
879
|
+
if (!fs7.existsSync(dotenvPath)) return {};
|
|
738
880
|
const result = {};
|
|
739
|
-
for (const rawLine of
|
|
881
|
+
for (const rawLine of fs7.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
740
882
|
const line = rawLine.trim();
|
|
741
883
|
if (!line || line.startsWith("#")) continue;
|
|
742
884
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -759,8 +901,8 @@ function stripBlockingEnv(env) {
|
|
|
759
901
|
}
|
|
760
902
|
|
|
761
903
|
// src/profile.ts
|
|
762
|
-
import * as
|
|
763
|
-
import * as
|
|
904
|
+
import * as fs8 from "fs";
|
|
905
|
+
import * as path7 from "path";
|
|
764
906
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
765
907
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
766
908
|
var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "watch", "utility"]);
|
|
@@ -775,12 +917,12 @@ var ProfileError = class extends Error {
|
|
|
775
917
|
profilePath;
|
|
776
918
|
};
|
|
777
919
|
function loadProfile(profilePath) {
|
|
778
|
-
if (!
|
|
920
|
+
if (!fs8.existsSync(profilePath)) {
|
|
779
921
|
throw new ProfileError(profilePath, "file not found");
|
|
780
922
|
}
|
|
781
923
|
let raw;
|
|
782
924
|
try {
|
|
783
|
-
raw = JSON.parse(
|
|
925
|
+
raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
|
|
784
926
|
} catch (err) {
|
|
785
927
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
786
928
|
}
|
|
@@ -820,7 +962,7 @@ function loadProfile(profilePath) {
|
|
|
820
962
|
outputContract: r.outputContract,
|
|
821
963
|
inputArtifacts: parseInputArtifacts(profilePath, r.input),
|
|
822
964
|
outputArtifacts: parseOutputArtifacts(profilePath, r.output),
|
|
823
|
-
dir:
|
|
965
|
+
dir: path7.dirname(profilePath)
|
|
824
966
|
};
|
|
825
967
|
return profile;
|
|
826
968
|
}
|
|
@@ -868,6 +1010,7 @@ function parseInputs(p, raw) {
|
|
|
868
1010
|
if (r.requiredWhen && typeof r.requiredWhen === "object") {
|
|
869
1011
|
spec.requiredWhen = r.requiredWhen;
|
|
870
1012
|
}
|
|
1013
|
+
if (r.bindsCommentRest === true) spec.bindsCommentRest = true;
|
|
871
1014
|
out.push(spec);
|
|
872
1015
|
}
|
|
873
1016
|
return out;
|
|
@@ -1249,21 +1392,21 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
1249
1392
|
};
|
|
1250
1393
|
|
|
1251
1394
|
// src/scripts/buildSyntheticPlugin.ts
|
|
1252
|
-
import * as
|
|
1395
|
+
import * as fs9 from "fs";
|
|
1253
1396
|
import * as os2 from "os";
|
|
1254
|
-
import * as
|
|
1397
|
+
import * as path8 from "path";
|
|
1255
1398
|
function getPluginsCatalogRoot() {
|
|
1256
|
-
const here =
|
|
1399
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
1257
1400
|
const candidates = [
|
|
1258
|
-
|
|
1401
|
+
path8.join(here, "..", "plugins"),
|
|
1259
1402
|
// dev: src/scripts → src/plugins
|
|
1260
|
-
|
|
1403
|
+
path8.join(here, "..", "..", "plugins"),
|
|
1261
1404
|
// built: dist/scripts → dist/plugins
|
|
1262
|
-
|
|
1405
|
+
path8.join(here, "..", "..", "src", "plugins")
|
|
1263
1406
|
// fallback
|
|
1264
1407
|
];
|
|
1265
1408
|
for (const c of candidates) {
|
|
1266
|
-
if (
|
|
1409
|
+
if (fs9.existsSync(c) && fs9.statSync(c).isDirectory()) return c;
|
|
1267
1410
|
}
|
|
1268
1411
|
return candidates[0];
|
|
1269
1412
|
}
|
|
@@ -1273,50 +1416,50 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1273
1416
|
if (!needsSynthetic) return;
|
|
1274
1417
|
const catalog = getPluginsCatalogRoot();
|
|
1275
1418
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1276
|
-
const root =
|
|
1277
|
-
|
|
1419
|
+
const root = path8.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
1420
|
+
fs9.mkdirSync(path8.join(root, ".claude-plugin"), { recursive: true });
|
|
1278
1421
|
if (cc.skills.length > 0) {
|
|
1279
|
-
const dst =
|
|
1280
|
-
|
|
1422
|
+
const dst = path8.join(root, "skills");
|
|
1423
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1281
1424
|
for (const name of cc.skills) {
|
|
1282
|
-
const src =
|
|
1283
|
-
if (!
|
|
1284
|
-
copyDir(src,
|
|
1425
|
+
const src = path8.join(catalog, "skills", name);
|
|
1426
|
+
if (!fs9.existsSync(src)) throw new Error(`buildSyntheticPlugin: skill not found in catalog: ${name}`);
|
|
1427
|
+
copyDir(src, path8.join(dst, name));
|
|
1285
1428
|
}
|
|
1286
1429
|
}
|
|
1287
1430
|
if (cc.commands.length > 0) {
|
|
1288
|
-
const dst =
|
|
1289
|
-
|
|
1431
|
+
const dst = path8.join(root, "commands");
|
|
1432
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1290
1433
|
for (const name of cc.commands) {
|
|
1291
|
-
const src =
|
|
1292
|
-
if (!
|
|
1293
|
-
|
|
1434
|
+
const src = path8.join(catalog, "commands", `${name}.md`);
|
|
1435
|
+
if (!fs9.existsSync(src)) throw new Error(`buildSyntheticPlugin: command not found in catalog: ${name}`);
|
|
1436
|
+
fs9.copyFileSync(src, path8.join(dst, `${name}.md`));
|
|
1294
1437
|
}
|
|
1295
1438
|
}
|
|
1296
1439
|
if (cc.subagents.length > 0) {
|
|
1297
|
-
const dst =
|
|
1298
|
-
|
|
1440
|
+
const dst = path8.join(root, "agents");
|
|
1441
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1299
1442
|
for (const name of cc.subagents) {
|
|
1300
|
-
const src =
|
|
1301
|
-
if (!
|
|
1302
|
-
|
|
1443
|
+
const src = path8.join(catalog, "agents", `${name}.md`);
|
|
1444
|
+
if (!fs9.existsSync(src)) throw new Error(`buildSyntheticPlugin: subagent not found in catalog: ${name}`);
|
|
1445
|
+
fs9.copyFileSync(src, path8.join(dst, `${name}.md`));
|
|
1303
1446
|
}
|
|
1304
1447
|
}
|
|
1305
1448
|
if (cc.hooks.length > 0) {
|
|
1306
|
-
const dst =
|
|
1307
|
-
|
|
1449
|
+
const dst = path8.join(root, "hooks");
|
|
1450
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1308
1451
|
const merged = { hooks: {} };
|
|
1309
1452
|
for (const name of cc.hooks) {
|
|
1310
|
-
const src =
|
|
1311
|
-
if (!
|
|
1312
|
-
const parsed = JSON.parse(
|
|
1453
|
+
const src = path8.join(catalog, "hooks", `${name}.json`);
|
|
1454
|
+
if (!fs9.existsSync(src)) throw new Error(`buildSyntheticPlugin: hook not found in catalog: ${name}`);
|
|
1455
|
+
const parsed = JSON.parse(fs9.readFileSync(src, "utf-8"));
|
|
1313
1456
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
1314
1457
|
if (!Array.isArray(entries)) continue;
|
|
1315
1458
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
1316
1459
|
merged.hooks[event].push(...entries);
|
|
1317
1460
|
}
|
|
1318
1461
|
}
|
|
1319
|
-
|
|
1462
|
+
fs9.writeFileSync(path8.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
1320
1463
|
`);
|
|
1321
1464
|
}
|
|
1322
1465
|
const manifest = {
|
|
@@ -1327,17 +1470,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1327
1470
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
1328
1471
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
1329
1472
|
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
1330
|
-
|
|
1473
|
+
fs9.writeFileSync(path8.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
1331
1474
|
`);
|
|
1332
1475
|
ctx.data.syntheticPluginPath = root;
|
|
1333
1476
|
};
|
|
1334
1477
|
function copyDir(src, dst) {
|
|
1335
|
-
|
|
1336
|
-
for (const ent of
|
|
1337
|
-
const s =
|
|
1338
|
-
const d =
|
|
1478
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1479
|
+
for (const ent of fs9.readdirSync(src, { withFileTypes: true })) {
|
|
1480
|
+
const s = path8.join(src, ent.name);
|
|
1481
|
+
const d = path8.join(dst, ent.name);
|
|
1339
1482
|
if (ent.isDirectory()) copyDir(s, d);
|
|
1340
|
-
else if (ent.isFile())
|
|
1483
|
+
else if (ent.isFile()) fs9.copyFileSync(s, d);
|
|
1341
1484
|
}
|
|
1342
1485
|
}
|
|
1343
1486
|
|
|
@@ -1403,18 +1546,18 @@ function formatMissesForFeedback(misses) {
|
|
|
1403
1546
|
}
|
|
1404
1547
|
|
|
1405
1548
|
// src/prompt.ts
|
|
1406
|
-
import * as
|
|
1407
|
-
import * as
|
|
1549
|
+
import * as fs10 from "fs";
|
|
1550
|
+
import * as path9 from "path";
|
|
1408
1551
|
var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
1409
1552
|
var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
1410
1553
|
function loadProjectConventions(projectDir) {
|
|
1411
1554
|
const out = [];
|
|
1412
1555
|
for (const rel of CONVENTION_FILES) {
|
|
1413
|
-
const abs =
|
|
1414
|
-
if (!
|
|
1556
|
+
const abs = path9.join(projectDir, rel);
|
|
1557
|
+
if (!fs10.existsSync(abs)) continue;
|
|
1415
1558
|
let content;
|
|
1416
1559
|
try {
|
|
1417
|
-
content =
|
|
1560
|
+
content = fs10.readFileSync(abs, "utf-8");
|
|
1418
1561
|
} catch {
|
|
1419
1562
|
continue;
|
|
1420
1563
|
}
|
|
@@ -1578,8 +1721,8 @@ import { execFileSync as execFileSync6 } from "child_process";
|
|
|
1578
1721
|
|
|
1579
1722
|
// src/commit.ts
|
|
1580
1723
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
1581
|
-
import * as
|
|
1582
|
-
import * as
|
|
1724
|
+
import * as fs11 from "fs";
|
|
1725
|
+
import * as path10 from "path";
|
|
1583
1726
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
1584
1727
|
".kody/",
|
|
1585
1728
|
".kody-engine/",
|
|
@@ -1634,18 +1777,18 @@ function tryGit(args, cwd) {
|
|
|
1634
1777
|
}
|
|
1635
1778
|
function abortUnfinishedGitOps(cwd) {
|
|
1636
1779
|
const aborted = [];
|
|
1637
|
-
const gitDir =
|
|
1638
|
-
if (!
|
|
1639
|
-
if (
|
|
1780
|
+
const gitDir = path10.join(cwd ?? process.cwd(), ".git");
|
|
1781
|
+
if (!fs11.existsSync(gitDir)) return aborted;
|
|
1782
|
+
if (fs11.existsSync(path10.join(gitDir, "MERGE_HEAD"))) {
|
|
1640
1783
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
1641
1784
|
}
|
|
1642
|
-
if (
|
|
1785
|
+
if (fs11.existsSync(path10.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
1643
1786
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
1644
1787
|
}
|
|
1645
|
-
if (
|
|
1788
|
+
if (fs11.existsSync(path10.join(gitDir, "REVERT_HEAD"))) {
|
|
1646
1789
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
1647
1790
|
}
|
|
1648
|
-
if (
|
|
1791
|
+
if (fs11.existsSync(path10.join(gitDir, "rebase-merge")) || fs11.existsSync(path10.join(gitDir, "rebase-apply"))) {
|
|
1649
1792
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
1650
1793
|
}
|
|
1651
1794
|
try {
|
|
@@ -1700,7 +1843,7 @@ function normalizeCommitMessage(raw) {
|
|
|
1700
1843
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
1701
1844
|
const allChanged = listChangedFiles(cwd);
|
|
1702
1845
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
1703
|
-
const mergeHeadExists =
|
|
1846
|
+
const mergeHeadExists = fs11.existsSync(path10.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
1704
1847
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
1705
1848
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
1706
1849
|
}
|
|
@@ -1796,20 +1939,20 @@ function defaultCommitMessage(mode, data) {
|
|
|
1796
1939
|
}
|
|
1797
1940
|
|
|
1798
1941
|
// src/scripts/composePrompt.ts
|
|
1799
|
-
import * as
|
|
1800
|
-
import * as
|
|
1942
|
+
import * as fs12 from "fs";
|
|
1943
|
+
import * as path11 from "path";
|
|
1801
1944
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
1802
1945
|
var composePrompt = async (ctx, profile) => {
|
|
1803
1946
|
const explicit = ctx.data.promptTemplate;
|
|
1804
1947
|
const mode = ctx.args.mode;
|
|
1805
1948
|
const candidates = [
|
|
1806
|
-
explicit ?
|
|
1807
|
-
mode ?
|
|
1808
|
-
|
|
1949
|
+
explicit ? path11.join(profile.dir, explicit) : null,
|
|
1950
|
+
mode ? path11.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
1951
|
+
path11.join(profile.dir, "prompt.md")
|
|
1809
1952
|
].filter(Boolean);
|
|
1810
1953
|
let templatePath = "";
|
|
1811
1954
|
for (const c of candidates) {
|
|
1812
|
-
if (
|
|
1955
|
+
if (fs12.existsSync(c)) {
|
|
1813
1956
|
templatePath = c;
|
|
1814
1957
|
break;
|
|
1815
1958
|
}
|
|
@@ -1817,7 +1960,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
1817
1960
|
if (!templatePath) {
|
|
1818
1961
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
1819
1962
|
}
|
|
1820
|
-
const template =
|
|
1963
|
+
const template = fs12.readFileSync(templatePath, "utf-8");
|
|
1821
1964
|
const tokens = {
|
|
1822
1965
|
...stringifyAll(ctx.args, "args."),
|
|
1823
1966
|
...stringifyAll(ctx.data, ""),
|
|
@@ -1894,17 +2037,17 @@ function formatToolsUsage(profile) {
|
|
|
1894
2037
|
}
|
|
1895
2038
|
|
|
1896
2039
|
// src/scripts/discoverQaContext.ts
|
|
1897
|
-
import * as
|
|
1898
|
-
import * as
|
|
2040
|
+
import * as fs14 from "fs";
|
|
2041
|
+
import * as path13 from "path";
|
|
1899
2042
|
|
|
1900
2043
|
// src/scripts/frameworkDetectors.ts
|
|
1901
|
-
import * as
|
|
1902
|
-
import * as
|
|
2044
|
+
import * as fs13 from "fs";
|
|
2045
|
+
import * as path12 from "path";
|
|
1903
2046
|
function detectFrameworks(cwd) {
|
|
1904
2047
|
const out = [];
|
|
1905
2048
|
let deps = {};
|
|
1906
2049
|
try {
|
|
1907
|
-
const pkg = JSON.parse(
|
|
2050
|
+
const pkg = JSON.parse(fs13.readFileSync(path12.join(cwd, "package.json"), "utf-8"));
|
|
1908
2051
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1909
2052
|
} catch {
|
|
1910
2053
|
return out;
|
|
@@ -1941,7 +2084,7 @@ function detectFrameworks(cwd) {
|
|
|
1941
2084
|
}
|
|
1942
2085
|
function findFile(cwd, candidates) {
|
|
1943
2086
|
for (const c of candidates) {
|
|
1944
|
-
if (
|
|
2087
|
+
if (fs13.existsSync(path12.join(cwd, c))) return c;
|
|
1945
2088
|
}
|
|
1946
2089
|
return null;
|
|
1947
2090
|
}
|
|
@@ -1954,18 +2097,18 @@ var COLLECTION_DIRS = [
|
|
|
1954
2097
|
function discoverPayloadCollections(cwd) {
|
|
1955
2098
|
const out = [];
|
|
1956
2099
|
for (const dir of COLLECTION_DIRS) {
|
|
1957
|
-
const full =
|
|
1958
|
-
if (!
|
|
2100
|
+
const full = path12.join(cwd, dir);
|
|
2101
|
+
if (!fs13.existsSync(full)) continue;
|
|
1959
2102
|
let files;
|
|
1960
2103
|
try {
|
|
1961
|
-
files =
|
|
2104
|
+
files = fs13.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
1962
2105
|
} catch {
|
|
1963
2106
|
continue;
|
|
1964
2107
|
}
|
|
1965
2108
|
for (const file of files) {
|
|
1966
2109
|
try {
|
|
1967
|
-
const filePath =
|
|
1968
|
-
const content =
|
|
2110
|
+
const filePath = path12.join(full, file);
|
|
2111
|
+
const content = fs13.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
1969
2112
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
1970
2113
|
if (!slugMatch) continue;
|
|
1971
2114
|
const slug = slugMatch[1];
|
|
@@ -1979,7 +2122,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
1979
2122
|
out.push({
|
|
1980
2123
|
name,
|
|
1981
2124
|
slug,
|
|
1982
|
-
filePath:
|
|
2125
|
+
filePath: path12.relative(cwd, filePath),
|
|
1983
2126
|
fields: fields.slice(0, 20),
|
|
1984
2127
|
hasAdmin
|
|
1985
2128
|
});
|
|
@@ -1993,28 +2136,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
1993
2136
|
function discoverAdminComponents(cwd, collections) {
|
|
1994
2137
|
const out = [];
|
|
1995
2138
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
1996
|
-
const full =
|
|
1997
|
-
if (!
|
|
2139
|
+
const full = path12.join(cwd, dir);
|
|
2140
|
+
if (!fs13.existsSync(full)) continue;
|
|
1998
2141
|
let entries;
|
|
1999
2142
|
try {
|
|
2000
|
-
entries =
|
|
2143
|
+
entries = fs13.readdirSync(full, { withFileTypes: true });
|
|
2001
2144
|
} catch {
|
|
2002
2145
|
continue;
|
|
2003
2146
|
}
|
|
2004
2147
|
for (const entry of entries) {
|
|
2005
|
-
const entryPath =
|
|
2148
|
+
const entryPath = path12.join(full, entry.name);
|
|
2006
2149
|
let name;
|
|
2007
2150
|
let filePath;
|
|
2008
2151
|
if (entry.isDirectory()) {
|
|
2009
2152
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
2010
|
-
(f) =>
|
|
2153
|
+
(f) => fs13.existsSync(path12.join(entryPath, f))
|
|
2011
2154
|
);
|
|
2012
2155
|
if (!indexFile) continue;
|
|
2013
2156
|
name = entry.name;
|
|
2014
|
-
filePath =
|
|
2157
|
+
filePath = path12.relative(cwd, path12.join(entryPath, indexFile));
|
|
2015
2158
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
2016
2159
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
2017
|
-
filePath =
|
|
2160
|
+
filePath = path12.relative(cwd, entryPath);
|
|
2018
2161
|
} else {
|
|
2019
2162
|
continue;
|
|
2020
2163
|
}
|
|
@@ -2022,7 +2165,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
2022
2165
|
if (collections) {
|
|
2023
2166
|
for (const col of collections) {
|
|
2024
2167
|
try {
|
|
2025
|
-
const colContent =
|
|
2168
|
+
const colContent = fs13.readFileSync(path12.join(cwd, col.filePath), "utf-8");
|
|
2026
2169
|
if (colContent.includes(name)) {
|
|
2027
2170
|
usedInCollection = col.slug;
|
|
2028
2171
|
break;
|
|
@@ -2041,8 +2184,8 @@ function scanApiRoutes(cwd) {
|
|
|
2041
2184
|
const out = [];
|
|
2042
2185
|
const appDirs = ["src/app", "app"];
|
|
2043
2186
|
for (const appDir of appDirs) {
|
|
2044
|
-
const apiDir =
|
|
2045
|
-
if (!
|
|
2187
|
+
const apiDir = path12.join(cwd, appDir, "api");
|
|
2188
|
+
if (!fs13.existsSync(apiDir)) continue;
|
|
2046
2189
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
2047
2190
|
break;
|
|
2048
2191
|
}
|
|
@@ -2051,20 +2194,20 @@ function scanApiRoutes(cwd) {
|
|
|
2051
2194
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
2052
2195
|
let entries;
|
|
2053
2196
|
try {
|
|
2054
|
-
entries =
|
|
2197
|
+
entries = fs13.readdirSync(dir, { withFileTypes: true });
|
|
2055
2198
|
} catch {
|
|
2056
2199
|
return;
|
|
2057
2200
|
}
|
|
2058
2201
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
2059
2202
|
if (routeFile) {
|
|
2060
2203
|
try {
|
|
2061
|
-
const content =
|
|
2204
|
+
const content = fs13.readFileSync(path12.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
2062
2205
|
const methods = HTTP_METHODS.filter((m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content));
|
|
2063
2206
|
if (methods.length > 0) {
|
|
2064
2207
|
out.push({
|
|
2065
2208
|
path: prefix,
|
|
2066
2209
|
methods,
|
|
2067
|
-
filePath:
|
|
2210
|
+
filePath: path12.relative(cwd, path12.join(dir, routeFile.name))
|
|
2068
2211
|
});
|
|
2069
2212
|
}
|
|
2070
2213
|
} catch {
|
|
@@ -2075,7 +2218,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
2075
2218
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
2076
2219
|
let segment = entry.name;
|
|
2077
2220
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
2078
|
-
walkApiRoutes(
|
|
2221
|
+
walkApiRoutes(path12.join(dir, entry.name), prefix, cwd, out);
|
|
2079
2222
|
continue;
|
|
2080
2223
|
}
|
|
2081
2224
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -2083,7 +2226,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
2083
2226
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
2084
2227
|
segment = `:${segment.slice(1, -1)}`;
|
|
2085
2228
|
}
|
|
2086
|
-
walkApiRoutes(
|
|
2229
|
+
walkApiRoutes(path12.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
2087
2230
|
}
|
|
2088
2231
|
}
|
|
2089
2232
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -2103,10 +2246,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
2103
2246
|
function scanEnvVars(cwd) {
|
|
2104
2247
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
2105
2248
|
for (const envFile of candidates) {
|
|
2106
|
-
const envPath =
|
|
2107
|
-
if (!
|
|
2249
|
+
const envPath = path12.join(cwd, envFile);
|
|
2250
|
+
if (!fs13.existsSync(envPath)) continue;
|
|
2108
2251
|
try {
|
|
2109
|
-
const content =
|
|
2252
|
+
const content = fs13.readFileSync(envPath, "utf-8");
|
|
2110
2253
|
const vars = [];
|
|
2111
2254
|
for (const line of content.split("\n")) {
|
|
2112
2255
|
const trimmed = line.trim();
|
|
@@ -2154,9 +2297,9 @@ function runQaDiscovery(cwd) {
|
|
|
2154
2297
|
}
|
|
2155
2298
|
function detectDevServer(cwd, out) {
|
|
2156
2299
|
try {
|
|
2157
|
-
const pkg = JSON.parse(
|
|
2300
|
+
const pkg = JSON.parse(fs14.readFileSync(path13.join(cwd, "package.json"), "utf-8"));
|
|
2158
2301
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2159
|
-
const pm =
|
|
2302
|
+
const pm = fs14.existsSync(path13.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs14.existsSync(path13.join(cwd, "yarn.lock")) ? "yarn" : fs14.existsSync(path13.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
2160
2303
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
2161
2304
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
2162
2305
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -2166,8 +2309,8 @@ function detectDevServer(cwd, out) {
|
|
|
2166
2309
|
function scanFrontendRoutes(cwd, out) {
|
|
2167
2310
|
const appDirs = ["src/app", "app"];
|
|
2168
2311
|
for (const appDir of appDirs) {
|
|
2169
|
-
const full =
|
|
2170
|
-
if (!
|
|
2312
|
+
const full = path13.join(cwd, appDir);
|
|
2313
|
+
if (!fs14.existsSync(full)) continue;
|
|
2171
2314
|
walkFrontendRoutes(full, "", out);
|
|
2172
2315
|
break;
|
|
2173
2316
|
}
|
|
@@ -2175,7 +2318,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
2175
2318
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
2176
2319
|
let entries;
|
|
2177
2320
|
try {
|
|
2178
|
-
entries =
|
|
2321
|
+
entries = fs14.readdirSync(dir, { withFileTypes: true });
|
|
2179
2322
|
} catch {
|
|
2180
2323
|
return;
|
|
2181
2324
|
}
|
|
@@ -2192,7 +2335,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
2192
2335
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
2193
2336
|
let segment = entry.name;
|
|
2194
2337
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
2195
|
-
walkFrontendRoutes(
|
|
2338
|
+
walkFrontendRoutes(path13.join(dir, entry.name), prefix, out);
|
|
2196
2339
|
continue;
|
|
2197
2340
|
}
|
|
2198
2341
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -2200,7 +2343,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
2200
2343
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
2201
2344
|
segment = `:${segment.slice(1, -1)}`;
|
|
2202
2345
|
}
|
|
2203
|
-
walkFrontendRoutes(
|
|
2346
|
+
walkFrontendRoutes(path13.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
2204
2347
|
}
|
|
2205
2348
|
}
|
|
2206
2349
|
function detectAuthFiles(cwd, out) {
|
|
@@ -2217,23 +2360,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
2217
2360
|
"src/app/api/oauth"
|
|
2218
2361
|
];
|
|
2219
2362
|
for (const c of candidates) {
|
|
2220
|
-
if (
|
|
2363
|
+
if (fs14.existsSync(path13.join(cwd, c))) out.authFiles.push(c);
|
|
2221
2364
|
}
|
|
2222
2365
|
}
|
|
2223
2366
|
function detectRoles(cwd, out) {
|
|
2224
2367
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
2225
2368
|
for (const rp of rolePaths) {
|
|
2226
|
-
const dir =
|
|
2227
|
-
if (!
|
|
2369
|
+
const dir = path13.join(cwd, rp);
|
|
2370
|
+
if (!fs14.existsSync(dir)) continue;
|
|
2228
2371
|
let files;
|
|
2229
2372
|
try {
|
|
2230
|
-
files =
|
|
2373
|
+
files = fs14.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
2231
2374
|
} catch {
|
|
2232
2375
|
continue;
|
|
2233
2376
|
}
|
|
2234
2377
|
for (const f of files) {
|
|
2235
2378
|
try {
|
|
2236
|
-
const content =
|
|
2379
|
+
const content = fs14.readFileSync(path13.join(dir, f), "utf-8").slice(0, 5e3);
|
|
2237
2380
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
2238
2381
|
if (roleMatches) {
|
|
2239
2382
|
for (const m of roleMatches) {
|
|
@@ -2720,67 +2863,6 @@ function computeFailureReason(ctx) {
|
|
|
2720
2863
|
// src/scripts/finishFlow.ts
|
|
2721
2864
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
2722
2865
|
|
|
2723
|
-
// src/registry.ts
|
|
2724
|
-
import * as fs14 from "fs";
|
|
2725
|
-
import * as path13 from "path";
|
|
2726
|
-
function getExecutablesRoot() {
|
|
2727
|
-
const here = path13.dirname(new URL(import.meta.url).pathname);
|
|
2728
|
-
const candidates = [
|
|
2729
|
-
path13.join(here, "executables"),
|
|
2730
|
-
// dev: src/
|
|
2731
|
-
path13.join(here, "..", "executables"),
|
|
2732
|
-
// built: dist/bin → dist/executables
|
|
2733
|
-
path13.join(here, "..", "src", "executables")
|
|
2734
|
-
// fallback
|
|
2735
|
-
];
|
|
2736
|
-
for (const c of candidates) {
|
|
2737
|
-
if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
|
|
2738
|
-
}
|
|
2739
|
-
return candidates[0];
|
|
2740
|
-
}
|
|
2741
|
-
function listExecutables(root = getExecutablesRoot()) {
|
|
2742
|
-
if (!fs14.existsSync(root)) return [];
|
|
2743
|
-
const entries = fs14.readdirSync(root, { withFileTypes: true });
|
|
2744
|
-
const out = [];
|
|
2745
|
-
for (const ent of entries) {
|
|
2746
|
-
if (!ent.isDirectory()) continue;
|
|
2747
|
-
const profilePath = path13.join(root, ent.name, "profile.json");
|
|
2748
|
-
if (fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile()) {
|
|
2749
|
-
out.push({ name: ent.name, profilePath });
|
|
2750
|
-
}
|
|
2751
|
-
}
|
|
2752
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
2753
|
-
}
|
|
2754
|
-
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
2755
|
-
if (!isSafeName(name)) return false;
|
|
2756
|
-
const profilePath = path13.join(root, name, "profile.json");
|
|
2757
|
-
return fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile();
|
|
2758
|
-
}
|
|
2759
|
-
function isSafeName(name) {
|
|
2760
|
-
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
2761
|
-
}
|
|
2762
|
-
function parseGenericFlags(argv) {
|
|
2763
|
-
const args = {};
|
|
2764
|
-
const positional = [];
|
|
2765
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2766
|
-
const arg = argv[i];
|
|
2767
|
-
if (!arg.startsWith("--")) {
|
|
2768
|
-
positional.push(arg);
|
|
2769
|
-
continue;
|
|
2770
|
-
}
|
|
2771
|
-
const key = arg.slice(2);
|
|
2772
|
-
const next = argv[i + 1];
|
|
2773
|
-
const value = next !== void 0 && !next.startsWith("--") ? (i++, next) : true;
|
|
2774
|
-
args[key] = value;
|
|
2775
|
-
if (key.includes("-")) {
|
|
2776
|
-
const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
2777
|
-
if (camel !== key && args[camel] === void 0) args[camel] = value;
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
|
-
if (positional.length > 0) args._ = positional;
|
|
2781
|
-
return args;
|
|
2782
|
-
}
|
|
2783
|
-
|
|
2784
2866
|
// src/lifecycleLabels.ts
|
|
2785
2867
|
var KODY_NAMESPACE = "kody";
|
|
2786
2868
|
function groupOf(label) {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"flag": "--feedback",
|
|
16
16
|
"type": "string",
|
|
17
17
|
"required": false,
|
|
18
|
+
"bindsCommentRest": true,
|
|
18
19
|
"describe": "Inline override. If absent, the flow reads the latest PR review comment."
|
|
19
20
|
}
|
|
20
21
|
],
|
|
@@ -49,10 +50,10 @@
|
|
|
49
50
|
"name": "playwright",
|
|
50
51
|
"install": {
|
|
51
52
|
"required": false,
|
|
52
|
-
"checkCommand": "
|
|
53
|
+
"checkCommand": "ls \"$HOME/.cache/ms-playwright\" 2>/dev/null | grep -q '^chromium'",
|
|
53
54
|
"installCommand": "npx --yes playwright install --with-deps chromium"
|
|
54
55
|
},
|
|
55
|
-
"verify": "
|
|
56
|
+
"verify": "ls \"$HOME/.cache/ms-playwright\" 2>/dev/null | grep -q '^chromium'",
|
|
56
57
|
"usage": ""
|
|
57
58
|
}
|
|
58
59
|
],
|
|
@@ -97,6 +97,13 @@ export interface InputSpec {
|
|
|
97
97
|
* e.g. `{ mode: "run" }` or `{ mode: ["fix", "fix-ci", "resolve"] }`.
|
|
98
98
|
*/
|
|
99
99
|
requiredWhen?: Record<string, string | string[]>
|
|
100
|
+
/**
|
|
101
|
+
* When true, this input collects any free-text left over from comment
|
|
102
|
+
* dispatch after flag/enum/bool parsing. Only one input per profile may
|
|
103
|
+
* set this. Used by e.g. `fix.feedback` so `@kody please change X` lands
|
|
104
|
+
* "please change X" in `feedback` without hardcoding that in the router.
|
|
105
|
+
*/
|
|
106
|
+
bindsCommentRest?: boolean
|
|
100
107
|
describe: string
|
|
101
108
|
}
|
|
102
109
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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",
|