@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 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.46",
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 fs19 from "fs";
55
- import * as path16 from "path";
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 fs18 from "fs";
533
- import * as path15 from "path";
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
- return { executable: "orchestrator", cliArgs: { issue: targetNum }, target: targetNum };
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 fs17 from "fs";
621
- import * as path14 from "path";
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(STATE_BEGIN);
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 fs12 from "fs";
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 || !fs12.existsSync(eventPath)) return;
2802
+ if (!eventPath || !fs14.existsSync(eventPath)) return;
2289
2803
  let event = null;
2290
2804
  try {
2291
- event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
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 fs14 from "fs";
2518
- import * as path12 from "path";
3031
+ import * as fs17 from "fs";
3032
+ import * as path15 from "path";
2519
3033
 
2520
3034
  // src/registry.ts
2521
- import * as fs13 from "fs";
2522
- import * as path11 from "path";
3035
+ import * as fs15 from "fs";
3036
+ import * as path13 from "path";
2523
3037
  function getExecutablesRoot() {
2524
- const here = path11.dirname(new URL(import.meta.url).pathname);
3038
+ const here = path13.dirname(new URL(import.meta.url).pathname);
2525
3039
  const candidates = [
2526
- path11.join(here, "executables"),
3040
+ path13.join(here, "executables"),
2527
3041
  // dev: src/
2528
- path11.join(here, "..", "executables"),
3042
+ path13.join(here, "..", "executables"),
2529
3043
  // built: dist/bin → dist/executables
2530
- path11.join(here, "..", "src", "executables")
3044
+ path13.join(here, "..", "src", "executables")
2531
3045
  // fallback
2532
3046
  ];
2533
3047
  for (const c of candidates) {
2534
- if (fs13.existsSync(c) && fs13.statSync(c).isDirectory()) return c;
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 (!fs13.existsSync(root)) return [];
2540
- const entries = fs13.readdirSync(root, { withFileTypes: true });
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 = path11.join(root, ent.name, "profile.json");
2545
- if (fs13.existsSync(profilePath) && fs13.statSync(profilePath).isFile()) {
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 = path11.join(root, name, "profile.json");
2554
- return fs13.existsSync(profilePath) && fs13.statSync(profilePath).isFile();
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 (fs14.existsSync(path12.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
2584
- if (fs14.existsSync(path12.join(cwd, "yarn.lock"))) return "yarn";
2585
- if (fs14.existsSync(path12.join(cwd, "bun.lockb"))) return "bun";
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 = path12.join(cwd, "kody.config.json");
2707
- if (fs14.existsSync(configPath) && !force) {
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
- fs14.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
3245
+ fs17.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
2712
3246
  `);
2713
3247
  wrote.push("kody.config.json");
2714
3248
  }
2715
- const workflowDir = path12.join(cwd, ".github", "workflows");
2716
- const workflowPath = path12.join(workflowDir, "kody2.yml");
2717
- if (fs14.existsSync(workflowPath) && !force) {
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
- fs14.mkdirSync(workflowDir, { recursive: true });
2721
- fs14.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
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 = path12.join(workflowDir, `kody2-${exe.name}.yml`);
2733
- if (fs14.existsSync(target) && !force) {
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
- fs14.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
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 fs15 from "fs";
3097
- import * as path13 from "path";
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 = path13.join(cwd, file);
3114
- if (!fs15.existsSync(abs)) return false;
3115
- const content = fs15.readFileSync(abs, "utf-8");
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
- fs15.writeFileSync(abs, updated);
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 = path13.join(cwd, "CHANGELOG.md");
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 (fs15.existsSync(p)) {
3169
- const prior = fs15.readFileSync(p, "utf-8");
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
- fs15.writeFileSync(p, `${prior.slice(0, idx + 1)}
3734
+ fs18.writeFileSync(p, `${prior.slice(0, idx + 1)}
3173
3735
  ${entry}${prior.slice(idx + 1)}`);
3174
3736
  } else {
3175
- fs15.writeFileSync(p, `${header}${entry}${prior}`);
3737
+ fs18.writeFileSync(p, `${header}${entry}${prior}`);
3176
3738
  }
3177
3739
  } else {
3178
- fs15.writeFileSync(p, `${header}${entry}`);
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 = path13.join(cwd, "package.json");
3230
- if (!fs15.existsSync(pkgPath)) {
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(fs15.readFileSync(pkgPath, "utf-8"));
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 = path13.join(cwd, "package.json");
3307
- const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
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 fs16 from "fs";
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
- fs16.appendFileSync(summaryPath, `${lines.join("\n")}
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 = path14.join(input.cwd, ".kody2");
4719
+ const ndjsonDir = path17.join(input.cwd, ".kody2");
4135
4720
  const invokeAgent = async (prompt) => {
4136
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path14.isAbsolute(p) ? p : path14.resolve(profile.dir, p)).filter((p) => p.length > 0);
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 = path14.dirname(new URL(import.meta.url).pathname);
4788
+ const here = path17.dirname(new URL(import.meta.url).pathname);
4204
4789
  const candidates = [
4205
- path14.join(here, "executables", profileName, "profile.json"),
4790
+ path17.join(here, "executables", profileName, "profile.json"),
4206
4791
  // same-dir sibling (dev)
4207
- path14.join(here, "..", "executables", profileName, "profile.json"),
4792
+ path17.join(here, "..", "executables", profileName, "profile.json"),
4208
4793
  // up one (prod: dist/bin → dist/executables)
4209
- path14.join(here, "..", "src", "executables", profileName, "profile.json")
4794
+ path17.join(here, "..", "src", "executables", profileName, "profile.json")
4210
4795
  // fallback
4211
4796
  ];
4212
4797
  for (const c of candidates) {
4213
- if (fs17.existsSync(c)) return c;
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 (fs18.existsSync(path15.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
4390
- if (fs18.existsSync(path15.join(cwd, "yarn.lock"))) return "yarn";
4391
- if (fs18.existsSync(path15.join(cwd, "bun.lockb"))) return "bun";
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 = path15.join(cwd, ".kody2", "last-run.jsonl");
5056
+ const logPath = path18.join(cwd, ".kody2", "last-run.jsonl");
4472
5057
  let tail = "";
4473
5058
  try {
4474
- if (fs18.existsSync(logPath)) {
4475
- const content = fs18.readFileSync(logPath, "utf-8");
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 ? path15.resolve(args.cwd) : process.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 = path16.relative(cwd, sessionFilePath(cwd, sessionId));
4634
- const eventsFile = path16.relative(cwd, eventsFilePath(cwd, sessionId));
4635
- const paths = [sessionFile, eventsFile].filter((p) => fs19.existsSync(path16.join(cwd, 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 ? path16.resolve(args.cwd) : process.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) {