@kody-ade/kody-engine 0.2.46 → 0.2.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/kody2.js +685 -100
- package/dist/executables/orchestrator-plan-build-review/profile.json +76 -0
- package/dist/executables/orchestrator-plan-build-review/prompt.md +7 -0
- package/dist/executables/ui-review/profile.json +75 -0
- package/dist/executables/ui-review/prompt.md +103 -0
- package/package.json +1 -1
- package/dist/executables/orchestrator/profile.json +0 -67
- package/dist/executables/orchestrator/prompt.md +0 -56
package/dist/bin/kody2.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.2.
|
|
6
|
+
version: "0.2.48",
|
|
7
7
|
description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -51,8 +51,8 @@ var package_default = {
|
|
|
51
51
|
|
|
52
52
|
// src/chat-cli.ts
|
|
53
53
|
import { execFileSync as execFileSync20 } from "child_process";
|
|
54
|
-
import * as
|
|
55
|
-
import * as
|
|
54
|
+
import * as fs22 from "fs";
|
|
55
|
+
import * as path19 from "path";
|
|
56
56
|
|
|
57
57
|
// src/chat/events.ts
|
|
58
58
|
import * as fs from "fs";
|
|
@@ -529,8 +529,8 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
529
529
|
|
|
530
530
|
// src/kody2-cli.ts
|
|
531
531
|
import { execFileSync as execFileSync19 } from "child_process";
|
|
532
|
-
import * as
|
|
533
|
-
import * as
|
|
532
|
+
import * as fs21 from "fs";
|
|
533
|
+
import * as path18 from "path";
|
|
534
534
|
|
|
535
535
|
// src/dispatch.ts
|
|
536
536
|
import * as fs5 from "fs";
|
|
@@ -572,6 +572,9 @@ function autoDispatch(opts) {
|
|
|
572
572
|
if (/\bresolve\b/.test(afterTag)) {
|
|
573
573
|
return { executable: "resolve", cliArgs: { pr: targetNum }, target: targetNum };
|
|
574
574
|
}
|
|
575
|
+
if (/\bui-review\b/.test(afterTag)) {
|
|
576
|
+
return { executable: "ui-review", cliArgs: { pr: targetNum }, target: targetNum };
|
|
577
|
+
}
|
|
575
578
|
if (/\breview\b/.test(afterTag)) {
|
|
576
579
|
return { executable: "review", cliArgs: { pr: targetNum }, target: targetNum };
|
|
577
580
|
}
|
|
@@ -591,7 +594,19 @@ function autoDispatch(opts) {
|
|
|
591
594
|
return asDispatch(defaultExec, targetNum);
|
|
592
595
|
}
|
|
593
596
|
if (sub === "orchestrate" || sub === "orchestrator") {
|
|
594
|
-
|
|
597
|
+
const flow = extractFlowName(afterTag);
|
|
598
|
+
if (flow) {
|
|
599
|
+
return {
|
|
600
|
+
executable: `orchestrator-${flow}`,
|
|
601
|
+
cliArgs: { issue: targetNum, flow },
|
|
602
|
+
target: targetNum
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
executable: "orchestrator-plan-build-review",
|
|
607
|
+
cliArgs: { issue: targetNum, flow: "plan-build-review" },
|
|
608
|
+
target: targetNum
|
|
609
|
+
};
|
|
595
610
|
}
|
|
596
611
|
if (sub === "build") {
|
|
597
612
|
return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
|
|
@@ -611,14 +626,18 @@ function extractSubcommand(afterTag) {
|
|
|
611
626
|
if (!match) return null;
|
|
612
627
|
return match[1];
|
|
613
628
|
}
|
|
629
|
+
function extractFlowName(afterTag) {
|
|
630
|
+
const match = afterTag.match(/--flow[=\s]+([a-z][a-z0-9-]{0,60})/);
|
|
631
|
+
return match ? match[1] : null;
|
|
632
|
+
}
|
|
614
633
|
function extractFeedback(afterTag) {
|
|
615
634
|
const cleaned = afterTag.replace(/^(fix|please|kindly)(?:[\s:,.-]+|$)/i, "").trim();
|
|
616
635
|
return cleaned.length > 0 ? cleaned : void 0;
|
|
617
636
|
}
|
|
618
637
|
|
|
619
638
|
// src/executor.ts
|
|
620
|
-
import * as
|
|
621
|
-
import * as
|
|
639
|
+
import * as fs20 from "fs";
|
|
640
|
+
import * as path17 from "path";
|
|
622
641
|
|
|
623
642
|
// src/litellm.ts
|
|
624
643
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -948,6 +967,9 @@ function parseScriptList(p, key, raw) {
|
|
|
948
967
|
if (r.runWhen && typeof r.runWhen === "object") {
|
|
949
968
|
entry.runWhen = r.runWhen;
|
|
950
969
|
}
|
|
970
|
+
if (r.with && typeof r.with === "object") {
|
|
971
|
+
entry.with = r.with;
|
|
972
|
+
}
|
|
951
973
|
out.push(entry);
|
|
952
974
|
}
|
|
953
975
|
return out;
|
|
@@ -1078,29 +1100,11 @@ function noteFromAction(action) {
|
|
|
1078
1100
|
}
|
|
1079
1101
|
function renderStateComment(state) {
|
|
1080
1102
|
const lines = [];
|
|
1081
|
-
lines.push(
|
|
1082
|
-
lines.push("");
|
|
1083
|
-
lines.push("```json");
|
|
1084
|
-
lines.push(
|
|
1085
|
-
JSON.stringify(
|
|
1086
|
-
{
|
|
1087
|
-
schemaVersion: state.schemaVersion,
|
|
1088
|
-
core: state.core,
|
|
1089
|
-
artifacts: state.artifacts ?? {},
|
|
1090
|
-
executables: state.executables,
|
|
1091
|
-
history: state.history,
|
|
1092
|
-
...state.flow ? { flow: state.flow } : {}
|
|
1093
|
-
},
|
|
1094
|
-
null,
|
|
1095
|
-
2
|
|
1096
|
-
)
|
|
1097
|
-
);
|
|
1098
|
-
lines.push("```");
|
|
1099
|
-
lines.push("");
|
|
1100
|
-
lines.push(STATE_END);
|
|
1101
|
-
lines.push("");
|
|
1102
|
-
lines.push("## kody2 task state");
|
|
1103
|
+
lines.push("## \u{1F4CB} kody2 task state");
|
|
1103
1104
|
lines.push("");
|
|
1105
|
+
if (state.flow) {
|
|
1106
|
+
lines.push(`- **Flow:** \`${state.flow.name}\` (step: \`${state.flow.step}\`)`);
|
|
1107
|
+
}
|
|
1104
1108
|
lines.push(`- **Phase:** \`${state.core.phase}\` **Status:** \`${state.core.status}\``);
|
|
1105
1109
|
if (state.core.currentExecutable) {
|
|
1106
1110
|
lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
|
|
@@ -1127,6 +1131,31 @@ function renderStateComment(state) {
|
|
|
1127
1131
|
}
|
|
1128
1132
|
lines.push("");
|
|
1129
1133
|
}
|
|
1134
|
+
lines.push("<details>");
|
|
1135
|
+
lines.push("<summary>Raw state (JSON)</summary>");
|
|
1136
|
+
lines.push("");
|
|
1137
|
+
lines.push(STATE_BEGIN);
|
|
1138
|
+
lines.push("");
|
|
1139
|
+
lines.push("```json");
|
|
1140
|
+
lines.push(
|
|
1141
|
+
JSON.stringify(
|
|
1142
|
+
{
|
|
1143
|
+
schemaVersion: state.schemaVersion,
|
|
1144
|
+
core: state.core,
|
|
1145
|
+
artifacts: state.artifacts ?? {},
|
|
1146
|
+
executables: state.executables,
|
|
1147
|
+
history: state.history,
|
|
1148
|
+
...state.flow ? { flow: state.flow } : {}
|
|
1149
|
+
},
|
|
1150
|
+
null,
|
|
1151
|
+
2
|
|
1152
|
+
)
|
|
1153
|
+
);
|
|
1154
|
+
lines.push("```");
|
|
1155
|
+
lines.push("");
|
|
1156
|
+
lines.push(STATE_END);
|
|
1157
|
+
lines.push("");
|
|
1158
|
+
lines.push("</details>");
|
|
1130
1159
|
return lines.join("\n");
|
|
1131
1160
|
}
|
|
1132
1161
|
function readTaskState(target, number, cwd) {
|
|
@@ -1797,6 +1826,491 @@ function formatToolsUsage(profile) {
|
|
|
1797
1826
|
return lines.join("\n");
|
|
1798
1827
|
}
|
|
1799
1828
|
|
|
1829
|
+
// src/scripts/discoverQaContext.ts
|
|
1830
|
+
import * as fs13 from "fs";
|
|
1831
|
+
import * as path12 from "path";
|
|
1832
|
+
|
|
1833
|
+
// src/scripts/frameworkDetectors.ts
|
|
1834
|
+
import * as fs12 from "fs";
|
|
1835
|
+
import * as path11 from "path";
|
|
1836
|
+
function detectFrameworks(cwd) {
|
|
1837
|
+
const out = [];
|
|
1838
|
+
let deps = {};
|
|
1839
|
+
try {
|
|
1840
|
+
const pkg = JSON.parse(fs12.readFileSync(path11.join(cwd, "package.json"), "utf-8"));
|
|
1841
|
+
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1842
|
+
} catch {
|
|
1843
|
+
return out;
|
|
1844
|
+
}
|
|
1845
|
+
if (deps.payload || deps["@payloadcms/next"]) {
|
|
1846
|
+
out.push({
|
|
1847
|
+
name: "payload-cms",
|
|
1848
|
+
version: deps.payload ?? deps["@payloadcms/next"] ?? null,
|
|
1849
|
+
configFile: findFile(cwd, ["payload.config.ts", "payload-config.ts", "src/payload.config.ts"])
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
if (deps["next-auth"]) {
|
|
1853
|
+
out.push({
|
|
1854
|
+
name: "nextauth",
|
|
1855
|
+
version: deps["next-auth"] ?? null,
|
|
1856
|
+
configFile: findFile(cwd, ["auth.ts", "auth.config.ts", "src/auth.ts", "src/auth.config.ts"])
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
if (deps.prisma || deps["@prisma/client"]) {
|
|
1860
|
+
out.push({
|
|
1861
|
+
name: "prisma",
|
|
1862
|
+
version: deps.prisma ?? deps["@prisma/client"] ?? null,
|
|
1863
|
+
configFile: findFile(cwd, ["prisma/schema.prisma"])
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
if (deps.next) {
|
|
1867
|
+
out.push({
|
|
1868
|
+
name: "nextjs",
|
|
1869
|
+
version: deps.next ?? null,
|
|
1870
|
+
configFile: findFile(cwd, ["next.config.ts", "next.config.mjs", "next.config.js"])
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
return out;
|
|
1874
|
+
}
|
|
1875
|
+
function findFile(cwd, candidates) {
|
|
1876
|
+
for (const c of candidates) {
|
|
1877
|
+
if (fs12.existsSync(path11.join(cwd, c))) return c;
|
|
1878
|
+
}
|
|
1879
|
+
return null;
|
|
1880
|
+
}
|
|
1881
|
+
var COLLECTION_DIRS = [
|
|
1882
|
+
"src/server/payload/collections",
|
|
1883
|
+
"src/payload/collections",
|
|
1884
|
+
"src/collections",
|
|
1885
|
+
"payload/collections"
|
|
1886
|
+
];
|
|
1887
|
+
function discoverPayloadCollections(cwd) {
|
|
1888
|
+
const out = [];
|
|
1889
|
+
for (const dir of COLLECTION_DIRS) {
|
|
1890
|
+
const full = path11.join(cwd, dir);
|
|
1891
|
+
if (!fs12.existsSync(full)) continue;
|
|
1892
|
+
let files;
|
|
1893
|
+
try {
|
|
1894
|
+
files = fs12.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
1895
|
+
} catch {
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
for (const file of files) {
|
|
1899
|
+
try {
|
|
1900
|
+
const filePath = path11.join(full, file);
|
|
1901
|
+
const content = fs12.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
1902
|
+
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
1903
|
+
if (!slugMatch) continue;
|
|
1904
|
+
const slug = slugMatch[1];
|
|
1905
|
+
const name = file.replace(/\.(ts|tsx)$/, "");
|
|
1906
|
+
const fields = [];
|
|
1907
|
+
const fieldMatches = content.matchAll(/name:\s*['"]([a-zA-Z_][a-zA-Z0-9_]*)['"]/g);
|
|
1908
|
+
for (const m of fieldMatches) {
|
|
1909
|
+
if (!fields.includes(m[1])) fields.push(m[1]);
|
|
1910
|
+
}
|
|
1911
|
+
const hasAdmin = /components:\s*\{/.test(content) || /Field:\s*['"]/.test(content) || /Cell:\s*['"]/.test(content) || /views:\s*\{/.test(content);
|
|
1912
|
+
out.push({
|
|
1913
|
+
name,
|
|
1914
|
+
slug,
|
|
1915
|
+
filePath: path11.relative(cwd, filePath),
|
|
1916
|
+
fields: fields.slice(0, 20),
|
|
1917
|
+
hasAdmin
|
|
1918
|
+
});
|
|
1919
|
+
} catch {
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
return out;
|
|
1924
|
+
}
|
|
1925
|
+
var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/components/admin"];
|
|
1926
|
+
function discoverAdminComponents(cwd, collections) {
|
|
1927
|
+
const out = [];
|
|
1928
|
+
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
1929
|
+
const full = path11.join(cwd, dir);
|
|
1930
|
+
if (!fs12.existsSync(full)) continue;
|
|
1931
|
+
let entries;
|
|
1932
|
+
try {
|
|
1933
|
+
entries = fs12.readdirSync(full, { withFileTypes: true });
|
|
1934
|
+
} catch {
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
for (const entry of entries) {
|
|
1938
|
+
const entryPath = path11.join(full, entry.name);
|
|
1939
|
+
let name;
|
|
1940
|
+
let filePath;
|
|
1941
|
+
if (entry.isDirectory()) {
|
|
1942
|
+
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
1943
|
+
(f) => fs12.existsSync(path11.join(entryPath, f))
|
|
1944
|
+
);
|
|
1945
|
+
if (!indexFile) continue;
|
|
1946
|
+
name = entry.name;
|
|
1947
|
+
filePath = path11.relative(cwd, path11.join(entryPath, indexFile));
|
|
1948
|
+
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
1949
|
+
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
1950
|
+
filePath = path11.relative(cwd, entryPath);
|
|
1951
|
+
} else {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
let usedInCollection = null;
|
|
1955
|
+
if (collections) {
|
|
1956
|
+
for (const col of collections) {
|
|
1957
|
+
try {
|
|
1958
|
+
const colContent = fs12.readFileSync(path11.join(cwd, col.filePath), "utf-8");
|
|
1959
|
+
if (colContent.includes(name)) {
|
|
1960
|
+
usedInCollection = col.slug;
|
|
1961
|
+
break;
|
|
1962
|
+
}
|
|
1963
|
+
} catch {
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
out.push({ name, filePath, usedInCollection });
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return out;
|
|
1971
|
+
}
|
|
1972
|
+
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
1973
|
+
function scanApiRoutes(cwd) {
|
|
1974
|
+
const out = [];
|
|
1975
|
+
const appDirs = ["src/app", "app"];
|
|
1976
|
+
for (const appDir of appDirs) {
|
|
1977
|
+
const apiDir = path11.join(cwd, appDir, "api");
|
|
1978
|
+
if (!fs12.existsSync(apiDir)) continue;
|
|
1979
|
+
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
return out;
|
|
1983
|
+
}
|
|
1984
|
+
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
1985
|
+
let entries;
|
|
1986
|
+
try {
|
|
1987
|
+
entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
1988
|
+
} catch {
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
1992
|
+
if (routeFile) {
|
|
1993
|
+
try {
|
|
1994
|
+
const content = fs12.readFileSync(path11.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
1995
|
+
const methods = HTTP_METHODS.filter((m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content));
|
|
1996
|
+
if (methods.length > 0) {
|
|
1997
|
+
out.push({
|
|
1998
|
+
path: prefix,
|
|
1999
|
+
methods,
|
|
2000
|
+
filePath: path11.relative(cwd, path11.join(dir, routeFile.name))
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
} catch {
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
for (const entry of entries) {
|
|
2007
|
+
if (!entry.isDirectory()) continue;
|
|
2008
|
+
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
2009
|
+
let segment = entry.name;
|
|
2010
|
+
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
2011
|
+
walkApiRoutes(path11.join(dir, entry.name), prefix, cwd, out);
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
2015
|
+
segment = `:${segment.slice(2, -2)}?`;
|
|
2016
|
+
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
2017
|
+
segment = `:${segment.slice(1, -1)}`;
|
|
2018
|
+
}
|
|
2019
|
+
walkApiRoutes(path11.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
2023
|
+
"NODE_ENV",
|
|
2024
|
+
"HOME",
|
|
2025
|
+
"PATH",
|
|
2026
|
+
"USER",
|
|
2027
|
+
"SHELL",
|
|
2028
|
+
"TERM",
|
|
2029
|
+
"LANG",
|
|
2030
|
+
"PWD",
|
|
2031
|
+
"HOSTNAME",
|
|
2032
|
+
"PORT",
|
|
2033
|
+
"CI",
|
|
2034
|
+
"GITHUB_ACTIONS"
|
|
2035
|
+
]);
|
|
2036
|
+
function scanEnvVars(cwd) {
|
|
2037
|
+
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
2038
|
+
for (const envFile of candidates) {
|
|
2039
|
+
const envPath = path11.join(cwd, envFile);
|
|
2040
|
+
if (!fs12.existsSync(envPath)) continue;
|
|
2041
|
+
try {
|
|
2042
|
+
const content = fs12.readFileSync(envPath, "utf-8");
|
|
2043
|
+
const vars = [];
|
|
2044
|
+
for (const line of content.split("\n")) {
|
|
2045
|
+
const trimmed = line.trim();
|
|
2046
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2047
|
+
const match = trimmed.match(/^([A-Z][A-Z0-9_]*)=/);
|
|
2048
|
+
if (match && !BUILTIN_ENV_VARS.has(match[1])) vars.push(match[1]);
|
|
2049
|
+
}
|
|
2050
|
+
return vars;
|
|
2051
|
+
} catch {
|
|
2052
|
+
return [];
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
return [];
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// src/scripts/discoverQaContext.ts
|
|
2059
|
+
var MAX_SERIALIZED_LENGTH = 8e3;
|
|
2060
|
+
function runQaDiscovery(cwd) {
|
|
2061
|
+
const out = {
|
|
2062
|
+
routes: [],
|
|
2063
|
+
authFiles: [],
|
|
2064
|
+
loginPage: null,
|
|
2065
|
+
adminPath: null,
|
|
2066
|
+
roles: [],
|
|
2067
|
+
devCommand: "",
|
|
2068
|
+
devPort: 3e3,
|
|
2069
|
+
frameworks: [],
|
|
2070
|
+
collections: [],
|
|
2071
|
+
adminComponents: [],
|
|
2072
|
+
apiRoutes: [],
|
|
2073
|
+
envVars: []
|
|
2074
|
+
};
|
|
2075
|
+
detectDevServer(cwd, out);
|
|
2076
|
+
scanFrontendRoutes(cwd, out);
|
|
2077
|
+
detectAuthFiles(cwd, out);
|
|
2078
|
+
detectRoles(cwd, out);
|
|
2079
|
+
out.frameworks = detectFrameworks(cwd);
|
|
2080
|
+
const hasPayload = out.frameworks.some((f) => f.name === "payload-cms");
|
|
2081
|
+
if (hasPayload) out.collections = discoverPayloadCollections(cwd);
|
|
2082
|
+
out.adminComponents = discoverAdminComponents(cwd, out.collections.length > 0 ? out.collections : void 0);
|
|
2083
|
+
out.apiRoutes = scanApiRoutes(cwd);
|
|
2084
|
+
out.envVars = scanEnvVars(cwd);
|
|
2085
|
+
if (hasPayload && !out.adminPath) out.adminPath = "/admin";
|
|
2086
|
+
return out;
|
|
2087
|
+
}
|
|
2088
|
+
function detectDevServer(cwd, out) {
|
|
2089
|
+
try {
|
|
2090
|
+
const pkg = JSON.parse(fs13.readFileSync(path12.join(cwd, "package.json"), "utf-8"));
|
|
2091
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2092
|
+
const pm = fs13.existsSync(path12.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs13.existsSync(path12.join(cwd, "yarn.lock")) ? "yarn" : fs13.existsSync(path12.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
2093
|
+
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
2094
|
+
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
2095
|
+
else if (allDeps.vite) out.devPort = 5173;
|
|
2096
|
+
} catch {
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
function scanFrontendRoutes(cwd, out) {
|
|
2100
|
+
const appDirs = ["src/app", "app"];
|
|
2101
|
+
for (const appDir of appDirs) {
|
|
2102
|
+
const full = path12.join(cwd, appDir);
|
|
2103
|
+
if (!fs13.existsSync(full)) continue;
|
|
2104
|
+
walkFrontendRoutes(full, "", out);
|
|
2105
|
+
break;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
function walkFrontendRoutes(dir, prefix, out) {
|
|
2109
|
+
let entries;
|
|
2110
|
+
try {
|
|
2111
|
+
entries = fs13.readdirSync(dir, { withFileTypes: true });
|
|
2112
|
+
} catch {
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
const hasPage = entries.some((e) => e.isFile() && /^page\.(tsx?|jsx?)$/.test(e.name));
|
|
2116
|
+
if (hasPage) {
|
|
2117
|
+
const routePath = prefix || "/";
|
|
2118
|
+
const group = prefix.startsWith("/admin") ? "admin" : prefix.includes("/login") || prefix.includes("/signup") ? "auth" : prefix.includes("/api") ? "api" : "frontend";
|
|
2119
|
+
out.routes.push({ path: routePath, group });
|
|
2120
|
+
if (prefix.includes("/login") && !out.loginPage) out.loginPage = routePath;
|
|
2121
|
+
if (prefix.startsWith("/admin") && !out.adminPath) out.adminPath = prefix;
|
|
2122
|
+
}
|
|
2123
|
+
for (const entry of entries) {
|
|
2124
|
+
if (!entry.isDirectory()) continue;
|
|
2125
|
+
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
2126
|
+
let segment = entry.name;
|
|
2127
|
+
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
2128
|
+
walkFrontendRoutes(path12.join(dir, entry.name), prefix, out);
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
2132
|
+
segment = `:${segment.slice(2, -2)}?`;
|
|
2133
|
+
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
2134
|
+
segment = `:${segment.slice(1, -1)}`;
|
|
2135
|
+
}
|
|
2136
|
+
walkFrontendRoutes(path12.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
function detectAuthFiles(cwd, out) {
|
|
2140
|
+
const candidates = [
|
|
2141
|
+
"middleware.ts",
|
|
2142
|
+
"middleware.js",
|
|
2143
|
+
"src/middleware.ts",
|
|
2144
|
+
"src/middleware.js",
|
|
2145
|
+
"src/app/api/auth",
|
|
2146
|
+
"src/auth",
|
|
2147
|
+
"src/lib/auth",
|
|
2148
|
+
"auth.config.ts",
|
|
2149
|
+
"auth.ts",
|
|
2150
|
+
"src/app/api/oauth"
|
|
2151
|
+
];
|
|
2152
|
+
for (const c of candidates) {
|
|
2153
|
+
if (fs13.existsSync(path12.join(cwd, c))) out.authFiles.push(c);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
function detectRoles(cwd, out) {
|
|
2157
|
+
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
2158
|
+
for (const rp of rolePaths) {
|
|
2159
|
+
const dir = path12.join(cwd, rp);
|
|
2160
|
+
if (!fs13.existsSync(dir)) continue;
|
|
2161
|
+
let files;
|
|
2162
|
+
try {
|
|
2163
|
+
files = fs13.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
2164
|
+
} catch {
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
for (const f of files) {
|
|
2168
|
+
try {
|
|
2169
|
+
const content = fs13.readFileSync(path12.join(dir, f), "utf-8").slice(0, 5e3);
|
|
2170
|
+
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
2171
|
+
if (roleMatches) {
|
|
2172
|
+
for (const m of roleMatches) {
|
|
2173
|
+
const val = m.match(/['"](\w+)['"]/);
|
|
2174
|
+
if (val && !out.roles.includes(val[1])) out.roles.push(val[1]);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
const enumMatch = content.match(/(?:enum|type)\s+\w*[Rr]ole\w*\s*[={]([^}]+)/s);
|
|
2178
|
+
if (enumMatch) {
|
|
2179
|
+
const vals = enumMatch[1].match(/['"](\w+)['"]/g);
|
|
2180
|
+
if (vals) {
|
|
2181
|
+
for (const v of vals) {
|
|
2182
|
+
const clean = v.replace(/['"]/g, "");
|
|
2183
|
+
if (!out.roles.includes(clean)) out.roles.push(clean);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
} catch {
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
function serializeDiscoveryForLLM(d) {
|
|
2193
|
+
const sections = [];
|
|
2194
|
+
sections.push(`Dev server: ${d.devCommand || "pnpm dev"} at http://localhost:${d.devPort}`);
|
|
2195
|
+
if (d.loginPage) sections.push(`Login page: ${d.loginPage}`);
|
|
2196
|
+
if (d.adminPath) sections.push(`Admin panel: ${d.adminPath}`);
|
|
2197
|
+
if (d.roles.length > 0) sections.push(`Roles: ${d.roles.join(", ")}`);
|
|
2198
|
+
if (d.frameworks.length > 0) {
|
|
2199
|
+
sections.push(
|
|
2200
|
+
`
|
|
2201
|
+
Frameworks: ${d.frameworks.map((f) => `${f.name}${f.version ? ` (${f.version})` : ""}`).join(", ")}`
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
if (d.collections.length > 0) {
|
|
2205
|
+
sections.push("\nCollections (Payload CMS):");
|
|
2206
|
+
for (const col of d.collections.slice(0, 15)) {
|
|
2207
|
+
const fields = col.fields.slice(0, 10).join(", ");
|
|
2208
|
+
let line = `- ${col.slug}: fields=[${fields}]`;
|
|
2209
|
+
if (col.hasAdmin) line += " (has custom admin components)";
|
|
2210
|
+
line += ` \u2014 ${col.filePath}`;
|
|
2211
|
+
sections.push(line);
|
|
2212
|
+
}
|
|
2213
|
+
if (d.collections.length > 15) sections.push(`- ... and ${d.collections.length - 15} more collections`);
|
|
2214
|
+
}
|
|
2215
|
+
if (d.adminComponents.length > 0) {
|
|
2216
|
+
sections.push("\nCustom Admin Components:");
|
|
2217
|
+
for (const comp of d.adminComponents.slice(0, 10)) {
|
|
2218
|
+
let line = `- ${comp.name} (${comp.filePath})`;
|
|
2219
|
+
if (comp.usedInCollection) line += ` \u2192 used in "${comp.usedInCollection}" collection`;
|
|
2220
|
+
sections.push(line);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
if (d.apiRoutes.length > 0) {
|
|
2224
|
+
sections.push("\nAPI Routes:");
|
|
2225
|
+
for (const route of d.apiRoutes.slice(0, 20)) {
|
|
2226
|
+
sections.push(`- ${route.methods.join("/")} ${route.path} \u2014 ${route.filePath}`);
|
|
2227
|
+
}
|
|
2228
|
+
if (d.apiRoutes.length > 20) sections.push(`- ... and ${d.apiRoutes.length - 20} more routes`);
|
|
2229
|
+
}
|
|
2230
|
+
if (d.routes.length > 0) {
|
|
2231
|
+
sections.push("\nFrontend Routes:");
|
|
2232
|
+
for (const route of d.routes.slice(0, 30)) {
|
|
2233
|
+
sections.push(`- [${route.group}] ${route.path}`);
|
|
2234
|
+
}
|
|
2235
|
+
if (d.routes.length > 30) sections.push(`- ... and ${d.routes.length - 30} more routes`);
|
|
2236
|
+
}
|
|
2237
|
+
if (d.envVars.length > 0) sections.push(`
|
|
2238
|
+
Required env vars: ${d.envVars.join(", ")}`);
|
|
2239
|
+
let result = sections.join("\n");
|
|
2240
|
+
if (result.length > MAX_SERIALIZED_LENGTH) {
|
|
2241
|
+
const cutoff = result.lastIndexOf("\n", MAX_SERIALIZED_LENGTH - 20);
|
|
2242
|
+
result = result.slice(0, cutoff > 0 ? cutoff : MAX_SERIALIZED_LENGTH - 20) + "\n... (truncated)";
|
|
2243
|
+
}
|
|
2244
|
+
return result;
|
|
2245
|
+
}
|
|
2246
|
+
function generateQaGuideTemplate(d) {
|
|
2247
|
+
const lines = [];
|
|
2248
|
+
lines.push("# QA guide");
|
|
2249
|
+
lines.push("");
|
|
2250
|
+
lines.push("This file is read by `kody2 ui-review`. Fill in the credential placeholders");
|
|
2251
|
+
lines.push("below and commit \u2014 the agent uses them to log in to your preview deployment.");
|
|
2252
|
+
lines.push("");
|
|
2253
|
+
lines.push("## Test accounts");
|
|
2254
|
+
lines.push("");
|
|
2255
|
+
lines.push("<!-- Replace CHANGE_ME with real credentials for your preview environment.");
|
|
2256
|
+
lines.push(" Remove any role row you don't have an account for. -->");
|
|
2257
|
+
lines.push("");
|
|
2258
|
+
lines.push("| Role | Email | Password |");
|
|
2259
|
+
lines.push("|------|-------|----------|");
|
|
2260
|
+
if (d.roles.length > 0) {
|
|
2261
|
+
for (const role of d.roles) {
|
|
2262
|
+
lines.push(`| ${role} | CHANGE_ME | CHANGE_ME |`);
|
|
2263
|
+
}
|
|
2264
|
+
} else {
|
|
2265
|
+
lines.push("| admin | admin@example.com | CHANGE_ME |");
|
|
2266
|
+
lines.push("| user | user@example.com | CHANGE_ME |");
|
|
2267
|
+
}
|
|
2268
|
+
lines.push("");
|
|
2269
|
+
lines.push("## Login");
|
|
2270
|
+
lines.push("");
|
|
2271
|
+
lines.push(`- Login page: \`${d.loginPage ?? "/login"}\``);
|
|
2272
|
+
if (d.adminPath) lines.push(`- Admin panel: \`${d.adminPath}\``);
|
|
2273
|
+
lines.push("");
|
|
2274
|
+
lines.push("### Steps");
|
|
2275
|
+
lines.push(`1. Navigate to \`${d.loginPage ?? "/login"}\``);
|
|
2276
|
+
lines.push("2. Enter credentials from the table above");
|
|
2277
|
+
lines.push("3. Submit the login form");
|
|
2278
|
+
lines.push("4. Verify the redirect lands on the expected page");
|
|
2279
|
+
lines.push("");
|
|
2280
|
+
if (d.roles.length > 0) {
|
|
2281
|
+
lines.push("## Roles");
|
|
2282
|
+
lines.push("");
|
|
2283
|
+
for (const role of d.roles) lines.push(`- \`${role}\``);
|
|
2284
|
+
lines.push("");
|
|
2285
|
+
}
|
|
2286
|
+
if (d.routes.length > 0) {
|
|
2287
|
+
lines.push("## Key pages");
|
|
2288
|
+
lines.push("");
|
|
2289
|
+
const groups = {};
|
|
2290
|
+
for (const r of d.routes) {
|
|
2291
|
+
if (!groups[r.group]) groups[r.group] = [];
|
|
2292
|
+
groups[r.group].push(r.path);
|
|
2293
|
+
}
|
|
2294
|
+
for (const [group, routes] of Object.entries(groups)) {
|
|
2295
|
+
lines.push(`### ${group[0].toUpperCase()}${group.slice(1)}`);
|
|
2296
|
+
for (const r of routes.slice(0, 15).sort()) lines.push(`- \`${r}\``);
|
|
2297
|
+
if (routes.length > 15) lines.push(`- \u2026 and ${routes.length - 15} more`);
|
|
2298
|
+
lines.push("");
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
lines.push("## Notes for the reviewer");
|
|
2302
|
+
lines.push("");
|
|
2303
|
+
lines.push("<!-- Add any repo-specific quirks the UI-review agent should know:");
|
|
2304
|
+
lines.push(" seed data assumptions, feature flags, preview-only behaviors, etc. -->");
|
|
2305
|
+
lines.push("");
|
|
2306
|
+
return lines.join("\n");
|
|
2307
|
+
}
|
|
2308
|
+
var discoverQaContext = async (ctx) => {
|
|
2309
|
+
const discovery = runQaDiscovery(ctx.cwd);
|
|
2310
|
+
ctx.data.qaDiscovery = discovery;
|
|
2311
|
+
ctx.data.qaContext = serializeDiscoveryForLLM(discovery);
|
|
2312
|
+
};
|
|
2313
|
+
|
|
1800
2314
|
// src/scripts/dispatch.ts
|
|
1801
2315
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
1802
2316
|
var API_TIMEOUT_MS3 = 3e4;
|
|
@@ -2274,7 +2788,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
2274
2788
|
|
|
2275
2789
|
// src/gha.ts
|
|
2276
2790
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
2277
|
-
import * as
|
|
2791
|
+
import * as fs14 from "fs";
|
|
2278
2792
|
function getRunUrl() {
|
|
2279
2793
|
const server = process.env.GITHUB_SERVER_URL;
|
|
2280
2794
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -2285,10 +2799,10 @@ function getRunUrl() {
|
|
|
2285
2799
|
function reactToTriggerComment(cwd) {
|
|
2286
2800
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
2287
2801
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2288
|
-
if (!eventPath || !
|
|
2802
|
+
if (!eventPath || !fs14.existsSync(eventPath)) return;
|
|
2289
2803
|
let event = null;
|
|
2290
2804
|
try {
|
|
2291
|
-
event = JSON.parse(
|
|
2805
|
+
event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
|
|
2292
2806
|
} catch {
|
|
2293
2807
|
return;
|
|
2294
2808
|
}
|
|
@@ -2514,35 +3028,35 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
2514
3028
|
|
|
2515
3029
|
// src/scripts/initFlow.ts
|
|
2516
3030
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
2517
|
-
import * as
|
|
2518
|
-
import * as
|
|
3031
|
+
import * as fs17 from "fs";
|
|
3032
|
+
import * as path15 from "path";
|
|
2519
3033
|
|
|
2520
3034
|
// src/registry.ts
|
|
2521
|
-
import * as
|
|
2522
|
-
import * as
|
|
3035
|
+
import * as fs15 from "fs";
|
|
3036
|
+
import * as path13 from "path";
|
|
2523
3037
|
function getExecutablesRoot() {
|
|
2524
|
-
const here =
|
|
3038
|
+
const here = path13.dirname(new URL(import.meta.url).pathname);
|
|
2525
3039
|
const candidates = [
|
|
2526
|
-
|
|
3040
|
+
path13.join(here, "executables"),
|
|
2527
3041
|
// dev: src/
|
|
2528
|
-
|
|
3042
|
+
path13.join(here, "..", "executables"),
|
|
2529
3043
|
// built: dist/bin → dist/executables
|
|
2530
|
-
|
|
3044
|
+
path13.join(here, "..", "src", "executables")
|
|
2531
3045
|
// fallback
|
|
2532
3046
|
];
|
|
2533
3047
|
for (const c of candidates) {
|
|
2534
|
-
if (
|
|
3048
|
+
if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
|
|
2535
3049
|
}
|
|
2536
3050
|
return candidates[0];
|
|
2537
3051
|
}
|
|
2538
3052
|
function listExecutables(root = getExecutablesRoot()) {
|
|
2539
|
-
if (!
|
|
2540
|
-
const entries =
|
|
3053
|
+
if (!fs15.existsSync(root)) return [];
|
|
3054
|
+
const entries = fs15.readdirSync(root, { withFileTypes: true });
|
|
2541
3055
|
const out = [];
|
|
2542
3056
|
for (const ent of entries) {
|
|
2543
3057
|
if (!ent.isDirectory()) continue;
|
|
2544
|
-
const profilePath =
|
|
2545
|
-
if (
|
|
3058
|
+
const profilePath = path13.join(root, ent.name, "profile.json");
|
|
3059
|
+
if (fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile()) {
|
|
2546
3060
|
out.push({ name: ent.name, profilePath });
|
|
2547
3061
|
}
|
|
2548
3062
|
}
|
|
@@ -2550,8 +3064,8 @@ function listExecutables(root = getExecutablesRoot()) {
|
|
|
2550
3064
|
}
|
|
2551
3065
|
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
2552
3066
|
if (!isSafeName(name)) return false;
|
|
2553
|
-
const profilePath =
|
|
2554
|
-
return
|
|
3067
|
+
const profilePath = path13.join(root, name, "profile.json");
|
|
3068
|
+
return fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile();
|
|
2555
3069
|
}
|
|
2556
3070
|
function isSafeName(name) {
|
|
2557
3071
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
@@ -2578,11 +3092,31 @@ function parseGenericFlags(argv) {
|
|
|
2578
3092
|
return args;
|
|
2579
3093
|
}
|
|
2580
3094
|
|
|
3095
|
+
// src/scripts/loadQaGuide.ts
|
|
3096
|
+
import * as fs16 from "fs";
|
|
3097
|
+
import * as path14 from "path";
|
|
3098
|
+
var QA_GUIDE_REL_PATH = ".kody2/qa-guide.md";
|
|
3099
|
+
var loadQaGuide = async (ctx) => {
|
|
3100
|
+
const full = path14.join(ctx.cwd, QA_GUIDE_REL_PATH);
|
|
3101
|
+
if (!fs16.existsSync(full)) {
|
|
3102
|
+
ctx.data.qaGuide = "";
|
|
3103
|
+
ctx.data.qaGuidePath = "";
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
try {
|
|
3107
|
+
ctx.data.qaGuide = fs16.readFileSync(full, "utf-8");
|
|
3108
|
+
ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
|
|
3109
|
+
} catch {
|
|
3110
|
+
ctx.data.qaGuide = "";
|
|
3111
|
+
ctx.data.qaGuidePath = "";
|
|
3112
|
+
}
|
|
3113
|
+
};
|
|
3114
|
+
|
|
2581
3115
|
// src/scripts/initFlow.ts
|
|
2582
3116
|
function detectPackageManager(cwd) {
|
|
2583
|
-
if (
|
|
2584
|
-
if (
|
|
2585
|
-
if (
|
|
3117
|
+
if (fs17.existsSync(path15.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
3118
|
+
if (fs17.existsSync(path15.join(cwd, "yarn.lock"))) return "yarn";
|
|
3119
|
+
if (fs17.existsSync(path15.join(cwd, "bun.lockb"))) return "bun";
|
|
2586
3120
|
return "npm";
|
|
2587
3121
|
}
|
|
2588
3122
|
function qualityCommandsFor(pm) {
|
|
@@ -2703,24 +3237,36 @@ function performInit(cwd, force) {
|
|
|
2703
3237
|
const pm = detectPackageManager(cwd);
|
|
2704
3238
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
2705
3239
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
2706
|
-
const configPath =
|
|
2707
|
-
if (
|
|
3240
|
+
const configPath = path15.join(cwd, "kody.config.json");
|
|
3241
|
+
if (fs17.existsSync(configPath) && !force) {
|
|
2708
3242
|
skipped.push("kody.config.json");
|
|
2709
3243
|
} else {
|
|
2710
3244
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
2711
|
-
|
|
3245
|
+
fs17.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
2712
3246
|
`);
|
|
2713
3247
|
wrote.push("kody.config.json");
|
|
2714
3248
|
}
|
|
2715
|
-
const workflowDir =
|
|
2716
|
-
const workflowPath =
|
|
2717
|
-
if (
|
|
3249
|
+
const workflowDir = path15.join(cwd, ".github", "workflows");
|
|
3250
|
+
const workflowPath = path15.join(workflowDir, "kody2.yml");
|
|
3251
|
+
if (fs17.existsSync(workflowPath) && !force) {
|
|
2718
3252
|
skipped.push(".github/workflows/kody2.yml");
|
|
2719
3253
|
} else {
|
|
2720
|
-
|
|
2721
|
-
|
|
3254
|
+
fs17.mkdirSync(workflowDir, { recursive: true });
|
|
3255
|
+
fs17.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
2722
3256
|
wrote.push(".github/workflows/kody2.yml");
|
|
2723
3257
|
}
|
|
3258
|
+
const hasUi = fs17.existsSync(path15.join(cwd, "src/app")) || fs17.existsSync(path15.join(cwd, "app")) || fs17.existsSync(path15.join(cwd, "pages"));
|
|
3259
|
+
if (hasUi) {
|
|
3260
|
+
const qaGuidePath = path15.join(cwd, QA_GUIDE_REL_PATH);
|
|
3261
|
+
if (fs17.existsSync(qaGuidePath) && !force) {
|
|
3262
|
+
skipped.push(QA_GUIDE_REL_PATH);
|
|
3263
|
+
} else {
|
|
3264
|
+
fs17.mkdirSync(path15.dirname(qaGuidePath), { recursive: true });
|
|
3265
|
+
const discovery = runQaDiscovery(cwd);
|
|
3266
|
+
fs17.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
|
|
3267
|
+
wrote.push(QA_GUIDE_REL_PATH);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
2724
3270
|
for (const exe of listExecutables()) {
|
|
2725
3271
|
let profile;
|
|
2726
3272
|
try {
|
|
@@ -2729,12 +3275,12 @@ function performInit(cwd, force) {
|
|
|
2729
3275
|
continue;
|
|
2730
3276
|
}
|
|
2731
3277
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
2732
|
-
const target =
|
|
2733
|
-
if (
|
|
3278
|
+
const target = path15.join(workflowDir, `kody2-${exe.name}.yml`);
|
|
3279
|
+
if (fs17.existsSync(target) && !force) {
|
|
2734
3280
|
skipped.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2735
3281
|
continue;
|
|
2736
3282
|
}
|
|
2737
|
-
|
|
3283
|
+
fs17.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
2738
3284
|
wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2739
3285
|
}
|
|
2740
3286
|
return { wrote, skipped };
|
|
@@ -2931,6 +3477,22 @@ function readDottedString(source, dotted) {
|
|
|
2931
3477
|
return String(cur);
|
|
2932
3478
|
}
|
|
2933
3479
|
|
|
3480
|
+
// src/scripts/persistFlowState.ts
|
|
3481
|
+
var persistFlowState = async (ctx) => {
|
|
3482
|
+
const state = ctx.data.taskState;
|
|
3483
|
+
if (!state) return;
|
|
3484
|
+
const issueNumber = ctx.args.issue ?? state.flow?.issueNumber;
|
|
3485
|
+
if (!issueNumber) return;
|
|
3486
|
+
try {
|
|
3487
|
+
writeTaskState("issue", issueNumber, state, ctx.cwd);
|
|
3488
|
+
} catch (err) {
|
|
3489
|
+
process.stderr.write(
|
|
3490
|
+
`[kody2 persistFlowState] failed to write state on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
3491
|
+
`
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
};
|
|
3495
|
+
|
|
2934
3496
|
// src/scripts/postIssueComment.ts
|
|
2935
3497
|
var postIssueComment2 = async (ctx) => {
|
|
2936
3498
|
if (ctx.skipAgent && ctx.output.exitCode !== void 0) return;
|
|
@@ -3093,8 +3655,8 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
3093
3655
|
|
|
3094
3656
|
// src/scripts/releaseFlow.ts
|
|
3095
3657
|
import { execFileSync as execFileSync14, spawnSync } from "child_process";
|
|
3096
|
-
import * as
|
|
3097
|
-
import * as
|
|
3658
|
+
import * as fs18 from "fs";
|
|
3659
|
+
import * as path16 from "path";
|
|
3098
3660
|
function bumpVersion(current, bump) {
|
|
3099
3661
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
3100
3662
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -3110,12 +3672,12 @@ function bumpVersion(current, bump) {
|
|
|
3110
3672
|
return `${major}.${minor}.${patch}`;
|
|
3111
3673
|
}
|
|
3112
3674
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
3113
|
-
const abs =
|
|
3114
|
-
if (!
|
|
3115
|
-
const content =
|
|
3675
|
+
const abs = path16.join(cwd, file);
|
|
3676
|
+
if (!fs18.existsSync(abs)) return false;
|
|
3677
|
+
const content = fs18.readFileSync(abs, "utf-8");
|
|
3116
3678
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
3117
3679
|
if (updated === content) return false;
|
|
3118
|
-
|
|
3680
|
+
fs18.writeFileSync(abs, updated);
|
|
3119
3681
|
return true;
|
|
3120
3682
|
}
|
|
3121
3683
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
@@ -3163,19 +3725,19 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
3163
3725
|
return parts.join("\n");
|
|
3164
3726
|
}
|
|
3165
3727
|
function prependChangelog(cwd, entry) {
|
|
3166
|
-
const p =
|
|
3728
|
+
const p = path16.join(cwd, "CHANGELOG.md");
|
|
3167
3729
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
3168
|
-
if (
|
|
3169
|
-
const prior =
|
|
3730
|
+
if (fs18.existsSync(p)) {
|
|
3731
|
+
const prior = fs18.readFileSync(p, "utf-8");
|
|
3170
3732
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
3171
3733
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
3172
|
-
|
|
3734
|
+
fs18.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
3173
3735
|
${entry}${prior.slice(idx + 1)}`);
|
|
3174
3736
|
} else {
|
|
3175
|
-
|
|
3737
|
+
fs18.writeFileSync(p, `${header}${entry}${prior}`);
|
|
3176
3738
|
}
|
|
3177
3739
|
} else {
|
|
3178
|
-
|
|
3740
|
+
fs18.writeFileSync(p, `${header}${entry}`);
|
|
3179
3741
|
}
|
|
3180
3742
|
}
|
|
3181
3743
|
function git3(args, cwd, timeout = 6e4) {
|
|
@@ -3226,13 +3788,13 @@ var releaseFlow = async (ctx) => {
|
|
|
3226
3788
|
};
|
|
3227
3789
|
async function runPrepare(args) {
|
|
3228
3790
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
3229
|
-
const pkgPath =
|
|
3230
|
-
if (!
|
|
3791
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
3792
|
+
if (!fs18.existsSync(pkgPath)) {
|
|
3231
3793
|
ctx.output.exitCode = 99;
|
|
3232
3794
|
ctx.output.reason = "release prepare: package.json not found";
|
|
3233
3795
|
return;
|
|
3234
3796
|
}
|
|
3235
|
-
const pkg = JSON.parse(
|
|
3797
|
+
const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf-8"));
|
|
3236
3798
|
if (typeof pkg.version !== "string") {
|
|
3237
3799
|
ctx.output.exitCode = 99;
|
|
3238
3800
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -3303,8 +3865,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
3303
3865
|
}
|
|
3304
3866
|
async function runFinalize(args) {
|
|
3305
3867
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
3306
|
-
const pkgPath =
|
|
3307
|
-
const pkg = JSON.parse(
|
|
3868
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
3869
|
+
const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf-8"));
|
|
3308
3870
|
if (typeof pkg.version !== "string") {
|
|
3309
3871
|
ctx.output.exitCode = 99;
|
|
3310
3872
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -3571,6 +4133,25 @@ function tryPostPr3(prNumber, body, cwd) {
|
|
|
3571
4133
|
}
|
|
3572
4134
|
}
|
|
3573
4135
|
|
|
4136
|
+
// src/scripts/resolvePreviewUrl.ts
|
|
4137
|
+
var DEFAULT_PREVIEW_URL = "http://localhost:3000";
|
|
4138
|
+
var resolvePreviewUrl = async (ctx) => {
|
|
4139
|
+
const fromFlag = typeof ctx.args.previewUrl === "string" ? ctx.args.previewUrl.trim() : "";
|
|
4140
|
+
if (fromFlag.length > 0) {
|
|
4141
|
+
ctx.data.previewUrl = fromFlag;
|
|
4142
|
+
ctx.data.previewUrlSource = "flag";
|
|
4143
|
+
return;
|
|
4144
|
+
}
|
|
4145
|
+
const fromEnv = (process.env.PREVIEW_URL ?? "").trim();
|
|
4146
|
+
if (fromEnv.length > 0) {
|
|
4147
|
+
ctx.data.previewUrl = fromEnv;
|
|
4148
|
+
ctx.data.previewUrlSource = "env";
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
ctx.data.previewUrl = DEFAULT_PREVIEW_URL;
|
|
4152
|
+
ctx.data.previewUrlSource = "default";
|
|
4153
|
+
};
|
|
4154
|
+
|
|
3574
4155
|
// src/scripts/reviewFlow.ts
|
|
3575
4156
|
var reviewFlow = async (ctx) => {
|
|
3576
4157
|
const prNumber = ctx.args.pr;
|
|
@@ -3955,7 +4536,7 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
3955
4536
|
};
|
|
3956
4537
|
|
|
3957
4538
|
// src/scripts/writeRunSummary.ts
|
|
3958
|
-
import * as
|
|
4539
|
+
import * as fs19 from "fs";
|
|
3959
4540
|
var writeRunSummary = async (ctx, profile) => {
|
|
3960
4541
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
3961
4542
|
if (!summaryPath) return;
|
|
@@ -3977,7 +4558,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
3977
4558
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
3978
4559
|
lines.push("");
|
|
3979
4560
|
try {
|
|
3980
|
-
|
|
4561
|
+
fs19.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
3981
4562
|
`);
|
|
3982
4563
|
} catch {
|
|
3983
4564
|
}
|
|
@@ -3998,8 +4579,11 @@ var preflightScripts = {
|
|
|
3998
4579
|
loadIssueContext,
|
|
3999
4580
|
loadConventions,
|
|
4000
4581
|
loadCoverageRules,
|
|
4582
|
+
loadQaGuide,
|
|
4001
4583
|
buildSyntheticPlugin,
|
|
4002
4584
|
resolveArtifacts,
|
|
4585
|
+
discoverQaContext,
|
|
4586
|
+
resolvePreviewUrl,
|
|
4003
4587
|
composePrompt,
|
|
4004
4588
|
skipAgent
|
|
4005
4589
|
};
|
|
@@ -4022,7 +4606,8 @@ var postflightScripts = {
|
|
|
4022
4606
|
startFlow,
|
|
4023
4607
|
dispatch,
|
|
4024
4608
|
finishFlow,
|
|
4025
|
-
advanceFlow
|
|
4609
|
+
advanceFlow,
|
|
4610
|
+
persistFlowState
|
|
4026
4611
|
};
|
|
4027
4612
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
4028
4613
|
...Object.keys(preflightScripts),
|
|
@@ -4131,9 +4716,9 @@ async function runExecutable(profileName, input) {
|
|
|
4131
4716
|
data: {},
|
|
4132
4717
|
output: { exitCode: 0 }
|
|
4133
4718
|
};
|
|
4134
|
-
const ndjsonDir =
|
|
4719
|
+
const ndjsonDir = path17.join(input.cwd, ".kody2");
|
|
4135
4720
|
const invokeAgent = async (prompt) => {
|
|
4136
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
4721
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path17.isAbsolute(p) ? p : path17.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
4137
4722
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
4138
4723
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
4139
4724
|
return runAgent({
|
|
@@ -4200,17 +4785,17 @@ async function runExecutable(profileName, input) {
|
|
|
4200
4785
|
}
|
|
4201
4786
|
}
|
|
4202
4787
|
function resolveProfilePath(profileName) {
|
|
4203
|
-
const here =
|
|
4788
|
+
const here = path17.dirname(new URL(import.meta.url).pathname);
|
|
4204
4789
|
const candidates = [
|
|
4205
|
-
|
|
4790
|
+
path17.join(here, "executables", profileName, "profile.json"),
|
|
4206
4791
|
// same-dir sibling (dev)
|
|
4207
|
-
|
|
4792
|
+
path17.join(here, "..", "executables", profileName, "profile.json"),
|
|
4208
4793
|
// up one (prod: dist/bin → dist/executables)
|
|
4209
|
-
|
|
4794
|
+
path17.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
4210
4795
|
// fallback
|
|
4211
4796
|
];
|
|
4212
4797
|
for (const c of candidates) {
|
|
4213
|
-
if (
|
|
4798
|
+
if (fs20.existsSync(c)) return c;
|
|
4214
4799
|
}
|
|
4215
4800
|
return candidates[0];
|
|
4216
4801
|
}
|
|
@@ -4386,9 +4971,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
4386
4971
|
return token;
|
|
4387
4972
|
}
|
|
4388
4973
|
function detectPackageManager2(cwd) {
|
|
4389
|
-
if (
|
|
4390
|
-
if (
|
|
4391
|
-
if (
|
|
4974
|
+
if (fs21.existsSync(path18.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
4975
|
+
if (fs21.existsSync(path18.join(cwd, "yarn.lock"))) return "yarn";
|
|
4976
|
+
if (fs21.existsSync(path18.join(cwd, "bun.lockb"))) return "bun";
|
|
4392
4977
|
return "npm";
|
|
4393
4978
|
}
|
|
4394
4979
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -4468,11 +5053,11 @@ function configureGitIdentity(cwd) {
|
|
|
4468
5053
|
}
|
|
4469
5054
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
4470
5055
|
if (!issueNumber) return;
|
|
4471
|
-
const logPath =
|
|
5056
|
+
const logPath = path18.join(cwd, ".kody2", "last-run.jsonl");
|
|
4472
5057
|
let tail = "";
|
|
4473
5058
|
try {
|
|
4474
|
-
if (
|
|
4475
|
-
const content =
|
|
5059
|
+
if (fs21.existsSync(logPath)) {
|
|
5060
|
+
const content = fs21.readFileSync(logPath, "utf-8");
|
|
4476
5061
|
tail = content.slice(-3e3);
|
|
4477
5062
|
}
|
|
4478
5063
|
} catch {
|
|
@@ -4497,7 +5082,7 @@ async function runCi(argv) {
|
|
|
4497
5082
|
return 0;
|
|
4498
5083
|
}
|
|
4499
5084
|
const args = parseCiArgs(argv);
|
|
4500
|
-
const cwd = args.cwd ?
|
|
5085
|
+
const cwd = args.cwd ? path18.resolve(args.cwd) : process.cwd();
|
|
4501
5086
|
let earlyConfig;
|
|
4502
5087
|
try {
|
|
4503
5088
|
earlyConfig = loadConfig(cwd);
|
|
@@ -4630,9 +5215,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
4630
5215
|
return result;
|
|
4631
5216
|
}
|
|
4632
5217
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
4633
|
-
const sessionFile =
|
|
4634
|
-
const eventsFile =
|
|
4635
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
5218
|
+
const sessionFile = path19.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
5219
|
+
const eventsFile = path19.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
5220
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs22.existsSync(path19.join(cwd, p)));
|
|
4636
5221
|
if (paths.length === 0) return;
|
|
4637
5222
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
4638
5223
|
try {
|
|
@@ -4670,7 +5255,7 @@ async function runChat(argv) {
|
|
|
4670
5255
|
${CHAT_HELP}`);
|
|
4671
5256
|
return 64;
|
|
4672
5257
|
}
|
|
4673
|
-
const cwd = args.cwd ?
|
|
5258
|
+
const cwd = args.cwd ? path19.resolve(args.cwd) : process.cwd();
|
|
4674
5259
|
const sessionId = args.sessionId;
|
|
4675
5260
|
const unpackedSecrets = unpackAllSecrets();
|
|
4676
5261
|
if (unpackedSecrets > 0) {
|