@kody-ade/kody-engine 0.3.0 → 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 +421 -233
- package/dist/executables/fix/profile.json +20 -2
- package/dist/executables/plan/profile.json +22 -2
- package/dist/executables/plan/prompt.md +117 -9
- package/dist/executables/research/profile.json +24 -2
- package/dist/executables/research/prompt.md +4 -0
- package/dist/executables/review/profile.json +19 -2
- package/dist/executables/types.ts +7 -0
- package/dist/executables/ui-review/profile.json +7 -1
- 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;
|
|
@@ -366,7 +382,14 @@ async function runAgent(opts) {
|
|
|
366
382
|
env
|
|
367
383
|
};
|
|
368
384
|
if (opts.mcpServers && opts.mcpServers.length > 0) {
|
|
369
|
-
queryOptions.mcpServers =
|
|
385
|
+
queryOptions.mcpServers = Object.fromEntries(
|
|
386
|
+
opts.mcpServers.map((s) => {
|
|
387
|
+
const cfg = { command: s.command };
|
|
388
|
+
if (s.args) cfg.args = s.args;
|
|
389
|
+
if (s.env) cfg.env = s.env;
|
|
390
|
+
return [s.name, cfg];
|
|
391
|
+
})
|
|
392
|
+
);
|
|
370
393
|
}
|
|
371
394
|
if (opts.pluginPaths && opts.pluginPaths.length > 0) {
|
|
372
395
|
queryOptions.plugins = opts.pluginPaths.map((p) => ({ type: "local", path: p }));
|
|
@@ -550,22 +573,92 @@ import * as fs21 from "fs";
|
|
|
550
573
|
import * as path18 from "path";
|
|
551
574
|
|
|
552
575
|
// src/dispatch.ts
|
|
576
|
+
import * as fs6 from "fs";
|
|
577
|
+
|
|
578
|
+
// src/registry.ts
|
|
553
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
|
|
554
651
|
function autoDispatch(opts) {
|
|
555
652
|
const explicit = opts?.explicit;
|
|
556
653
|
if (explicit?.issueNumber && explicit.issueNumber > 0) {
|
|
557
|
-
return {
|
|
558
|
-
executable: "run",
|
|
559
|
-
cliArgs: { issue: explicit.issueNumber },
|
|
560
|
-
target: explicit.issueNumber
|
|
561
|
-
};
|
|
654
|
+
return { executable: "run", cliArgs: { issue: explicit.issueNumber }, target: explicit.issueNumber };
|
|
562
655
|
}
|
|
563
656
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
564
657
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
565
|
-
if (!eventName || !eventPath || !
|
|
658
|
+
if (!eventName || !eventPath || !fs6.existsSync(eventPath)) return null;
|
|
566
659
|
let event = {};
|
|
567
660
|
try {
|
|
568
|
-
event = JSON.parse(
|
|
661
|
+
event = JSON.parse(fs6.readFileSync(eventPath, "utf-8"));
|
|
569
662
|
} catch {
|
|
570
663
|
return null;
|
|
571
664
|
}
|
|
@@ -582,45 +675,34 @@ function autoDispatch(opts) {
|
|
|
582
675
|
const isPr = !!event.issue?.pull_request;
|
|
583
676
|
if (!targetNum) return null;
|
|
584
677
|
const afterTag = extractAfterTag(body);
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
const
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
if (sub === "build") {
|
|
615
|
-
return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
|
|
616
|
-
}
|
|
617
|
-
if (sub === "orchestrate" || sub === "orchestrator") {
|
|
618
|
-
return { executable: "bug", cliArgs: { issue: targetNum }, target: targetNum };
|
|
619
|
-
}
|
|
620
|
-
return asDispatch(sub, targetNum);
|
|
621
|
-
}
|
|
622
|
-
function asDispatch(executable, target) {
|
|
623
|
-
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 };
|
|
624
706
|
}
|
|
625
707
|
function extractAfterTag(body) {
|
|
626
708
|
const idx = body.indexOf("@kody");
|
|
@@ -629,12 +711,79 @@ function extractAfterTag(body) {
|
|
|
629
711
|
}
|
|
630
712
|
function extractSubcommand(afterTag) {
|
|
631
713
|
const match = afterTag.match(/^([a-z][a-z0-9-]{1,40})\b/);
|
|
632
|
-
|
|
633
|
-
return match[1];
|
|
714
|
+
return match ? match[1] : null;
|
|
634
715
|
}
|
|
635
|
-
function
|
|
636
|
-
|
|
637
|
-
|
|
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}`);
|
|
776
|
+
}
|
|
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;
|
|
638
787
|
}
|
|
639
788
|
|
|
640
789
|
// src/executor.ts
|
|
@@ -643,9 +792,9 @@ import * as path17 from "path";
|
|
|
643
792
|
|
|
644
793
|
// src/litellm.ts
|
|
645
794
|
import { execFileSync, spawn } from "child_process";
|
|
646
|
-
import * as
|
|
795
|
+
import * as fs7 from "fs";
|
|
647
796
|
import * as os from "os";
|
|
648
|
-
import * as
|
|
797
|
+
import * as path6 from "path";
|
|
649
798
|
async function checkLitellmHealth(url) {
|
|
650
799
|
try {
|
|
651
800
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -685,20 +834,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
685
834
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
686
835
|
}
|
|
687
836
|
}
|
|
688
|
-
const configPath =
|
|
689
|
-
|
|
837
|
+
const configPath = path6.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
838
|
+
fs7.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
690
839
|
const portMatch = url.match(/:(\d+)/);
|
|
691
840
|
const port = portMatch ? portMatch[1] : "4000";
|
|
692
841
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
693
842
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
694
|
-
const logPath =
|
|
695
|
-
const outFd =
|
|
843
|
+
const logPath = path6.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
844
|
+
const outFd = fs7.openSync(logPath, "w");
|
|
696
845
|
const child = spawn(cmd, args, {
|
|
697
846
|
stdio: ["ignore", outFd, outFd],
|
|
698
847
|
detached: true,
|
|
699
848
|
env: stripBlockingEnv({ ...process.env, ...dotenvVars })
|
|
700
849
|
});
|
|
701
|
-
|
|
850
|
+
fs7.closeSync(outFd);
|
|
702
851
|
for (let i = 0; i < 30; i++) {
|
|
703
852
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
704
853
|
if (await checkLitellmHealth(url)) {
|
|
@@ -715,7 +864,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
715
864
|
}
|
|
716
865
|
let logTail = "";
|
|
717
866
|
try {
|
|
718
|
-
logTail =
|
|
867
|
+
logTail = fs7.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
719
868
|
} catch {
|
|
720
869
|
}
|
|
721
870
|
try {
|
|
@@ -726,10 +875,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
726
875
|
${logTail}`);
|
|
727
876
|
}
|
|
728
877
|
function readDotenvApiKeys(projectDir) {
|
|
729
|
-
const dotenvPath =
|
|
730
|
-
if (!
|
|
878
|
+
const dotenvPath = path6.join(projectDir, ".env");
|
|
879
|
+
if (!fs7.existsSync(dotenvPath)) return {};
|
|
731
880
|
const result = {};
|
|
732
|
-
for (const rawLine of
|
|
881
|
+
for (const rawLine of fs7.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
733
882
|
const line = rawLine.trim();
|
|
734
883
|
if (!line || line.startsWith("#")) continue;
|
|
735
884
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -752,8 +901,8 @@ function stripBlockingEnv(env) {
|
|
|
752
901
|
}
|
|
753
902
|
|
|
754
903
|
// src/profile.ts
|
|
755
|
-
import * as
|
|
756
|
-
import * as
|
|
904
|
+
import * as fs8 from "fs";
|
|
905
|
+
import * as path7 from "path";
|
|
757
906
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
758
907
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
759
908
|
var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "watch", "utility"]);
|
|
@@ -768,12 +917,12 @@ var ProfileError = class extends Error {
|
|
|
768
917
|
profilePath;
|
|
769
918
|
};
|
|
770
919
|
function loadProfile(profilePath) {
|
|
771
|
-
if (!
|
|
920
|
+
if (!fs8.existsSync(profilePath)) {
|
|
772
921
|
throw new ProfileError(profilePath, "file not found");
|
|
773
922
|
}
|
|
774
923
|
let raw;
|
|
775
924
|
try {
|
|
776
|
-
raw = JSON.parse(
|
|
925
|
+
raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
|
|
777
926
|
} catch (err) {
|
|
778
927
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
779
928
|
}
|
|
@@ -813,7 +962,7 @@ function loadProfile(profilePath) {
|
|
|
813
962
|
outputContract: r.outputContract,
|
|
814
963
|
inputArtifacts: parseInputArtifacts(profilePath, r.input),
|
|
815
964
|
outputArtifacts: parseOutputArtifacts(profilePath, r.output),
|
|
816
|
-
dir:
|
|
965
|
+
dir: path7.dirname(profilePath)
|
|
817
966
|
};
|
|
818
967
|
return profile;
|
|
819
968
|
}
|
|
@@ -861,6 +1010,7 @@ function parseInputs(p, raw) {
|
|
|
861
1010
|
if (r.requiredWhen && typeof r.requiredWhen === "object") {
|
|
862
1011
|
spec.requiredWhen = r.requiredWhen;
|
|
863
1012
|
}
|
|
1013
|
+
if (r.bindsCommentRest === true) spec.bindsCommentRest = true;
|
|
864
1014
|
out.push(spec);
|
|
865
1015
|
}
|
|
866
1016
|
return out;
|
|
@@ -1242,21 +1392,21 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
1242
1392
|
};
|
|
1243
1393
|
|
|
1244
1394
|
// src/scripts/buildSyntheticPlugin.ts
|
|
1245
|
-
import * as
|
|
1395
|
+
import * as fs9 from "fs";
|
|
1246
1396
|
import * as os2 from "os";
|
|
1247
|
-
import * as
|
|
1397
|
+
import * as path8 from "path";
|
|
1248
1398
|
function getPluginsCatalogRoot() {
|
|
1249
|
-
const here =
|
|
1399
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
1250
1400
|
const candidates = [
|
|
1251
|
-
|
|
1401
|
+
path8.join(here, "..", "plugins"),
|
|
1252
1402
|
// dev: src/scripts → src/plugins
|
|
1253
|
-
|
|
1403
|
+
path8.join(here, "..", "..", "plugins"),
|
|
1254
1404
|
// built: dist/scripts → dist/plugins
|
|
1255
|
-
|
|
1405
|
+
path8.join(here, "..", "..", "src", "plugins")
|
|
1256
1406
|
// fallback
|
|
1257
1407
|
];
|
|
1258
1408
|
for (const c of candidates) {
|
|
1259
|
-
if (
|
|
1409
|
+
if (fs9.existsSync(c) && fs9.statSync(c).isDirectory()) return c;
|
|
1260
1410
|
}
|
|
1261
1411
|
return candidates[0];
|
|
1262
1412
|
}
|
|
@@ -1266,50 +1416,50 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1266
1416
|
if (!needsSynthetic) return;
|
|
1267
1417
|
const catalog = getPluginsCatalogRoot();
|
|
1268
1418
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1269
|
-
const root =
|
|
1270
|
-
|
|
1419
|
+
const root = path8.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
1420
|
+
fs9.mkdirSync(path8.join(root, ".claude-plugin"), { recursive: true });
|
|
1271
1421
|
if (cc.skills.length > 0) {
|
|
1272
|
-
const dst =
|
|
1273
|
-
|
|
1422
|
+
const dst = path8.join(root, "skills");
|
|
1423
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1274
1424
|
for (const name of cc.skills) {
|
|
1275
|
-
const src =
|
|
1276
|
-
if (!
|
|
1277
|
-
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));
|
|
1278
1428
|
}
|
|
1279
1429
|
}
|
|
1280
1430
|
if (cc.commands.length > 0) {
|
|
1281
|
-
const dst =
|
|
1282
|
-
|
|
1431
|
+
const dst = path8.join(root, "commands");
|
|
1432
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1283
1433
|
for (const name of cc.commands) {
|
|
1284
|
-
const src =
|
|
1285
|
-
if (!
|
|
1286
|
-
|
|
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`));
|
|
1287
1437
|
}
|
|
1288
1438
|
}
|
|
1289
1439
|
if (cc.subagents.length > 0) {
|
|
1290
|
-
const dst =
|
|
1291
|
-
|
|
1440
|
+
const dst = path8.join(root, "agents");
|
|
1441
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1292
1442
|
for (const name of cc.subagents) {
|
|
1293
|
-
const src =
|
|
1294
|
-
if (!
|
|
1295
|
-
|
|
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`));
|
|
1296
1446
|
}
|
|
1297
1447
|
}
|
|
1298
1448
|
if (cc.hooks.length > 0) {
|
|
1299
|
-
const dst =
|
|
1300
|
-
|
|
1449
|
+
const dst = path8.join(root, "hooks");
|
|
1450
|
+
fs9.mkdirSync(dst, { recursive: true });
|
|
1301
1451
|
const merged = { hooks: {} };
|
|
1302
1452
|
for (const name of cc.hooks) {
|
|
1303
|
-
const src =
|
|
1304
|
-
if (!
|
|
1305
|
-
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"));
|
|
1306
1456
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
1307
1457
|
if (!Array.isArray(entries)) continue;
|
|
1308
1458
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
1309
1459
|
merged.hooks[event].push(...entries);
|
|
1310
1460
|
}
|
|
1311
1461
|
}
|
|
1312
|
-
|
|
1462
|
+
fs9.writeFileSync(path8.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
1313
1463
|
`);
|
|
1314
1464
|
}
|
|
1315
1465
|
const manifest = {
|
|
@@ -1320,17 +1470,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1320
1470
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
1321
1471
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
1322
1472
|
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
1323
|
-
|
|
1473
|
+
fs9.writeFileSync(path8.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
1324
1474
|
`);
|
|
1325
1475
|
ctx.data.syntheticPluginPath = root;
|
|
1326
1476
|
};
|
|
1327
1477
|
function copyDir(src, dst) {
|
|
1328
|
-
|
|
1329
|
-
for (const ent of
|
|
1330
|
-
const s =
|
|
1331
|
-
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);
|
|
1332
1482
|
if (ent.isDirectory()) copyDir(s, d);
|
|
1333
|
-
else if (ent.isFile())
|
|
1483
|
+
else if (ent.isFile()) fs9.copyFileSync(s, d);
|
|
1334
1484
|
}
|
|
1335
1485
|
}
|
|
1336
1486
|
|
|
@@ -1396,18 +1546,18 @@ function formatMissesForFeedback(misses) {
|
|
|
1396
1546
|
}
|
|
1397
1547
|
|
|
1398
1548
|
// src/prompt.ts
|
|
1399
|
-
import * as
|
|
1400
|
-
import * as
|
|
1549
|
+
import * as fs10 from "fs";
|
|
1550
|
+
import * as path9 from "path";
|
|
1401
1551
|
var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
1402
1552
|
var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
1403
1553
|
function loadProjectConventions(projectDir) {
|
|
1404
1554
|
const out = [];
|
|
1405
1555
|
for (const rel of CONVENTION_FILES) {
|
|
1406
|
-
const abs =
|
|
1407
|
-
if (!
|
|
1556
|
+
const abs = path9.join(projectDir, rel);
|
|
1557
|
+
if (!fs10.existsSync(abs)) continue;
|
|
1408
1558
|
let content;
|
|
1409
1559
|
try {
|
|
1410
|
-
content =
|
|
1560
|
+
content = fs10.readFileSync(abs, "utf-8");
|
|
1411
1561
|
} catch {
|
|
1412
1562
|
continue;
|
|
1413
1563
|
}
|
|
@@ -1428,6 +1578,7 @@ function parseAgentResult(finalText) {
|
|
|
1428
1578
|
prSummary: "",
|
|
1429
1579
|
feedbackActions: "",
|
|
1430
1580
|
planDeviations: "",
|
|
1581
|
+
priorArt: "",
|
|
1431
1582
|
failureReason: "agent produced no final message"
|
|
1432
1583
|
};
|
|
1433
1584
|
const failedMatch = text.match(/(?:^|\n)\s*FAILED\s*:\s*(.+?)\s*$/s);
|
|
@@ -1438,6 +1589,7 @@ function parseAgentResult(finalText) {
|
|
|
1438
1589
|
prSummary: "",
|
|
1439
1590
|
feedbackActions: "",
|
|
1440
1591
|
planDeviations: "",
|
|
1592
|
+
priorArt: "",
|
|
1441
1593
|
failureReason: failedMatch[1].trim()
|
|
1442
1594
|
};
|
|
1443
1595
|
}
|
|
@@ -1450,6 +1602,7 @@ function parseAgentResult(finalText) {
|
|
|
1450
1602
|
prSummary: "",
|
|
1451
1603
|
feedbackActions: "",
|
|
1452
1604
|
planDeviations: "",
|
|
1605
|
+
priorArt: "",
|
|
1453
1606
|
failureReason: "no DONE or FAILED marker in agent output"
|
|
1454
1607
|
};
|
|
1455
1608
|
}
|
|
@@ -1458,24 +1611,27 @@ function parseAgentResult(finalText) {
|
|
|
1458
1611
|
const feedbackActions = extractBlock(
|
|
1459
1612
|
text,
|
|
1460
1613
|
/(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
|
|
1461
|
-
/(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY)\s*:/i
|
|
1614
|
+
/(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
|
|
1462
1615
|
);
|
|
1463
1616
|
let planDeviations = extractBlock(
|
|
1464
1617
|
text,
|
|
1465
1618
|
/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
|
|
1466
|
-
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS)\s*:/i
|
|
1619
|
+
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
|
|
1467
1620
|
);
|
|
1468
1621
|
if (!planDeviations) {
|
|
1469
1622
|
const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
1470
1623
|
if (inline) planDeviations = inline[1].trim();
|
|
1471
1624
|
}
|
|
1625
|
+
let priorArt = "";
|
|
1626
|
+
const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
1627
|
+
if (priorArtInline) priorArt = priorArtInline[1].trim();
|
|
1472
1628
|
const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
|
|
1473
1629
|
let prSummary = "";
|
|
1474
1630
|
if (summaryStart !== -1) {
|
|
1475
1631
|
const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
|
|
1476
1632
|
prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
|
|
1477
1633
|
}
|
|
1478
|
-
return { done: true, commitMessage, prSummary, feedbackActions, planDeviations, failureReason: "" };
|
|
1634
|
+
return { done: true, commitMessage, prSummary, feedbackActions, planDeviations, priorArt, failureReason: "" };
|
|
1479
1635
|
}
|
|
1480
1636
|
function extractBlock(text, startMarker, endMarker) {
|
|
1481
1637
|
const startIdx = text.search(startMarker);
|
|
@@ -1565,8 +1721,8 @@ import { execFileSync as execFileSync6 } from "child_process";
|
|
|
1565
1721
|
|
|
1566
1722
|
// src/commit.ts
|
|
1567
1723
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
1568
|
-
import * as
|
|
1569
|
-
import * as
|
|
1724
|
+
import * as fs11 from "fs";
|
|
1725
|
+
import * as path10 from "path";
|
|
1570
1726
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
1571
1727
|
".kody/",
|
|
1572
1728
|
".kody-engine/",
|
|
@@ -1621,18 +1777,18 @@ function tryGit(args, cwd) {
|
|
|
1621
1777
|
}
|
|
1622
1778
|
function abortUnfinishedGitOps(cwd) {
|
|
1623
1779
|
const aborted = [];
|
|
1624
|
-
const gitDir =
|
|
1625
|
-
if (!
|
|
1626
|
-
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"))) {
|
|
1627
1783
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
1628
1784
|
}
|
|
1629
|
-
if (
|
|
1785
|
+
if (fs11.existsSync(path10.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
1630
1786
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
1631
1787
|
}
|
|
1632
|
-
if (
|
|
1788
|
+
if (fs11.existsSync(path10.join(gitDir, "REVERT_HEAD"))) {
|
|
1633
1789
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
1634
1790
|
}
|
|
1635
|
-
if (
|
|
1791
|
+
if (fs11.existsSync(path10.join(gitDir, "rebase-merge")) || fs11.existsSync(path10.join(gitDir, "rebase-apply"))) {
|
|
1636
1792
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
1637
1793
|
}
|
|
1638
1794
|
try {
|
|
@@ -1687,7 +1843,7 @@ function normalizeCommitMessage(raw) {
|
|
|
1687
1843
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
1688
1844
|
const allChanged = listChangedFiles(cwd);
|
|
1689
1845
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
1690
|
-
const mergeHeadExists =
|
|
1846
|
+
const mergeHeadExists = fs11.existsSync(path10.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
1691
1847
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
1692
1848
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
1693
1849
|
}
|
|
@@ -1783,20 +1939,20 @@ function defaultCommitMessage(mode, data) {
|
|
|
1783
1939
|
}
|
|
1784
1940
|
|
|
1785
1941
|
// src/scripts/composePrompt.ts
|
|
1786
|
-
import * as
|
|
1787
|
-
import * as
|
|
1942
|
+
import * as fs12 from "fs";
|
|
1943
|
+
import * as path11 from "path";
|
|
1788
1944
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
1789
1945
|
var composePrompt = async (ctx, profile) => {
|
|
1790
1946
|
const explicit = ctx.data.promptTemplate;
|
|
1791
1947
|
const mode = ctx.args.mode;
|
|
1792
1948
|
const candidates = [
|
|
1793
|
-
explicit ?
|
|
1794
|
-
mode ?
|
|
1795
|
-
|
|
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")
|
|
1796
1952
|
].filter(Boolean);
|
|
1797
1953
|
let templatePath = "";
|
|
1798
1954
|
for (const c of candidates) {
|
|
1799
|
-
if (
|
|
1955
|
+
if (fs12.existsSync(c)) {
|
|
1800
1956
|
templatePath = c;
|
|
1801
1957
|
break;
|
|
1802
1958
|
}
|
|
@@ -1804,7 +1960,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
1804
1960
|
if (!templatePath) {
|
|
1805
1961
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
1806
1962
|
}
|
|
1807
|
-
const template =
|
|
1963
|
+
const template = fs12.readFileSync(templatePath, "utf-8");
|
|
1808
1964
|
const tokens = {
|
|
1809
1965
|
...stringifyAll(ctx.args, "args."),
|
|
1810
1966
|
...stringifyAll(ctx.data, ""),
|
|
@@ -1881,17 +2037,17 @@ function formatToolsUsage(profile) {
|
|
|
1881
2037
|
}
|
|
1882
2038
|
|
|
1883
2039
|
// src/scripts/discoverQaContext.ts
|
|
1884
|
-
import * as
|
|
1885
|
-
import * as
|
|
2040
|
+
import * as fs14 from "fs";
|
|
2041
|
+
import * as path13 from "path";
|
|
1886
2042
|
|
|
1887
2043
|
// src/scripts/frameworkDetectors.ts
|
|
1888
|
-
import * as
|
|
1889
|
-
import * as
|
|
2044
|
+
import * as fs13 from "fs";
|
|
2045
|
+
import * as path12 from "path";
|
|
1890
2046
|
function detectFrameworks(cwd) {
|
|
1891
2047
|
const out = [];
|
|
1892
2048
|
let deps = {};
|
|
1893
2049
|
try {
|
|
1894
|
-
const pkg = JSON.parse(
|
|
2050
|
+
const pkg = JSON.parse(fs13.readFileSync(path12.join(cwd, "package.json"), "utf-8"));
|
|
1895
2051
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1896
2052
|
} catch {
|
|
1897
2053
|
return out;
|
|
@@ -1928,7 +2084,7 @@ function detectFrameworks(cwd) {
|
|
|
1928
2084
|
}
|
|
1929
2085
|
function findFile(cwd, candidates) {
|
|
1930
2086
|
for (const c of candidates) {
|
|
1931
|
-
if (
|
|
2087
|
+
if (fs13.existsSync(path12.join(cwd, c))) return c;
|
|
1932
2088
|
}
|
|
1933
2089
|
return null;
|
|
1934
2090
|
}
|
|
@@ -1941,18 +2097,18 @@ var COLLECTION_DIRS = [
|
|
|
1941
2097
|
function discoverPayloadCollections(cwd) {
|
|
1942
2098
|
const out = [];
|
|
1943
2099
|
for (const dir of COLLECTION_DIRS) {
|
|
1944
|
-
const full =
|
|
1945
|
-
if (!
|
|
2100
|
+
const full = path12.join(cwd, dir);
|
|
2101
|
+
if (!fs13.existsSync(full)) continue;
|
|
1946
2102
|
let files;
|
|
1947
2103
|
try {
|
|
1948
|
-
files =
|
|
2104
|
+
files = fs13.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
1949
2105
|
} catch {
|
|
1950
2106
|
continue;
|
|
1951
2107
|
}
|
|
1952
2108
|
for (const file of files) {
|
|
1953
2109
|
try {
|
|
1954
|
-
const filePath =
|
|
1955
|
-
const content =
|
|
2110
|
+
const filePath = path12.join(full, file);
|
|
2111
|
+
const content = fs13.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
1956
2112
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
1957
2113
|
if (!slugMatch) continue;
|
|
1958
2114
|
const slug = slugMatch[1];
|
|
@@ -1966,7 +2122,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
1966
2122
|
out.push({
|
|
1967
2123
|
name,
|
|
1968
2124
|
slug,
|
|
1969
|
-
filePath:
|
|
2125
|
+
filePath: path12.relative(cwd, filePath),
|
|
1970
2126
|
fields: fields.slice(0, 20),
|
|
1971
2127
|
hasAdmin
|
|
1972
2128
|
});
|
|
@@ -1980,28 +2136,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
1980
2136
|
function discoverAdminComponents(cwd, collections) {
|
|
1981
2137
|
const out = [];
|
|
1982
2138
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
1983
|
-
const full =
|
|
1984
|
-
if (!
|
|
2139
|
+
const full = path12.join(cwd, dir);
|
|
2140
|
+
if (!fs13.existsSync(full)) continue;
|
|
1985
2141
|
let entries;
|
|
1986
2142
|
try {
|
|
1987
|
-
entries =
|
|
2143
|
+
entries = fs13.readdirSync(full, { withFileTypes: true });
|
|
1988
2144
|
} catch {
|
|
1989
2145
|
continue;
|
|
1990
2146
|
}
|
|
1991
2147
|
for (const entry of entries) {
|
|
1992
|
-
const entryPath =
|
|
2148
|
+
const entryPath = path12.join(full, entry.name);
|
|
1993
2149
|
let name;
|
|
1994
2150
|
let filePath;
|
|
1995
2151
|
if (entry.isDirectory()) {
|
|
1996
2152
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
1997
|
-
(f) =>
|
|
2153
|
+
(f) => fs13.existsSync(path12.join(entryPath, f))
|
|
1998
2154
|
);
|
|
1999
2155
|
if (!indexFile) continue;
|
|
2000
2156
|
name = entry.name;
|
|
2001
|
-
filePath =
|
|
2157
|
+
filePath = path12.relative(cwd, path12.join(entryPath, indexFile));
|
|
2002
2158
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
2003
2159
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
2004
|
-
filePath =
|
|
2160
|
+
filePath = path12.relative(cwd, entryPath);
|
|
2005
2161
|
} else {
|
|
2006
2162
|
continue;
|
|
2007
2163
|
}
|
|
@@ -2009,7 +2165,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
2009
2165
|
if (collections) {
|
|
2010
2166
|
for (const col of collections) {
|
|
2011
2167
|
try {
|
|
2012
|
-
const colContent =
|
|
2168
|
+
const colContent = fs13.readFileSync(path12.join(cwd, col.filePath), "utf-8");
|
|
2013
2169
|
if (colContent.includes(name)) {
|
|
2014
2170
|
usedInCollection = col.slug;
|
|
2015
2171
|
break;
|
|
@@ -2028,8 +2184,8 @@ function scanApiRoutes(cwd) {
|
|
|
2028
2184
|
const out = [];
|
|
2029
2185
|
const appDirs = ["src/app", "app"];
|
|
2030
2186
|
for (const appDir of appDirs) {
|
|
2031
|
-
const apiDir =
|
|
2032
|
-
if (!
|
|
2187
|
+
const apiDir = path12.join(cwd, appDir, "api");
|
|
2188
|
+
if (!fs13.existsSync(apiDir)) continue;
|
|
2033
2189
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
2034
2190
|
break;
|
|
2035
2191
|
}
|
|
@@ -2038,20 +2194,20 @@ function scanApiRoutes(cwd) {
|
|
|
2038
2194
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
2039
2195
|
let entries;
|
|
2040
2196
|
try {
|
|
2041
|
-
entries =
|
|
2197
|
+
entries = fs13.readdirSync(dir, { withFileTypes: true });
|
|
2042
2198
|
} catch {
|
|
2043
2199
|
return;
|
|
2044
2200
|
}
|
|
2045
2201
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
2046
2202
|
if (routeFile) {
|
|
2047
2203
|
try {
|
|
2048
|
-
const content =
|
|
2204
|
+
const content = fs13.readFileSync(path12.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
2049
2205
|
const methods = HTTP_METHODS.filter((m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content));
|
|
2050
2206
|
if (methods.length > 0) {
|
|
2051
2207
|
out.push({
|
|
2052
2208
|
path: prefix,
|
|
2053
2209
|
methods,
|
|
2054
|
-
filePath:
|
|
2210
|
+
filePath: path12.relative(cwd, path12.join(dir, routeFile.name))
|
|
2055
2211
|
});
|
|
2056
2212
|
}
|
|
2057
2213
|
} catch {
|
|
@@ -2062,7 +2218,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
2062
2218
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
2063
2219
|
let segment = entry.name;
|
|
2064
2220
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
2065
|
-
walkApiRoutes(
|
|
2221
|
+
walkApiRoutes(path12.join(dir, entry.name), prefix, cwd, out);
|
|
2066
2222
|
continue;
|
|
2067
2223
|
}
|
|
2068
2224
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -2070,7 +2226,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
2070
2226
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
2071
2227
|
segment = `:${segment.slice(1, -1)}`;
|
|
2072
2228
|
}
|
|
2073
|
-
walkApiRoutes(
|
|
2229
|
+
walkApiRoutes(path12.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
2074
2230
|
}
|
|
2075
2231
|
}
|
|
2076
2232
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -2090,10 +2246,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
2090
2246
|
function scanEnvVars(cwd) {
|
|
2091
2247
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
2092
2248
|
for (const envFile of candidates) {
|
|
2093
|
-
const envPath =
|
|
2094
|
-
if (!
|
|
2249
|
+
const envPath = path12.join(cwd, envFile);
|
|
2250
|
+
if (!fs13.existsSync(envPath)) continue;
|
|
2095
2251
|
try {
|
|
2096
|
-
const content =
|
|
2252
|
+
const content = fs13.readFileSync(envPath, "utf-8");
|
|
2097
2253
|
const vars = [];
|
|
2098
2254
|
for (const line of content.split("\n")) {
|
|
2099
2255
|
const trimmed = line.trim();
|
|
@@ -2141,9 +2297,9 @@ function runQaDiscovery(cwd) {
|
|
|
2141
2297
|
}
|
|
2142
2298
|
function detectDevServer(cwd, out) {
|
|
2143
2299
|
try {
|
|
2144
|
-
const pkg = JSON.parse(
|
|
2300
|
+
const pkg = JSON.parse(fs14.readFileSync(path13.join(cwd, "package.json"), "utf-8"));
|
|
2145
2301
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2146
|
-
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";
|
|
2147
2303
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
2148
2304
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
2149
2305
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -2153,8 +2309,8 @@ function detectDevServer(cwd, out) {
|
|
|
2153
2309
|
function scanFrontendRoutes(cwd, out) {
|
|
2154
2310
|
const appDirs = ["src/app", "app"];
|
|
2155
2311
|
for (const appDir of appDirs) {
|
|
2156
|
-
const full =
|
|
2157
|
-
if (!
|
|
2312
|
+
const full = path13.join(cwd, appDir);
|
|
2313
|
+
if (!fs14.existsSync(full)) continue;
|
|
2158
2314
|
walkFrontendRoutes(full, "", out);
|
|
2159
2315
|
break;
|
|
2160
2316
|
}
|
|
@@ -2162,7 +2318,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
2162
2318
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
2163
2319
|
let entries;
|
|
2164
2320
|
try {
|
|
2165
|
-
entries =
|
|
2321
|
+
entries = fs14.readdirSync(dir, { withFileTypes: true });
|
|
2166
2322
|
} catch {
|
|
2167
2323
|
return;
|
|
2168
2324
|
}
|
|
@@ -2179,7 +2335,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
2179
2335
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
2180
2336
|
let segment = entry.name;
|
|
2181
2337
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
2182
|
-
walkFrontendRoutes(
|
|
2338
|
+
walkFrontendRoutes(path13.join(dir, entry.name), prefix, out);
|
|
2183
2339
|
continue;
|
|
2184
2340
|
}
|
|
2185
2341
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -2187,7 +2343,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
2187
2343
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
2188
2344
|
segment = `:${segment.slice(1, -1)}`;
|
|
2189
2345
|
}
|
|
2190
|
-
walkFrontendRoutes(
|
|
2346
|
+
walkFrontendRoutes(path13.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
2191
2347
|
}
|
|
2192
2348
|
}
|
|
2193
2349
|
function detectAuthFiles(cwd, out) {
|
|
@@ -2204,23 +2360,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
2204
2360
|
"src/app/api/oauth"
|
|
2205
2361
|
];
|
|
2206
2362
|
for (const c of candidates) {
|
|
2207
|
-
if (
|
|
2363
|
+
if (fs14.existsSync(path13.join(cwd, c))) out.authFiles.push(c);
|
|
2208
2364
|
}
|
|
2209
2365
|
}
|
|
2210
2366
|
function detectRoles(cwd, out) {
|
|
2211
2367
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
2212
2368
|
for (const rp of rolePaths) {
|
|
2213
|
-
const dir =
|
|
2214
|
-
if (!
|
|
2369
|
+
const dir = path13.join(cwd, rp);
|
|
2370
|
+
if (!fs14.existsSync(dir)) continue;
|
|
2215
2371
|
let files;
|
|
2216
2372
|
try {
|
|
2217
|
-
files =
|
|
2373
|
+
files = fs14.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
2218
2374
|
} catch {
|
|
2219
2375
|
continue;
|
|
2220
2376
|
}
|
|
2221
2377
|
for (const f of files) {
|
|
2222
2378
|
try {
|
|
2223
|
-
const content =
|
|
2379
|
+
const content = fs14.readFileSync(path13.join(dir, f), "utf-8").slice(0, 5e3);
|
|
2224
2380
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
2225
2381
|
if (roleMatches) {
|
|
2226
2382
|
for (const m of roleMatches) {
|
|
@@ -2707,67 +2863,6 @@ function computeFailureReason(ctx) {
|
|
|
2707
2863
|
// src/scripts/finishFlow.ts
|
|
2708
2864
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
2709
2865
|
|
|
2710
|
-
// src/registry.ts
|
|
2711
|
-
import * as fs14 from "fs";
|
|
2712
|
-
import * as path13 from "path";
|
|
2713
|
-
function getExecutablesRoot() {
|
|
2714
|
-
const here = path13.dirname(new URL(import.meta.url).pathname);
|
|
2715
|
-
const candidates = [
|
|
2716
|
-
path13.join(here, "executables"),
|
|
2717
|
-
// dev: src/
|
|
2718
|
-
path13.join(here, "..", "executables"),
|
|
2719
|
-
// built: dist/bin → dist/executables
|
|
2720
|
-
path13.join(here, "..", "src", "executables")
|
|
2721
|
-
// fallback
|
|
2722
|
-
];
|
|
2723
|
-
for (const c of candidates) {
|
|
2724
|
-
if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
|
|
2725
|
-
}
|
|
2726
|
-
return candidates[0];
|
|
2727
|
-
}
|
|
2728
|
-
function listExecutables(root = getExecutablesRoot()) {
|
|
2729
|
-
if (!fs14.existsSync(root)) return [];
|
|
2730
|
-
const entries = fs14.readdirSync(root, { withFileTypes: true });
|
|
2731
|
-
const out = [];
|
|
2732
|
-
for (const ent of entries) {
|
|
2733
|
-
if (!ent.isDirectory()) continue;
|
|
2734
|
-
const profilePath = path13.join(root, ent.name, "profile.json");
|
|
2735
|
-
if (fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile()) {
|
|
2736
|
-
out.push({ name: ent.name, profilePath });
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
2740
|
-
}
|
|
2741
|
-
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
2742
|
-
if (!isSafeName(name)) return false;
|
|
2743
|
-
const profilePath = path13.join(root, name, "profile.json");
|
|
2744
|
-
return fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile();
|
|
2745
|
-
}
|
|
2746
|
-
function isSafeName(name) {
|
|
2747
|
-
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
2748
|
-
}
|
|
2749
|
-
function parseGenericFlags(argv) {
|
|
2750
|
-
const args = {};
|
|
2751
|
-
const positional = [];
|
|
2752
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2753
|
-
const arg = argv[i];
|
|
2754
|
-
if (!arg.startsWith("--")) {
|
|
2755
|
-
positional.push(arg);
|
|
2756
|
-
continue;
|
|
2757
|
-
}
|
|
2758
|
-
const key = arg.slice(2);
|
|
2759
|
-
const next = argv[i + 1];
|
|
2760
|
-
const value = next !== void 0 && !next.startsWith("--") ? (i++, next) : true;
|
|
2761
|
-
args[key] = value;
|
|
2762
|
-
if (key.includes("-")) {
|
|
2763
|
-
const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
2764
|
-
if (camel !== key && args[camel] === void 0) args[camel] = value;
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
if (positional.length > 0) args._ = positional;
|
|
2768
|
-
return args;
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
2866
|
// src/lifecycleLabels.ts
|
|
2772
2867
|
var KODY_NAMESPACE = "kody";
|
|
2773
2868
|
function groupOf(label) {
|
|
@@ -3600,6 +3695,97 @@ var loadIssueContext = async (ctx) => {
|
|
|
3600
3695
|
ctx.data.commentTargetNumber = issueNumber;
|
|
3601
3696
|
};
|
|
3602
3697
|
|
|
3698
|
+
// src/scripts/loadPriorArt.ts
|
|
3699
|
+
var PER_PR_DIFF_MAX_BYTES = 8e3;
|
|
3700
|
+
var TOTAL_MAX_BYTES = 3e4;
|
|
3701
|
+
var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
3702
|
+
var loadPriorArt = async (ctx, _profile, args) => {
|
|
3703
|
+
const artifactName = typeof args?.artifactName === "string" ? args.artifactName : "priorArt";
|
|
3704
|
+
const state = ctx.data.taskState;
|
|
3705
|
+
const artifact = state?.artifacts?.[artifactName];
|
|
3706
|
+
const prNumbers = parsePrNumbers(artifact?.content);
|
|
3707
|
+
if (prNumbers.length === 0) {
|
|
3708
|
+
ctx.data.priorArt = "";
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
const blocks = [];
|
|
3712
|
+
for (const n of prNumbers) {
|
|
3713
|
+
const block = fetchPrBlock(n, ctx.cwd);
|
|
3714
|
+
if (block) blocks.push(block);
|
|
3715
|
+
}
|
|
3716
|
+
const joined = blocks.join("\n\n---\n\n");
|
|
3717
|
+
ctx.data.priorArt = joined.length > TOTAL_MAX_BYTES ? joined.slice(0, TOTAL_MAX_BYTES) + TRUNCATED_SUFFIX : joined;
|
|
3718
|
+
};
|
|
3719
|
+
function parsePrNumbers(raw) {
|
|
3720
|
+
if (!raw) return [];
|
|
3721
|
+
const trimmed = raw.trim();
|
|
3722
|
+
if (!trimmed) return [];
|
|
3723
|
+
try {
|
|
3724
|
+
const parsed = JSON.parse(trimmed);
|
|
3725
|
+
if (!Array.isArray(parsed)) return [];
|
|
3726
|
+
return parsed.filter((n) => typeof n === "number" && Number.isInteger(n) && n > 0);
|
|
3727
|
+
} catch {
|
|
3728
|
+
return [];
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
function fetchPrBlock(prNumber, cwd) {
|
|
3732
|
+
try {
|
|
3733
|
+
const metaRaw = gh2(["pr", "view", String(prNumber), "--json", "title,state,url,mergedAt,closedAt"], { cwd });
|
|
3734
|
+
const meta = JSON.parse(metaRaw);
|
|
3735
|
+
const diff = truncate3(safeGh(["pr", "diff", String(prNumber)], cwd), PER_PR_DIFF_MAX_BYTES);
|
|
3736
|
+
const commentsRaw = safeGh(["pr", "view", String(prNumber), "--json", "comments,reviews"], cwd);
|
|
3737
|
+
const commentsBlock = formatReviewComments(commentsRaw);
|
|
3738
|
+
const lines = [
|
|
3739
|
+
`## Prior art: PR #${prNumber} \u2014 ${meta.title ?? "(no title)"} [${meta.state ?? "unknown"}]`,
|
|
3740
|
+
meta.url ? meta.url : "",
|
|
3741
|
+
"",
|
|
3742
|
+
"### Diff",
|
|
3743
|
+
"```diff",
|
|
3744
|
+
diff || "(empty)",
|
|
3745
|
+
"```"
|
|
3746
|
+
];
|
|
3747
|
+
if (commentsBlock) {
|
|
3748
|
+
lines.push("");
|
|
3749
|
+
lines.push("### Review comments");
|
|
3750
|
+
lines.push(commentsBlock);
|
|
3751
|
+
}
|
|
3752
|
+
return lines.filter((l) => l !== "").join("\n");
|
|
3753
|
+
} catch (err) {
|
|
3754
|
+
return `## Prior art: PR #${prNumber}
|
|
3755
|
+
_Could not fetch \u2014 ${err instanceof Error ? err.message : String(err)}_`;
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
function safeGh(args, cwd) {
|
|
3759
|
+
try {
|
|
3760
|
+
return gh2(args, { cwd });
|
|
3761
|
+
} catch {
|
|
3762
|
+
return "";
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
function truncate3(s, max) {
|
|
3766
|
+
return s.length <= max ? s : s.slice(0, max) + TRUNCATED_SUFFIX;
|
|
3767
|
+
}
|
|
3768
|
+
function formatReviewComments(raw) {
|
|
3769
|
+
if (!raw) return "";
|
|
3770
|
+
try {
|
|
3771
|
+
const parsed = JSON.parse(raw);
|
|
3772
|
+
const out = [];
|
|
3773
|
+
for (const c of parsed.comments ?? []) {
|
|
3774
|
+
if (!c.body) continue;
|
|
3775
|
+
out.push(`- **${c.author?.login ?? "unknown"}**: ${c.body.replace(/\n/g, " ").slice(0, 500)}`);
|
|
3776
|
+
}
|
|
3777
|
+
for (const r of parsed.reviews ?? []) {
|
|
3778
|
+
if (!r.body && !r.state) continue;
|
|
3779
|
+
const state = r.state ? ` (${r.state})` : "";
|
|
3780
|
+
const body = r.body ? `: ${r.body.replace(/\n/g, " ").slice(0, 500)}` : "";
|
|
3781
|
+
out.push(`- **${r.author?.login ?? "unknown"}**${state}${body}`);
|
|
3782
|
+
}
|
|
3783
|
+
return out.join("\n");
|
|
3784
|
+
} catch {
|
|
3785
|
+
return "";
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3603
3789
|
// src/scripts/loadTaskState.ts
|
|
3604
3790
|
var loadTaskState = async (ctx) => {
|
|
3605
3791
|
const target = ctx.data.commentTargetType;
|
|
@@ -3656,6 +3842,7 @@ var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
|
3656
3842
|
ctx.data.prSummary = parsed.prSummary;
|
|
3657
3843
|
ctx.data.feedbackActions = parsed.feedbackActions;
|
|
3658
3844
|
ctx.data.planDeviations = parsed.planDeviations;
|
|
3845
|
+
ctx.data.priorArt = parsed.priorArt;
|
|
3659
3846
|
ctx.data.agentFailureReason = parsed.failureReason;
|
|
3660
3847
|
ctx.data.agentOutcome = agentResult.outcome;
|
|
3661
3848
|
ctx.data.agentError = agentResult.error;
|
|
@@ -4924,6 +5111,7 @@ var preflightScripts = {
|
|
|
4924
5111
|
loadIssueContext,
|
|
4925
5112
|
loadConventions,
|
|
4926
5113
|
loadCoverageRules,
|
|
5114
|
+
loadPriorArt,
|
|
4927
5115
|
loadQaGuide,
|
|
4928
5116
|
buildSyntheticPlugin,
|
|
4929
5117
|
resolveArtifacts,
|
|
@@ -5079,7 +5267,7 @@ async function runExecutable(profileName, input) {
|
|
|
5079
5267
|
ndjsonDir,
|
|
5080
5268
|
allowedToolsOverride: profile.claudeCode.tools,
|
|
5081
5269
|
permissionModeOverride: profile.claudeCode.permissionMode,
|
|
5082
|
-
mcpServers: profile.claudeCode.mcpServers,
|
|
5270
|
+
mcpServers: profile.claudeCode.mcpServers.length > 0 ? profile.claudeCode.mcpServers : void 0,
|
|
5083
5271
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
5084
5272
|
maxTurns: profile.claudeCode.maxTurns,
|
|
5085
5273
|
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|