@autonoma-ai/planner 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/index.js +790 -390
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -199,7 +199,7 @@ function trackError(error, properties = {}, handled = true) {
|
|
|
199
199
|
}
|
|
200
200
|
async function flushAnalytics(timeoutMs = 1500) {
|
|
201
201
|
if (pending.size === 0) return;
|
|
202
|
-
await Promise.race([Promise.allSettled([...pending]), new Promise((
|
|
202
|
+
await Promise.race([Promise.allSettled([...pending]), new Promise((resolve6) => setTimeout(resolve6, timeoutMs))]);
|
|
203
203
|
}
|
|
204
204
|
var AUTONOMA_HOME2, DEVICE_ID_PATH, POSTHOG_PUBLIC_KEY, DEFAULT_HOST, RUN_ID, cachedDeviceId, enabled, pending;
|
|
205
205
|
var init_analytics = __esm({
|
|
@@ -288,7 +288,7 @@ var init_context = __esm({
|
|
|
288
288
|
// src/core/errors.ts
|
|
289
289
|
import { APICallError, RetryError, LoadAPIKeyError, InvalidPromptError, NoSuchModelError } from "ai";
|
|
290
290
|
function sleep(ms) {
|
|
291
|
-
return new Promise((
|
|
291
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
292
292
|
}
|
|
293
293
|
function isUserCancellation(err) {
|
|
294
294
|
return err instanceof Error && /\bcancell?ed\b/i.test(err.message);
|
|
@@ -492,6 +492,151 @@ var init_notify = __esm({
|
|
|
492
492
|
}
|
|
493
493
|
});
|
|
494
494
|
|
|
495
|
+
// src/core/project-map.ts
|
|
496
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
497
|
+
import { join as join8 } from "path";
|
|
498
|
+
import { z as z2 } from "zod";
|
|
499
|
+
async function saveProjectMap(outputDir, map) {
|
|
500
|
+
await writeFile3(join8(outputDir, PROJECT_MAP_FILE), JSON.stringify(map, null, 2), "utf-8");
|
|
501
|
+
}
|
|
502
|
+
async function loadProjectMap(outputDir) {
|
|
503
|
+
const path3 = join8(outputDir, PROJECT_MAP_FILE);
|
|
504
|
+
try {
|
|
505
|
+
const raw = await readFile3(path3, "utf-8");
|
|
506
|
+
const parsed = ProjectMapSchema.safeParse(JSON.parse(raw));
|
|
507
|
+
if (parsed.success) return parsed.data;
|
|
508
|
+
debugLog("project-map.json failed schema validation, ignoring it", { path: path3, issues: parsed.error.issues });
|
|
509
|
+
return void 0;
|
|
510
|
+
} catch (err) {
|
|
511
|
+
const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
512
|
+
if (!isMissingFile) debugLog("Failed to read project-map.json, ignoring it", { path: path3, err });
|
|
513
|
+
return void 0;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function renderProjectMap(map) {
|
|
517
|
+
const section = (title, rows, fmt) => {
|
|
518
|
+
if (rows.length === 0) return `${title}: (none)`;
|
|
519
|
+
return `${title}:
|
|
520
|
+
` + rows.map((r) => ` - ${fmt(r)}`).join("\n");
|
|
521
|
+
};
|
|
522
|
+
return [
|
|
523
|
+
section("Frontend(s)", map.frontends, (f) => {
|
|
524
|
+
const deps = f.dependsOn.length > 0 ? ` needs: ${f.dependsOn.join(", ")}` : "";
|
|
525
|
+
return `${f.path} [${f.framework}]${deps} - ${f.why}`;
|
|
526
|
+
}),
|
|
527
|
+
section("Backend(s)", map.backends, (b) => {
|
|
528
|
+
const dl = b.dataLayer != null ? ` data: ${b.dataLayer.kind}${b.dataLayer.schemaPath != null ? ` @ ${b.dataLayer.schemaPath}` : ""}` : "";
|
|
529
|
+
return `${b.path} [${b.language}/${b.framework}]${dl} - ${b.why}`;
|
|
530
|
+
}),
|
|
531
|
+
section("Ignoring", map.ignore, (i) => `${i.path} - ${i.why}`)
|
|
532
|
+
].join("\n\n");
|
|
533
|
+
}
|
|
534
|
+
function formatFrontendScope(map) {
|
|
535
|
+
if (map.frontends.length === 0) return void 0;
|
|
536
|
+
const fronts = map.frontends.map((f) => f.path).join(", ");
|
|
537
|
+
const ignore = map.ignore.map((i) => i.path);
|
|
538
|
+
const ignoreLine = ignore.length > 0 ? ` Do NOT spend time in these irrelevant directories: ${ignore.join(", ")}.` : "";
|
|
539
|
+
return `The project has already been mapped. The frontend surface to focus on is: ${fronts}. Confine your exploration to that surface.${ignoreLine}`;
|
|
540
|
+
}
|
|
541
|
+
function relatedPath(a, b) {
|
|
542
|
+
return a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
|
|
543
|
+
}
|
|
544
|
+
function matchMapPath(requested, candidates) {
|
|
545
|
+
if (candidates.includes(requested)) return requested;
|
|
546
|
+
return candidates.find((c) => relatedPath(c, requested));
|
|
547
|
+
}
|
|
548
|
+
function matchBackend(requested, backends) {
|
|
549
|
+
const byPath = backends.find((b) => relatedPath(b.path, requested));
|
|
550
|
+
if (byPath != null) return byPath.path;
|
|
551
|
+
const bySchema = backends.find(
|
|
552
|
+
(b) => b.dataLayer?.schemaPath != null && relatedPath(b.dataLayer.schemaPath, requested)
|
|
553
|
+
);
|
|
554
|
+
return bySchema?.path;
|
|
555
|
+
}
|
|
556
|
+
function resolveSelection(map, requested) {
|
|
557
|
+
const frontendPaths = map.frontends.map((f) => f.path);
|
|
558
|
+
return {
|
|
559
|
+
frontend: matchMapPath(requested.frontend, frontendPaths) ?? requested.frontend,
|
|
560
|
+
backends: requested.backends.map((b) => matchBackend(b, map.backends) ?? b)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function defaultBackendsFor(map, frontendPath) {
|
|
564
|
+
const declared = map.frontends.find((f) => f.path === frontendPath)?.dependsOn ?? [];
|
|
565
|
+
const resolved = declared.map((dep) => matchBackend(dep, map.backends)).filter((p10) => p10 != null);
|
|
566
|
+
return [...new Set(resolved)];
|
|
567
|
+
}
|
|
568
|
+
function pickDefaultSelection(map) {
|
|
569
|
+
if (map.frontends.length !== 1) return void 0;
|
|
570
|
+
const [only] = map.frontends;
|
|
571
|
+
if (only == null) return void 0;
|
|
572
|
+
return { frontend: only.path, backends: defaultBackendsFor(map, only.path) };
|
|
573
|
+
}
|
|
574
|
+
function applySelection(map, selection) {
|
|
575
|
+
const frontend = map.frontends.find((f) => f.path === selection.frontend);
|
|
576
|
+
if (frontend == null) {
|
|
577
|
+
const options = map.frontends.map((f) => f.path).join(", ") || "(none)";
|
|
578
|
+
throw new Error(`Selected frontend "${selection.frontend}" is not in the map. Candidates: ${options}`);
|
|
579
|
+
}
|
|
580
|
+
const chosenBackends = map.backends.filter((b) => selection.backends.includes(b.path));
|
|
581
|
+
const missing = selection.backends.filter((p10) => !map.backends.some((b) => b.path === p10));
|
|
582
|
+
if (missing.length > 0) {
|
|
583
|
+
const options = map.backends.map((b) => b.path).join(", ") || "(none)";
|
|
584
|
+
throw new Error(`Selected backend(s) not in the map: ${missing.join(", ")}. Candidates: ${options}`);
|
|
585
|
+
}
|
|
586
|
+
const droppedFrontends = map.frontends.filter((f) => f.path !== selection.frontend).map((f) => ({ path: f.path, why: "Another frontend in the monorepo; not the app under test." }));
|
|
587
|
+
const droppedBackends = map.backends.filter((b) => !selection.backends.includes(b.path)).map((b) => ({ path: b.path, why: "Backend not required by the selected frontend." }));
|
|
588
|
+
return {
|
|
589
|
+
frontends: [frontend],
|
|
590
|
+
backends: chosenBackends,
|
|
591
|
+
ignore: [...map.ignore, ...droppedFrontends, ...droppedBackends]
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function formatBackendScope(map) {
|
|
595
|
+
if (map.backends.length === 0) return void 0;
|
|
596
|
+
const backs = map.backends.map((b) => b.dataLayer?.schemaPath != null ? `${b.path} (models @ ${b.dataLayer.schemaPath})` : b.path).join(", ");
|
|
597
|
+
const ignore = map.ignore.map((i) => i.path);
|
|
598
|
+
const ignoreLine = ignore.length > 0 ? ` Ignore these irrelevant directories: ${ignore.join(", ")}.` : "";
|
|
599
|
+
return `The project has already been mapped. The backend(s)/data layer(s) that own the models to seed are: ${backs}. Scope your data-model work to those backend(s).${ignoreLine}`;
|
|
600
|
+
}
|
|
601
|
+
var FrontendEntry, BackendEntry, IgnoreEntry, ProjectMapSchema, PROJECT_MAP_FILE;
|
|
602
|
+
var init_project_map = __esm({
|
|
603
|
+
"src/core/project-map.ts"() {
|
|
604
|
+
"use strict";
|
|
605
|
+
init_esm_shims();
|
|
606
|
+
init_debug();
|
|
607
|
+
FrontendEntry = z2.object({
|
|
608
|
+
path: z2.string().min(1).describe("Repo-relative path to the frontend app/directory (the UI surface)."),
|
|
609
|
+
framework: z2.string().describe("Detected UI framework/stack, or 'unknown' if not obvious (e.g. next, react, vue, svelte)."),
|
|
610
|
+
dependsOn: z2.array(z2.string()).describe(
|
|
611
|
+
"Repo-relative paths (each matching a backend in this map) that THIS frontend needs in order to function - the API/service(s) it calls and the data layer(s) that own the records it renders. These are pre-selected when the user picks this frontend, so only list backends this frontend actually depends on. Empty if it needs none."
|
|
612
|
+
),
|
|
613
|
+
why: z2.string().min(1).describe("One line: the evidence that made you classify this as a frontend.")
|
|
614
|
+
});
|
|
615
|
+
BackendEntry = z2.object({
|
|
616
|
+
path: z2.string().min(1).describe("Repo-relative path to the backend/API/service or the package that owns the data layer."),
|
|
617
|
+
language: z2.string().describe("Primary language, or 'unknown' (e.g. typescript, python, go, rust)."),
|
|
618
|
+
framework: z2.string().describe("Web/service framework or ORM stack, or 'unknown' (e.g. express, hono, fastapi, rails)."),
|
|
619
|
+
dataLayer: z2.object({
|
|
620
|
+
kind: z2.string().describe("How models are defined (e.g. prisma, drizzle, sqlalchemy, typeorm, raw-sql, unknown)."),
|
|
621
|
+
schemaPath: z2.string().optional().describe("Repo-relative path to the schema/models definition, if found.")
|
|
622
|
+
}).optional().describe("Where this backend's database models live. Omit if this backend owns no data layer."),
|
|
623
|
+
why: z2.string().min(1).describe("One line: the evidence that made you classify this as a backend.")
|
|
624
|
+
});
|
|
625
|
+
IgnoreEntry = z2.object({
|
|
626
|
+
path: z2.string().min(1).describe("Repo-relative path to a directory that is NOT relevant to testing this app."),
|
|
627
|
+
why: z2.string().min(1).describe("One line: why it is irrelevant (e.g. docs site, infra, tooling, examples, unrelated app).")
|
|
628
|
+
});
|
|
629
|
+
ProjectMapSchema = z2.object({
|
|
630
|
+
frontends: z2.array(FrontendEntry).describe("The UI surface(s) whose pages/flows get tested. Usually 1; may be more."),
|
|
631
|
+
backends: z2.array(BackendEntry).describe(
|
|
632
|
+
"The API/service(s) and data layer(s) that own the models we seed. May be 0 (frontend-only), 1, or many."
|
|
633
|
+
),
|
|
634
|
+
ignore: z2.array(IgnoreEntry).describe("Directories judged irrelevant to this app's tests, so later steps skip them.")
|
|
635
|
+
});
|
|
636
|
+
PROJECT_MAP_FILE = "project-map.json";
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
495
640
|
// src/core/display.ts
|
|
496
641
|
function formatArgs(input, keys) {
|
|
497
642
|
const parts = [];
|
|
@@ -766,8 +911,8 @@ var init_agent = __esm({
|
|
|
766
911
|
});
|
|
767
912
|
|
|
768
913
|
// src/core/gitignore.ts
|
|
769
|
-
import { readFile as
|
|
770
|
-
import { join as
|
|
914
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
915
|
+
import { join as join11, relative as relative2 } from "path";
|
|
771
916
|
import { glob as glob2 } from "glob";
|
|
772
917
|
async function loadGitignorePatterns(projectRoot) {
|
|
773
918
|
const patterns = [
|
|
@@ -787,10 +932,10 @@ async function loadGitignorePatterns(projectRoot) {
|
|
|
787
932
|
];
|
|
788
933
|
const matches = await glob2("**/.gitignore", { cwd: projectRoot, dot: true });
|
|
789
934
|
for (const match of matches) {
|
|
790
|
-
const fullPath =
|
|
935
|
+
const fullPath = join11(projectRoot, match);
|
|
791
936
|
try {
|
|
792
|
-
const content = await
|
|
793
|
-
const prefix = relative2(projectRoot,
|
|
937
|
+
const content = await readFile6(fullPath, "utf-8");
|
|
938
|
+
const prefix = relative2(projectRoot, join11(projectRoot, match, ".."));
|
|
794
939
|
const parsed = parseGitignore(content, prefix);
|
|
795
940
|
patterns.push(...parsed);
|
|
796
941
|
} catch (err) {
|
|
@@ -853,7 +998,7 @@ var init_exec_error = __esm({
|
|
|
853
998
|
import { execFile as execFile3 } from "child_process";
|
|
854
999
|
import { promisify as promisify2 } from "util";
|
|
855
1000
|
import { tool } from "ai";
|
|
856
|
-
import { z as
|
|
1001
|
+
import { z as z4 } from "zod";
|
|
857
1002
|
function validateCommand(command, allowed) {
|
|
858
1003
|
const trimmed = command.trim();
|
|
859
1004
|
if (trimmed.length === 0) return "Empty command";
|
|
@@ -919,8 +1064,8 @@ var init_bash = __esm({
|
|
|
919
1064
|
DEFAULT_ALLOWED = /* @__PURE__ */ new Set(["git", "wc", "sort", "head", "tail", "cat", "ls", "find", "diff", "echo"]);
|
|
920
1065
|
TIMEOUT_MS = 3e4;
|
|
921
1066
|
MAX_OUTPUT_BYTES = 1024 * 512;
|
|
922
|
-
inputSchema =
|
|
923
|
-
command:
|
|
1067
|
+
inputSchema = z4.object({
|
|
1068
|
+
command: z4.string().describe("Shell command to execute")
|
|
924
1069
|
});
|
|
925
1070
|
}
|
|
926
1071
|
});
|
|
@@ -928,7 +1073,7 @@ var init_bash = __esm({
|
|
|
928
1073
|
// src/tools/glob.ts
|
|
929
1074
|
import { tool as tool2 } from "ai";
|
|
930
1075
|
import { glob as glob3 } from "glob";
|
|
931
|
-
import { z as
|
|
1076
|
+
import { z as z5 } from "zod";
|
|
932
1077
|
async function executeGlob(pattern, cwd, ignorePatterns = DEFAULT_IGNORE) {
|
|
933
1078
|
try {
|
|
934
1079
|
const matches = await glob3(pattern, {
|
|
@@ -955,9 +1100,9 @@ var init_glob = __esm({
|
|
|
955
1100
|
"src/tools/glob.ts"() {
|
|
956
1101
|
"use strict";
|
|
957
1102
|
init_esm_shims();
|
|
958
|
-
inputSchema2 =
|
|
959
|
-
pattern:
|
|
960
|
-
cwd:
|
|
1103
|
+
inputSchema2 = z5.object({
|
|
1104
|
+
pattern: z5.string().describe("Glob pattern to match files (e.g. '**/*.ts', 'src/**/*.py')"),
|
|
1105
|
+
cwd: z5.string().optional().describe("Directory to search in. Defaults to working directory.")
|
|
961
1106
|
});
|
|
962
1107
|
DEFAULT_IGNORE = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
|
|
963
1108
|
}
|
|
@@ -967,7 +1112,7 @@ var init_glob = __esm({
|
|
|
967
1112
|
import { execFile as execFile4 } from "child_process";
|
|
968
1113
|
import { promisify as promisify3 } from "util";
|
|
969
1114
|
import { tool as tool3 } from "ai";
|
|
970
|
-
import { z as
|
|
1115
|
+
import { z as z6 } from "zod";
|
|
971
1116
|
function buildGrepTool(workingDirectory) {
|
|
972
1117
|
return tool3({
|
|
973
1118
|
description: "Search file contents with ripgrep. Returns matching lines with file paths and line numbers.",
|
|
@@ -1011,10 +1156,10 @@ var init_grep = __esm({
|
|
|
1011
1156
|
init_esm_shims();
|
|
1012
1157
|
init_exec_error();
|
|
1013
1158
|
execFileAsync3 = promisify3(execFile4);
|
|
1014
|
-
inputSchema3 =
|
|
1015
|
-
pattern:
|
|
1016
|
-
glob:
|
|
1017
|
-
path:
|
|
1159
|
+
inputSchema3 = z6.object({
|
|
1160
|
+
pattern: z6.string().describe("Regex pattern to search for in file contents"),
|
|
1161
|
+
glob: z6.string().optional().describe("Glob to filter files (e.g. '*.ts')"),
|
|
1162
|
+
path: z6.string().optional().describe("File or directory to search in")
|
|
1018
1163
|
});
|
|
1019
1164
|
}
|
|
1020
1165
|
});
|
|
@@ -1022,10 +1167,10 @@ var init_grep = __esm({
|
|
|
1022
1167
|
// src/tools/list-directory.ts
|
|
1023
1168
|
import { readdir } from "fs/promises";
|
|
1024
1169
|
import { stat } from "fs/promises";
|
|
1025
|
-
import { join as
|
|
1170
|
+
import { join as join12, relative as relative3 } from "path";
|
|
1026
1171
|
import { tool as tool4 } from "ai";
|
|
1027
1172
|
import { minimatch } from "minimatch";
|
|
1028
|
-
import { z as
|
|
1173
|
+
import { z as z7 } from "zod";
|
|
1029
1174
|
function buildMatcher(patterns) {
|
|
1030
1175
|
const positive = patterns.filter((p10) => !p10.startsWith("!"));
|
|
1031
1176
|
const negative = patterns.filter((p10) => p10.startsWith("!")).map((p10) => p10.slice(1));
|
|
@@ -1047,7 +1192,7 @@ async function buildTree(dirPath, maxDepth, currentDepth, isIgnored, relativeBas
|
|
|
1047
1192
|
const withTypes = [];
|
|
1048
1193
|
for (const name of rawEntries) {
|
|
1049
1194
|
try {
|
|
1050
|
-
const s = await stat(
|
|
1195
|
+
const s = await stat(join12(dirPath, name));
|
|
1051
1196
|
withTypes.push({ name, isDir: s.isDirectory() });
|
|
1052
1197
|
} catch {
|
|
1053
1198
|
withTypes.push({ name, isDir: false });
|
|
@@ -1067,7 +1212,7 @@ async function buildTree(dirPath, maxDepth, currentDepth, isIgnored, relativeBas
|
|
|
1067
1212
|
}
|
|
1068
1213
|
if (entry.isDir) {
|
|
1069
1214
|
const children = await buildTree(
|
|
1070
|
-
|
|
1215
|
+
join12(dirPath, entry.name),
|
|
1071
1216
|
maxDepth,
|
|
1072
1217
|
currentDepth + 1,
|
|
1073
1218
|
isIgnored,
|
|
@@ -1105,10 +1250,10 @@ async function buildListDirectoryTool(workingDirectory) {
|
|
|
1105
1250
|
const isIgnored = buildMatcher(patterns);
|
|
1106
1251
|
return tool4({
|
|
1107
1252
|
description: "List directory structure as a tree. Use this for an overview of the project layout. Start at the root (path='.') with depth 3, then increase depth or narrow path if needed. Do NOT call this on every subdirectory - use glob to find specific files instead. Returns cached result if the same path+depth was already requested.",
|
|
1108
|
-
inputSchema:
|
|
1109
|
-
path:
|
|
1110
|
-
depth:
|
|
1111
|
-
gitignore:
|
|
1253
|
+
inputSchema: z7.object({
|
|
1254
|
+
path: z7.string().default(".").describe("Directory path relative to project root. Defaults to root."),
|
|
1255
|
+
depth: z7.number().min(1).max(15).default(10).describe("Max depth to traverse (1-15). Default 10."),
|
|
1256
|
+
gitignore: z7.boolean().describe(
|
|
1112
1257
|
"Whether to respect the gitignore or to ignore it. true will respect it. false will ignore it. Default true"
|
|
1113
1258
|
).default(true)
|
|
1114
1259
|
}),
|
|
@@ -1120,7 +1265,7 @@ async function buildListDirectoryTool(workingDirectory) {
|
|
|
1120
1265
|
};
|
|
1121
1266
|
}
|
|
1122
1267
|
seen.add(cacheKey);
|
|
1123
|
-
const targetDir = input.path === "." ? workingDirectory :
|
|
1268
|
+
const targetDir = input.path === "." ? workingDirectory : join12(workingDirectory, input.path);
|
|
1124
1269
|
try {
|
|
1125
1270
|
const s = await stat(targetDir);
|
|
1126
1271
|
if (!s.isDirectory()) {
|
|
@@ -1152,10 +1297,10 @@ var init_list_directory = __esm({
|
|
|
1152
1297
|
});
|
|
1153
1298
|
|
|
1154
1299
|
// src/tools/read-file.ts
|
|
1155
|
-
import { readFile as
|
|
1300
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
1156
1301
|
import { relative as relative4, resolve as resolve2 } from "path";
|
|
1157
1302
|
import { tool as tool5 } from "ai";
|
|
1158
|
-
import { z as
|
|
1303
|
+
import { z as z8 } from "zod";
|
|
1159
1304
|
function resolveSandboxedPath(workingDirectory, filePath) {
|
|
1160
1305
|
const absolutePath = resolve2(workingDirectory, filePath);
|
|
1161
1306
|
const relativePath = relative4(workingDirectory, absolutePath);
|
|
@@ -1180,7 +1325,7 @@ async function executeReadFile(workingDirectory, filePath, offset, limit) {
|
|
|
1180
1325
|
const resolved = resolveSandboxedPath(workingDirectory, filePath);
|
|
1181
1326
|
if ("error" in resolved) return resolved;
|
|
1182
1327
|
try {
|
|
1183
|
-
const content = await
|
|
1328
|
+
const content = await readFile7(resolved.absolutePath, "utf-8");
|
|
1184
1329
|
const sliced = sliceLines(content, offset ?? 0, limit ?? MAX_LINES);
|
|
1185
1330
|
return {
|
|
1186
1331
|
path: resolved.relativePath,
|
|
@@ -1208,10 +1353,10 @@ var init_read_file = __esm({
|
|
|
1208
1353
|
"use strict";
|
|
1209
1354
|
init_esm_shims();
|
|
1210
1355
|
MAX_LINES = 2e3;
|
|
1211
|
-
inputSchema4 =
|
|
1212
|
-
filePath:
|
|
1213
|
-
offset:
|
|
1214
|
-
limit:
|
|
1356
|
+
inputSchema4 = z8.object({
|
|
1357
|
+
filePath: z8.string().describe("Path to the file (absolute or relative to working directory)"),
|
|
1358
|
+
offset: z8.number().int().min(0).optional().describe("Line number to start reading from (0-based)"),
|
|
1359
|
+
limit: z8.number().int().min(1).optional().describe("Maximum number of lines to read")
|
|
1215
1360
|
});
|
|
1216
1361
|
}
|
|
1217
1362
|
});
|
|
@@ -1234,10 +1379,10 @@ var init_pick_string = __esm({
|
|
|
1234
1379
|
|
|
1235
1380
|
// src/tools/subagent.ts
|
|
1236
1381
|
import { ToolLoopAgent as ToolLoopAgent2, hasToolCall as hasToolCall2, stepCountIs as stepCountIs2, tool as tool6 } from "ai";
|
|
1237
|
-
import { z as
|
|
1382
|
+
import { z as z9 } from "zod";
|
|
1238
1383
|
function buildSubagentTools(workingDirectory, onFileRead) {
|
|
1239
1384
|
const baseReadFile = buildReadFileTool(workingDirectory);
|
|
1240
|
-
const
|
|
1385
|
+
const readFile24 = onFileRead ? tool6({
|
|
1241
1386
|
description: baseReadFile.description,
|
|
1242
1387
|
inputSchema: baseReadFile.inputSchema,
|
|
1243
1388
|
execute: async (input, options) => {
|
|
@@ -1250,7 +1395,7 @@ function buildSubagentTools(workingDirectory, onFileRead) {
|
|
|
1250
1395
|
bash: buildBashTool(workingDirectory),
|
|
1251
1396
|
glob: buildGlobTool(workingDirectory),
|
|
1252
1397
|
grep: buildGrepTool(workingDirectory),
|
|
1253
|
-
read_file:
|
|
1398
|
+
read_file: readFile24
|
|
1254
1399
|
};
|
|
1255
1400
|
}
|
|
1256
1401
|
function buildSubagentTool(model, workingDirectory, onHeartbeat, onFileRead) {
|
|
@@ -1301,11 +1446,11 @@ var init_subagent = __esm({
|
|
|
1301
1446
|
init_glob();
|
|
1302
1447
|
init_grep();
|
|
1303
1448
|
init_read_file();
|
|
1304
|
-
inputSchema5 =
|
|
1305
|
-
instruction:
|
|
1449
|
+
inputSchema5 = z9.object({
|
|
1450
|
+
instruction: z9.string().describe("Focused task for the subagent. Be specific about files and patterns to investigate.")
|
|
1306
1451
|
});
|
|
1307
|
-
resultSchema =
|
|
1308
|
-
findings:
|
|
1452
|
+
resultSchema = z9.object({
|
|
1453
|
+
findings: z9.string().describe("Summary of what was found")
|
|
1309
1454
|
});
|
|
1310
1455
|
SYSTEM_PROMPT = `You are a code research assistant. You have tools to explore a codebase: bash (shell commands, mainly git), glob (find files), grep (search content), and read_file (read files).
|
|
1311
1456
|
|
|
@@ -1316,10 +1461,10 @@ Be thorough but focused - only investigate what's relevant to your instruction.`
|
|
|
1316
1461
|
});
|
|
1317
1462
|
|
|
1318
1463
|
// src/tools/write-file.ts
|
|
1319
|
-
import { writeFile as
|
|
1464
|
+
import { writeFile as writeFile5, mkdir as mkdir2 } from "fs/promises";
|
|
1320
1465
|
import { dirname as dirname2, relative as relative5, resolve as resolve3 } from "path";
|
|
1321
1466
|
import { tool as tool7 } from "ai";
|
|
1322
|
-
import { z as
|
|
1467
|
+
import { z as z10 } from "zod";
|
|
1323
1468
|
async function executeWriteFile(outputDirectory, filePath, content) {
|
|
1324
1469
|
const cleaned = filePath.replace(/^autonoma\//, "");
|
|
1325
1470
|
const absolutePath = resolve3(outputDirectory, cleaned);
|
|
@@ -1329,7 +1474,7 @@ async function executeWriteFile(outputDirectory, filePath, content) {
|
|
|
1329
1474
|
}
|
|
1330
1475
|
try {
|
|
1331
1476
|
await mkdir2(dirname2(absolutePath), { recursive: true });
|
|
1332
|
-
await
|
|
1477
|
+
await writeFile5(absolutePath, content, "utf-8");
|
|
1333
1478
|
return { path: relativePath, bytesWritten: content.length };
|
|
1334
1479
|
} catch (err) {
|
|
1335
1480
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1348,9 +1493,9 @@ var init_write_file = __esm({
|
|
|
1348
1493
|
"src/tools/write-file.ts"() {
|
|
1349
1494
|
"use strict";
|
|
1350
1495
|
init_esm_shims();
|
|
1351
|
-
inputSchema6 =
|
|
1352
|
-
filePath:
|
|
1353
|
-
content:
|
|
1496
|
+
inputSchema6 = z10.object({
|
|
1497
|
+
filePath: z10.string().describe("Path to write (absolute or relative to output directory)"),
|
|
1498
|
+
content: z10.string().describe("File content to write")
|
|
1354
1499
|
});
|
|
1355
1500
|
}
|
|
1356
1501
|
});
|
|
@@ -1358,16 +1503,21 @@ var init_write_file = __esm({
|
|
|
1358
1503
|
// src/tools/ask-user.ts
|
|
1359
1504
|
import * as p2 from "@clack/prompts";
|
|
1360
1505
|
import { tool as tool8 } from "ai";
|
|
1361
|
-
import { z as
|
|
1506
|
+
import { z as z11 } from "zod";
|
|
1362
1507
|
function buildAskUserTool() {
|
|
1363
1508
|
return tool8({
|
|
1364
1509
|
description: "Ask the user a question ONLY when the answer is truly unknowable from the codebase. Valid reasons: untyped JSON/JSONB field schemas, business rules not in code, config values not in source. NEVER ask about: field names (read the schema), field types (read the ORM model), enum values (read the code), relationships (read foreign keys), numeric values (read the seed data or defaults). If you can find it by reading a file, DO NOT ask - read the file instead.",
|
|
1365
|
-
inputSchema:
|
|
1366
|
-
question:
|
|
1510
|
+
inputSchema: z11.object({
|
|
1511
|
+
question: z11.string().describe(
|
|
1367
1512
|
"A clear, plain-language question. State exactly what you need to know and why you can't find it in code. BAD: 'What are the decimal values for checking_balance?' - GOOD: 'Your Account model has a metadata JSON column with no type definition. What fields go inside it?'"
|
|
1368
1513
|
)
|
|
1369
1514
|
}),
|
|
1370
1515
|
execute: async (input) => {
|
|
1516
|
+
if (!process.stdin.isTTY) {
|
|
1517
|
+
return {
|
|
1518
|
+
answer: "No interactive user is available (non-interactive run). Do not ask again - infer the answer by reading the relevant model/schema/service files in the codebase and proceed with your best judgment."
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1371
1521
|
const answer = await p2.text({ message: input.question });
|
|
1372
1522
|
if (p2.isCancel(answer)) return { answer: "User skipped this question" };
|
|
1373
1523
|
return { answer };
|
|
@@ -1411,6 +1561,58 @@ var init_tools = __esm({
|
|
|
1411
1561
|
}
|
|
1412
1562
|
});
|
|
1413
1563
|
|
|
1564
|
+
// src/agents/00-project-mapper/index.ts
|
|
1565
|
+
var project_mapper_exports = {};
|
|
1566
|
+
__export(project_mapper_exports, {
|
|
1567
|
+
runProjectMapper: () => runProjectMapper
|
|
1568
|
+
});
|
|
1569
|
+
import { tool as tool9 } from "ai";
|
|
1570
|
+
async function runProjectMapper(input) {
|
|
1571
|
+
const model = getModel(input.modelId);
|
|
1572
|
+
const { logger, onStepFinish } = buildDefaultStepLogger("project-map", MAX_STEPS);
|
|
1573
|
+
let captured;
|
|
1574
|
+
const prompt = `Map the codebase rooted at ${input.projectRoot}.
|
|
1575
|
+
|
|
1576
|
+
Explore the layout and dependency manifests, then enumerate EVERY candidate frontend and EVERY candidate backend/data-layer, and for each frontend record the backends it depends on. List everything genuinely irrelevant under ignore. Do not prune to a single app - a human picks the one to test afterward. Then call set_project_map and finish.`;
|
|
1577
|
+
const agentConfig = {
|
|
1578
|
+
id: "project-mapper",
|
|
1579
|
+
systemPrompt: SYSTEM_PROMPT2,
|
|
1580
|
+
model,
|
|
1581
|
+
maxSteps: MAX_STEPS,
|
|
1582
|
+
tools: async (heartbeat) => {
|
|
1583
|
+
const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat);
|
|
1584
|
+
return {
|
|
1585
|
+
...tools,
|
|
1586
|
+
set_project_map: tool9({
|
|
1587
|
+
description: "Record the final project map: the frontend app(s), the backend(s)/data layer(s), and the directories to ignore. Call this once you are confident in the partition, then call finish.",
|
|
1588
|
+
inputSchema: ProjectMapSchema,
|
|
1589
|
+
execute: (map) => {
|
|
1590
|
+
captured = map;
|
|
1591
|
+
return `Recorded: ${map.frontends.length} frontend(s), ${map.backends.length} backend(s), ${map.ignore.length} ignored. Now call finish.`;
|
|
1592
|
+
}
|
|
1593
|
+
})
|
|
1594
|
+
};
|
|
1595
|
+
},
|
|
1596
|
+
onStepFinish
|
|
1597
|
+
};
|
|
1598
|
+
await runAgent(agentConfig, prompt, () => captured);
|
|
1599
|
+
logger.summary();
|
|
1600
|
+
return captured;
|
|
1601
|
+
}
|
|
1602
|
+
var MAX_STEPS, SYSTEM_PROMPT2;
|
|
1603
|
+
var init_project_mapper = __esm({
|
|
1604
|
+
"src/agents/00-project-mapper/index.ts"() {
|
|
1605
|
+
"use strict";
|
|
1606
|
+
init_esm_shims();
|
|
1607
|
+
init_agent();
|
|
1608
|
+
init_model();
|
|
1609
|
+
init_project_map();
|
|
1610
|
+
init_tools();
|
|
1611
|
+
MAX_STEPS = 60;
|
|
1612
|
+
SYSTEM_PROMPT2 = "You map a codebase into three groups so the rest of the test-planning pipeline knows exactly what to look at and what to skip: FRONTENDS (the UI surfaces whose pages and flows could be tested), BACKENDS (the API/service and the data layer that owns the database models we would seed test data into), and IGNORE (everything irrelevant to testing this product).\n\nYou are a DISCOVERY step, not a decision step. The pipeline tests ONE frontend at a time, but YOU do not choose which - a human (or the agent driving you) picks afterward from what you found. So enumerate EVERY candidate frontend and EVERY candidate backend you can justify; do not prune down to a single app. Pruning happens at selection, using the dependency edges you record.\n\nDiscover the structure - never assume it. Read package/dependency manifests, config files, and the directory layout, then reason from evidence:\n- A FRONTEND renders a user interface (pages/routes/views, a UI framework or bundler, browser entry points).\n- A BACKEND serves an API and/or owns the data layer (a database schema, ORM models, migrations, server routes).\n- IGNORE is the rest: infra, build tooling, examples, generated code, and packages that are neither a UI nor a service/data-layer.\n\nFor EACH frontend, record `dependsOn`: the repo-relative paths (each must be one of the backends you list) that that frontend actually needs in order to work - the API/service(s) it calls and the data layer(s) that own the records it renders. Infer these from evidence: the frontend's dependency manifest, the API clients/SDKs it imports, GraphQL/REST endpoints or gateway URLs it points at, and shared data-layer packages it reads. When the user selects that frontend, its `dependsOn` backends are pre-checked, so be accurate: list the backends it truly needs, not every backend in the repo.\n\nHandle every shape without special-casing any framework:\n- A single fullstack app can be BOTH a frontend and a backend at the SAME path - list that path under both, and put its own path in its `dependsOn`.\n- A monorepo can hold many apps and packages - list every genuine frontend and backend; only truly irrelevant directories go in IGNORE.\n- There may be MANY backends (a main API, separate services, a shared data-layer package, a gateway plus the services behind it) - list each one, and wire each frontend's `dependsOn` to just the ones it uses.\n- The data layer a frontend uses may live in a sibling/shared package OUTSIDE the frontend's own folder - record that package as a backend, point its dataLayer.schemaPath at the schema, and include it in the frontend's `dependsOn`.\n- A codebase may ship only one half. If you find frontends but NO backend/data layer (or vice versa), still record what you found and leave the other group empty - the caller will ask the user to supply the missing half.\n\nBe thorough and evidence-based. When you have enumerated the candidates and their dependency edges, call set_project_map, then call finish. Keep each `why` to a single concrete sentence citing what you saw.";
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1414
1616
|
// src/agents/00-pages-finder/index.ts
|
|
1415
1617
|
var pages_finder_exports = {};
|
|
1416
1618
|
__export(pages_finder_exports, {
|
|
@@ -1418,8 +1620,8 @@ __export(pages_finder_exports, {
|
|
|
1418
1620
|
});
|
|
1419
1621
|
import { existsSync } from "fs";
|
|
1420
1622
|
import * as path2 from "path";
|
|
1421
|
-
import { tool as
|
|
1422
|
-
import { z as
|
|
1623
|
+
import { tool as tool10 } from "ai";
|
|
1624
|
+
import { z as z12 } from "zod";
|
|
1423
1625
|
async function runPageFinder(input) {
|
|
1424
1626
|
const model = getModel(input.modelId);
|
|
1425
1627
|
const pageCollector = new PageCollector();
|
|
@@ -1438,7 +1640,7 @@ ${input.extraMessage}`;
|
|
|
1438
1640
|
const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat);
|
|
1439
1641
|
return {
|
|
1440
1642
|
...tools,
|
|
1441
|
-
add_page:
|
|
1643
|
+
add_page: tool10({
|
|
1442
1644
|
description: "use this tool to add a page that you found",
|
|
1443
1645
|
inputSchema: Page,
|
|
1444
1646
|
execute: (input2) => {
|
|
@@ -1450,9 +1652,9 @@ ${input.extraMessage}`;
|
|
|
1450
1652
|
return `page ${JSON.stringify(input2)} added`;
|
|
1451
1653
|
}
|
|
1452
1654
|
}),
|
|
1453
|
-
view_pages:
|
|
1655
|
+
view_pages: tool10({
|
|
1454
1656
|
description: "use this tool to view all the pages that you already added",
|
|
1455
|
-
inputSchema:
|
|
1657
|
+
inputSchema: z12.object(),
|
|
1456
1658
|
execute: () => pageCollector.viewPages()
|
|
1457
1659
|
})
|
|
1458
1660
|
};
|
|
@@ -1471,10 +1673,10 @@ var init_pages_finder = __esm({
|
|
|
1471
1673
|
init_agent();
|
|
1472
1674
|
init_model();
|
|
1473
1675
|
init_tools();
|
|
1474
|
-
Page =
|
|
1475
|
-
route:
|
|
1476
|
-
path:
|
|
1477
|
-
description:
|
|
1676
|
+
Page = z12.object({
|
|
1677
|
+
route: z12.string().min(1),
|
|
1678
|
+
path: z12.string().min(1),
|
|
1679
|
+
description: z12.string().min(10)
|
|
1478
1680
|
});
|
|
1479
1681
|
PageCollector = class {
|
|
1480
1682
|
// the key is the path
|
|
@@ -1504,13 +1706,13 @@ var init_pages_finder = __esm({
|
|
|
1504
1706
|
|
|
1505
1707
|
// src/core/review.ts
|
|
1506
1708
|
import { access } from "fs/promises";
|
|
1507
|
-
import { join as
|
|
1709
|
+
import { join as join13, isAbsolute } from "path";
|
|
1508
1710
|
import * as p3 from "@clack/prompts";
|
|
1509
1711
|
import spawn from "cross-spawn";
|
|
1510
1712
|
import which from "which";
|
|
1511
1713
|
function resolvePath(artifact, outputDir) {
|
|
1512
1714
|
if (isAbsolute(artifact)) return artifact;
|
|
1513
|
-
return
|
|
1715
|
+
return join13(outputDir, artifact);
|
|
1514
1716
|
}
|
|
1515
1717
|
async function detectEditors() {
|
|
1516
1718
|
if (cachedEditors) return cachedEditors;
|
|
@@ -1525,12 +1727,12 @@ async function detectEditors() {
|
|
|
1525
1727
|
async function launchEditor(editor, files) {
|
|
1526
1728
|
const args = editor.args(files);
|
|
1527
1729
|
const isTerminalEditor = TERMINAL_EDITORS.has(editor.command);
|
|
1528
|
-
await new Promise((
|
|
1730
|
+
await new Promise((resolve6) => {
|
|
1529
1731
|
let settled = false;
|
|
1530
1732
|
const settle = () => {
|
|
1531
1733
|
if (settled) return;
|
|
1532
1734
|
settled = true;
|
|
1533
|
-
|
|
1735
|
+
resolve6();
|
|
1534
1736
|
};
|
|
1535
1737
|
const proc = spawn(editor.command, args, { stdio: "inherit" });
|
|
1536
1738
|
proc.on("error", (err) => {
|
|
@@ -1592,7 +1794,7 @@ async function showResults(result, options) {
|
|
|
1592
1794
|
if (result.artifacts.length === 0) {
|
|
1593
1795
|
const knownFiles = ["AUTONOMA.md", "entity-audit.md", "scenarios.md"];
|
|
1594
1796
|
for (const f of knownFiles) {
|
|
1595
|
-
const fullPath =
|
|
1797
|
+
const fullPath = join13(options.outputDir, f);
|
|
1596
1798
|
try {
|
|
1597
1799
|
await access(fullPath);
|
|
1598
1800
|
result.artifacts.push(f);
|
|
@@ -1681,13 +1883,13 @@ var init_review = __esm({
|
|
|
1681
1883
|
});
|
|
1682
1884
|
|
|
1683
1885
|
// src/agents/01-kb-generator/flows.ts
|
|
1684
|
-
import { readFile as
|
|
1685
|
-
import { join as
|
|
1886
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
1887
|
+
import { join as join14 } from "path";
|
|
1686
1888
|
import matter from "gray-matter";
|
|
1687
1889
|
async function parseCoreFlows(outputDir) {
|
|
1688
1890
|
let raw;
|
|
1689
1891
|
try {
|
|
1690
|
-
raw = await
|
|
1892
|
+
raw = await readFile8(join14(outputDir, "AUTONOMA.md"), "utf-8");
|
|
1691
1893
|
} catch {
|
|
1692
1894
|
return [];
|
|
1693
1895
|
}
|
|
@@ -1752,12 +1954,12 @@ var init_flows = __esm({
|
|
|
1752
1954
|
});
|
|
1753
1955
|
|
|
1754
1956
|
// src/agents/01-kb-generator/prompt.ts
|
|
1755
|
-
var
|
|
1957
|
+
var SYSTEM_PROMPT3;
|
|
1756
1958
|
var init_prompt = __esm({
|
|
1757
1959
|
"src/agents/01-kb-generator/prompt.ts"() {
|
|
1758
1960
|
"use strict";
|
|
1759
1961
|
init_esm_shims();
|
|
1760
|
-
|
|
1962
|
+
SYSTEM_PROMPT3 = `You are a knowledge base generator for E2E test planning. You analyze a frontend codebase and produce a structured guide to EVERY page, flow, and interaction. You must be EXHAUSTIVE - missing a page means missing test coverage.
|
|
1761
1963
|
|
|
1762
1964
|
## Your output
|
|
1763
1965
|
|
|
@@ -1900,15 +2102,15 @@ var kb_generator_exports = {};
|
|
|
1900
2102
|
__export(kb_generator_exports, {
|
|
1901
2103
|
runKBGenerator: () => runKBGenerator
|
|
1902
2104
|
});
|
|
1903
|
-
import { readFile as
|
|
1904
|
-
import { join as
|
|
1905
|
-
import { tool as
|
|
1906
|
-
import { z as
|
|
2105
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2106
|
+
import { join as join15, resolve as resolve5 } from "path";
|
|
2107
|
+
import { tool as tool11 } from "ai";
|
|
2108
|
+
import { z as z13 } from "zod";
|
|
1907
2109
|
function buildRegisterPagesTool(tracker) {
|
|
1908
|
-
return
|
|
2110
|
+
return tool11({
|
|
1909
2111
|
description: "Register ALL page/route files discovered via glob. Call this ONCE after globbing for page files. The system will track which ones you've read and block finish until all are covered.",
|
|
1910
|
-
inputSchema:
|
|
1911
|
-
pages:
|
|
2112
|
+
inputSchema: z13.object({
|
|
2113
|
+
pages: z13.array(z13.string()).describe("All page file paths found by glob")
|
|
1912
2114
|
}),
|
|
1913
2115
|
execute: async (input) => {
|
|
1914
2116
|
tracker.register(input.pages);
|
|
@@ -1920,25 +2122,33 @@ function buildRegisterPagesTool(tracker) {
|
|
|
1920
2122
|
});
|
|
1921
2123
|
}
|
|
1922
2124
|
function buildPageCoverageTool(tracker) {
|
|
1923
|
-
return
|
|
2125
|
+
return tool11({
|
|
1924
2126
|
description: "Check how many registered pages you've read vs how many remain.",
|
|
1925
|
-
inputSchema:
|
|
2127
|
+
inputSchema: z13.object({}),
|
|
1926
2128
|
execute: async () => tracker.coverage()
|
|
1927
2129
|
});
|
|
1928
2130
|
}
|
|
2131
|
+
function requiredReads(total) {
|
|
2132
|
+
if (total <= FULL_COVERAGE_MAX_ROUTES) return total;
|
|
2133
|
+
return Math.ceil(total * LARGE_APP_COVERAGE_FLOOR);
|
|
2134
|
+
}
|
|
1929
2135
|
function buildFinishTool(tracker, onFinish) {
|
|
1930
|
-
return
|
|
1931
|
-
description: "Call when you have finished generating the knowledge base. BLOCKED
|
|
1932
|
-
inputSchema:
|
|
1933
|
-
summary:
|
|
1934
|
-
artifacts:
|
|
2136
|
+
return tool11({
|
|
2137
|
+
description: "Call when you have finished generating the knowledge base. BLOCKED until you have read enough of the registered routes (every route on a small app; a strong majority on a large one) - call page_coverage first to check how many remain.",
|
|
2138
|
+
inputSchema: z13.object({
|
|
2139
|
+
summary: z13.string().describe("Summary of what was generated"),
|
|
2140
|
+
artifacts: z13.array(z13.string()).describe("List of files written")
|
|
1935
2141
|
}),
|
|
1936
2142
|
execute: async (input) => {
|
|
1937
2143
|
const cov = tracker.coverage();
|
|
1938
|
-
|
|
2144
|
+
const required = requiredReads(cov.total);
|
|
2145
|
+
if (cov.read < required) {
|
|
2146
|
+
const preview = cov.unread.slice(0, 40).join("\n");
|
|
2147
|
+
const more = cov.unread.length > 40 ? `
|
|
2148
|
+
...and ${cov.unread.length - 40} more` : "";
|
|
1939
2149
|
return {
|
|
1940
|
-
error: `Cannot finish: ${cov.
|
|
1941
|
-
${
|
|
2150
|
+
error: `Cannot finish: only ${cov.read}/${cov.total} routes read - read at least ${required - cov.read} more (target ${required} of ${cov.total}). Start with:
|
|
2151
|
+
${preview}${more}`
|
|
1942
2152
|
};
|
|
1943
2153
|
}
|
|
1944
2154
|
onFinish({
|
|
@@ -1951,7 +2161,7 @@ ${cov.unread.join("\n")}`
|
|
|
1951
2161
|
});
|
|
1952
2162
|
}
|
|
1953
2163
|
function buildTrackedReadTool(tracker, baseTool) {
|
|
1954
|
-
return
|
|
2164
|
+
return tool11({
|
|
1955
2165
|
description: baseTool.description,
|
|
1956
2166
|
inputSchema: baseTool.inputSchema,
|
|
1957
2167
|
execute: async (input, options) => {
|
|
@@ -1961,13 +2171,36 @@ function buildTrackedReadTool(tracker, baseTool) {
|
|
|
1961
2171
|
}
|
|
1962
2172
|
});
|
|
1963
2173
|
}
|
|
2174
|
+
function buildKbAgentConfig(tracker, model, input, onStepFinish, setResult) {
|
|
2175
|
+
return {
|
|
2176
|
+
id: "kb-generator",
|
|
2177
|
+
systemPrompt: SYSTEM_PROMPT3,
|
|
2178
|
+
model,
|
|
2179
|
+
maxSteps: 150,
|
|
2180
|
+
tools: async (heartbeat) => {
|
|
2181
|
+
const onFileRead = (path3) => tracker.markRead(path3);
|
|
2182
|
+
const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat, onFileRead);
|
|
2183
|
+
return {
|
|
2184
|
+
...tools,
|
|
2185
|
+
read_file: buildTrackedReadTool(tracker, tools.read_file),
|
|
2186
|
+
register_pages: buildRegisterPagesTool(tracker),
|
|
2187
|
+
page_coverage: buildPageCoverageTool(tracker),
|
|
2188
|
+
finish: buildFinishTool(tracker, setResult)
|
|
2189
|
+
};
|
|
2190
|
+
},
|
|
2191
|
+
onStepFinish
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
1964
2194
|
async function runKBGenerator(input) {
|
|
1965
2195
|
const model = getModel(input.modelId);
|
|
1966
2196
|
let result;
|
|
1967
|
-
const
|
|
2197
|
+
const setResult = (r) => {
|
|
2198
|
+
result = r;
|
|
2199
|
+
};
|
|
1968
2200
|
const { logger, onStepFinish } = buildDefaultStepLogger("kb", 150);
|
|
1969
2201
|
const contextBlock = (input.projectContext ? "\n" + formatContext(input.projectContext) + "\n" : "") + formatRetryGuidance(input.retryGuidance);
|
|
1970
2202
|
const pages = input.projectContext?.pages;
|
|
2203
|
+
const tracker = new PageTracker(input.projectRoot);
|
|
1971
2204
|
if (pages?.length) {
|
|
1972
2205
|
tracker.register(pages.map((p10) => p10.path));
|
|
1973
2206
|
}
|
|
@@ -1979,7 +2212,7 @@ Pages have already been discovered (${pages.length} routes pre-registered). You
|
|
|
1979
2212
|
2. Read EVERY registered page file with read_file - the system tracks this
|
|
1980
2213
|
3. Write AUTONOMA.md progressively as you go (update it after each major area)
|
|
1981
2214
|
4. Call page_coverage to verify you've read all pages
|
|
1982
|
-
5. Call finish - it will REJECT if
|
|
2215
|
+
5. Call finish - it will REJECT if you have not read enough of the registered routes
|
|
1983
2216
|
|
|
1984
2217
|
Output files:
|
|
1985
2218
|
1. AUTONOMA.md - with YAML frontmatter (app_name, app_description, core_flows, feature_count)` : `Analyze the codebase at the working directory and generate a complete knowledge base.
|
|
@@ -1991,34 +2224,15 @@ MANDATORY PROCESS:
|
|
|
1991
2224
|
4. Read EVERY registered page file with read_file - the system tracks this
|
|
1992
2225
|
5. Write AUTONOMA.md progressively as you go (update it after each major area)
|
|
1993
2226
|
6. Call page_coverage to verify you've read all pages
|
|
1994
|
-
7. Call finish - it will REJECT if
|
|
2227
|
+
7. Call finish - it will REJECT if you have not read enough of the registered routes
|
|
1995
2228
|
|
|
1996
2229
|
Output files:
|
|
1997
2230
|
1. AUTONOMA.md - with YAML frontmatter (app_name, app_description, core_flows, feature_count)`;
|
|
1998
|
-
const agentConfig =
|
|
1999
|
-
id: "kb-generator",
|
|
2000
|
-
systemPrompt: SYSTEM_PROMPT2,
|
|
2001
|
-
model,
|
|
2002
|
-
maxSteps: 150,
|
|
2003
|
-
tools: async (heartbeat) => {
|
|
2004
|
-
const onFileRead = (path3) => tracker.markRead(path3);
|
|
2005
|
-
const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat, onFileRead);
|
|
2006
|
-
return {
|
|
2007
|
-
...tools,
|
|
2008
|
-
read_file: buildTrackedReadTool(tracker, tools.read_file),
|
|
2009
|
-
register_pages: buildRegisterPagesTool(tracker),
|
|
2010
|
-
page_coverage: buildPageCoverageTool(tracker),
|
|
2011
|
-
finish: buildFinishTool(tracker, (r) => {
|
|
2012
|
-
result = r;
|
|
2013
|
-
})
|
|
2014
|
-
};
|
|
2015
|
-
},
|
|
2016
|
-
onStepFinish
|
|
2017
|
-
};
|
|
2231
|
+
const agentConfig = buildKbAgentConfig(tracker, model, input, onStepFinish, setResult);
|
|
2018
2232
|
await runAgent(agentConfig, prompt, () => result);
|
|
2019
2233
|
logger.summary();
|
|
2020
|
-
const autonomaPath =
|
|
2021
|
-
const autonomaExists = await
|
|
2234
|
+
const autonomaPath = join15(input.outputDir, "AUTONOMA.md");
|
|
2235
|
+
const autonomaExists = await readFile9(autonomaPath, "utf-8").then(() => true).catch((err) => {
|
|
2022
2236
|
debugLog("AUTONOMA.md not found while checking step completion", { err });
|
|
2023
2237
|
return false;
|
|
2024
2238
|
});
|
|
@@ -2029,6 +2243,13 @@ Output files:
|
|
|
2029
2243
|
summary: "Knowledge base generated."
|
|
2030
2244
|
};
|
|
2031
2245
|
}
|
|
2246
|
+
const finalTracker = new PageTracker(input.projectRoot);
|
|
2247
|
+
if (pages?.length) {
|
|
2248
|
+
const paths = pages.map((p10) => p10.path);
|
|
2249
|
+
finalTracker.register(paths);
|
|
2250
|
+
for (const path3 of paths) finalTracker.markRead(path3);
|
|
2251
|
+
}
|
|
2252
|
+
const finalConfig = buildKbAgentConfig(finalTracker, model, input, onStepFinish, setResult);
|
|
2032
2253
|
const declaredCriticalFlows = input.projectContext?.criticalFlows?.trim();
|
|
2033
2254
|
if (result?.success && declaredCriticalFlows) {
|
|
2034
2255
|
const beforeSelfReview = result;
|
|
@@ -2045,7 +2266,7 @@ Read your AUTONOMA.md output. For EACH critical flow the user named:
|
|
|
2045
2266
|
If any declared critical flow is missing, mismatched, or left core: false, FIX AUTONOMA.md now - add the feature if it is genuinely absent, or flip core to true with a coreReason. Do not downgrade or drop anything the user declared critical.
|
|
2046
2267
|
|
|
2047
2268
|
When AUTONOMA.md correctly reflects every declared critical flow, call finish.`;
|
|
2048
|
-
await runAgent(
|
|
2269
|
+
await runAgent(finalConfig, selfReviewPrompt, () => result);
|
|
2049
2270
|
if (!result) result = beforeSelfReview;
|
|
2050
2271
|
}
|
|
2051
2272
|
const reviewed = await reviewLoop(result, {
|
|
@@ -2066,7 +2287,7 @@ When AUTONOMA.md correctly reflects every declared critical flow, call finish.`;
|
|
|
2066
2287
|
Read your previous output file (AUTONOMA.md) from the output directory to see what you produced.
|
|
2067
2288
|
Adjust based on the feedback. You can read source files again if needed.
|
|
2068
2289
|
Call page_coverage to see current state. When done with changes, call finish again.`;
|
|
2069
|
-
await runAgent(
|
|
2290
|
+
await runAgent(finalConfig, feedbackPrompt, () => result);
|
|
2070
2291
|
return result;
|
|
2071
2292
|
}
|
|
2072
2293
|
});
|
|
@@ -2076,7 +2297,7 @@ Call page_coverage to see current state. When done with changes, call finish aga
|
|
|
2076
2297
|
summary: "KB generator agent stopped without producing AUTONOMA.md"
|
|
2077
2298
|
};
|
|
2078
2299
|
}
|
|
2079
|
-
var PageTracker;
|
|
2300
|
+
var PageTracker, FULL_COVERAGE_MAX_ROUTES, LARGE_APP_COVERAGE_FLOOR;
|
|
2080
2301
|
var init_kb_generator = __esm({
|
|
2081
2302
|
"src/agents/01-kb-generator/index.ts"() {
|
|
2082
2303
|
"use strict";
|
|
@@ -2091,14 +2312,26 @@ var init_kb_generator = __esm({
|
|
|
2091
2312
|
init_flows();
|
|
2092
2313
|
init_prompt();
|
|
2093
2314
|
PageTracker = class {
|
|
2315
|
+
// Registered pages (from pages.json) are absolute paths, but the agent reads them
|
|
2316
|
+
// with paths relative to the working directory. Canonicalize both to an absolute
|
|
2317
|
+
// path against projectRoot so coverage matches regardless of how each side spelled
|
|
2318
|
+
// it - otherwise the finish gate never clears and the agent nudges out re-reading.
|
|
2319
|
+
constructor(projectRoot) {
|
|
2320
|
+
this.projectRoot = projectRoot;
|
|
2321
|
+
}
|
|
2322
|
+
projectRoot;
|
|
2094
2323
|
registered = /* @__PURE__ */ new Set();
|
|
2095
2324
|
read = /* @__PURE__ */ new Set();
|
|
2325
|
+
normalize(filePath) {
|
|
2326
|
+
return resolve5(this.projectRoot, filePath);
|
|
2327
|
+
}
|
|
2096
2328
|
register(pages) {
|
|
2097
|
-
for (const p10 of pages) this.registered.add(p10);
|
|
2329
|
+
for (const p10 of pages) this.registered.add(this.normalize(p10));
|
|
2098
2330
|
}
|
|
2099
2331
|
markRead(filePath) {
|
|
2100
|
-
|
|
2101
|
-
|
|
2332
|
+
const normalized = this.normalize(filePath);
|
|
2333
|
+
if (this.registered.has(normalized)) {
|
|
2334
|
+
this.read.add(normalized);
|
|
2102
2335
|
}
|
|
2103
2336
|
}
|
|
2104
2337
|
unread() {
|
|
@@ -2112,16 +2345,18 @@ var init_kb_generator = __esm({
|
|
|
2112
2345
|
};
|
|
2113
2346
|
}
|
|
2114
2347
|
};
|
|
2348
|
+
FULL_COVERAGE_MAX_ROUTES = 40;
|
|
2349
|
+
LARGE_APP_COVERAGE_FLOOR = 0.5;
|
|
2115
2350
|
}
|
|
2116
2351
|
});
|
|
2117
2352
|
|
|
2118
2353
|
// src/agents/04-recipe-builder/entity-order.ts
|
|
2119
|
-
import { readFile as
|
|
2120
|
-
import { join as
|
|
2354
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2355
|
+
import { join as join16 } from "path";
|
|
2121
2356
|
import matter2 from "gray-matter";
|
|
2122
|
-
import { z as
|
|
2357
|
+
import { z as z14 } from "zod";
|
|
2123
2358
|
async function parseEntityAudit(outputDir) {
|
|
2124
|
-
const raw = await
|
|
2359
|
+
const raw = await readFile10(join16(outputDir, "entity-audit.md"), "utf-8");
|
|
2125
2360
|
try {
|
|
2126
2361
|
const parsed = frontmatterSchema.safeParse(matter2(raw).data);
|
|
2127
2362
|
if (parsed.success && parsed.data.models.length > 0) {
|
|
@@ -2282,22 +2517,22 @@ var init_entity_order = __esm({
|
|
|
2282
2517
|
"src/agents/04-recipe-builder/entity-order.ts"() {
|
|
2283
2518
|
"use strict";
|
|
2284
2519
|
init_esm_shims();
|
|
2285
|
-
createdBySchema =
|
|
2286
|
-
owner:
|
|
2287
|
-
via:
|
|
2288
|
-
why:
|
|
2520
|
+
createdBySchema = z14.object({
|
|
2521
|
+
owner: z14.string(),
|
|
2522
|
+
via: z14.string().optional(),
|
|
2523
|
+
why: z14.string().optional()
|
|
2289
2524
|
});
|
|
2290
|
-
auditedModelSchema =
|
|
2291
|
-
name:
|
|
2292
|
-
independently_created:
|
|
2293
|
-
creation_file:
|
|
2294
|
-
creation_function:
|
|
2295
|
-
side_effects:
|
|
2525
|
+
auditedModelSchema = z14.object({
|
|
2526
|
+
name: z14.string(),
|
|
2527
|
+
independently_created: z14.coerce.boolean().default(false),
|
|
2528
|
+
creation_file: z14.string().optional(),
|
|
2529
|
+
creation_function: z14.string().optional(),
|
|
2530
|
+
side_effects: z14.array(z14.string()).optional(),
|
|
2296
2531
|
// Tolerate a stray `created_by:` with no entries (parsed as null by YAML).
|
|
2297
|
-
created_by:
|
|
2532
|
+
created_by: z14.array(createdBySchema).nullish().transform((v) => v ?? [])
|
|
2298
2533
|
});
|
|
2299
|
-
frontmatterSchema =
|
|
2300
|
-
models:
|
|
2534
|
+
frontmatterSchema = z14.object({
|
|
2535
|
+
models: z14.array(auditedModelSchema).nullish().transform((v) => v ?? [])
|
|
2301
2536
|
});
|
|
2302
2537
|
}
|
|
2303
2538
|
});
|
|
@@ -2364,12 +2599,12 @@ var init_audit_table = __esm({
|
|
|
2364
2599
|
});
|
|
2365
2600
|
|
|
2366
2601
|
// src/agents/02-entity-audit/prompt.ts
|
|
2367
|
-
var
|
|
2602
|
+
var SYSTEM_PROMPT4;
|
|
2368
2603
|
var init_prompt2 = __esm({
|
|
2369
2604
|
"src/agents/02-entity-audit/prompt.ts"() {
|
|
2370
2605
|
"use strict";
|
|
2371
2606
|
init_esm_shims();
|
|
2372
|
-
|
|
2607
|
+
SYSTEM_PROMPT4 = `You audit a codebase to discover EVERY database model and every way each is created. You must find ALL models - missing one means the test data layer has a gap. This audit drives factory generation and scenario planning.
|
|
2373
2608
|
|
|
2374
2609
|
## The two orthogonal questions
|
|
2375
2610
|
|
|
@@ -2507,17 +2742,17 @@ var entity_audit_exports = {};
|
|
|
2507
2742
|
__export(entity_audit_exports, {
|
|
2508
2743
|
runEntityAudit: () => runEntityAudit
|
|
2509
2744
|
});
|
|
2510
|
-
import { readFile as
|
|
2511
|
-
import { join as
|
|
2512
|
-
import { tool as
|
|
2745
|
+
import { readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
|
|
2746
|
+
import { join as join17 } from "path";
|
|
2747
|
+
import { tool as tool12 } from "ai";
|
|
2513
2748
|
import { glob as glob4 } from "glob";
|
|
2514
|
-
import { z as
|
|
2749
|
+
import { z as z15 } from "zod";
|
|
2515
2750
|
function buildRegisterModelsTool(tracker) {
|
|
2516
|
-
return
|
|
2751
|
+
return tool12({
|
|
2517
2752
|
description: "Register ALL database models discovered via grep. Call this ONCE after grepping for model definitions. After registering, use next_model to process them one at a time.",
|
|
2518
|
-
inputSchema:
|
|
2519
|
-
models:
|
|
2520
|
-
framework:
|
|
2753
|
+
inputSchema: z15.object({
|
|
2754
|
+
models: z15.array(z15.string()).describe("All model/table names found by grep"),
|
|
2755
|
+
framework: z15.string().describe("Database framework detected (e.g. 'sqlalchemy', 'prisma', 'drizzle')")
|
|
2521
2756
|
}),
|
|
2522
2757
|
execute: async (input) => {
|
|
2523
2758
|
tracker.register(input.models);
|
|
@@ -2531,9 +2766,9 @@ function buildRegisterModelsTool(tracker) {
|
|
|
2531
2766
|
});
|
|
2532
2767
|
}
|
|
2533
2768
|
function buildNextModelTool(tracker) {
|
|
2534
|
-
return
|
|
2769
|
+
return tool12({
|
|
2535
2770
|
description: "Get the next model to audit from the queue. If you called next_model before without calling mark_model_audited, the previous model is auto-skipped (marked as no creation path found). Returns done:true when all models are processed.",
|
|
2536
|
-
inputSchema:
|
|
2771
|
+
inputSchema: z15.object({}),
|
|
2537
2772
|
execute: async () => {
|
|
2538
2773
|
const next = tracker.nextModel();
|
|
2539
2774
|
if (!next) {
|
|
@@ -2551,19 +2786,19 @@ function buildNextModelTool(tracker) {
|
|
|
2551
2786
|
});
|
|
2552
2787
|
}
|
|
2553
2788
|
function buildMarkModelAuditedTool(tracker) {
|
|
2554
|
-
return
|
|
2789
|
+
return tool12({
|
|
2555
2790
|
description: "Mark a model as audited after you have determined its creation paths. Call this for EACH model after reading its creation code and determining independently_created + created_by. Include creation_function (e.g. 'UserService.create'), side_effects (list of things the creation does beyond the model itself), and for each created_by entry include owner, via (function name), and why (one sentence explaining the relationship).",
|
|
2556
|
-
inputSchema:
|
|
2557
|
-
model:
|
|
2558
|
-
independently_created:
|
|
2559
|
-
creation_file:
|
|
2560
|
-
creation_function:
|
|
2561
|
-
side_effects:
|
|
2562
|
-
created_by:
|
|
2563
|
-
|
|
2564
|
-
owner:
|
|
2565
|
-
via:
|
|
2566
|
-
why:
|
|
2791
|
+
inputSchema: z15.object({
|
|
2792
|
+
model: z15.string().describe("Model name"),
|
|
2793
|
+
independently_created: z15.boolean(),
|
|
2794
|
+
creation_file: z15.string().optional().describe("File containing the creation function"),
|
|
2795
|
+
creation_function: z15.string().optional().describe("Function/method name (e.g. 'UserService.create' or 'create_user')"),
|
|
2796
|
+
side_effects: z15.array(z15.string()).optional().describe("Side effects of creation (e.g. 'creates default Settings row', 'hashes password')"),
|
|
2797
|
+
created_by: z15.array(
|
|
2798
|
+
z15.object({
|
|
2799
|
+
owner: z15.string().describe("Owner model name"),
|
|
2800
|
+
via: z15.string().optional().describe("Function that creates this model (e.g. 'OrganizationService.create')"),
|
|
2801
|
+
why: z15.string().optional().describe("One sentence explaining why this model is created as a side effect")
|
|
2567
2802
|
})
|
|
2568
2803
|
).describe("List of owner models that create this as a side effect, empty array if none")
|
|
2569
2804
|
}),
|
|
@@ -2600,18 +2835,18 @@ function buildMarkModelAuditedTool(tracker) {
|
|
|
2600
2835
|
});
|
|
2601
2836
|
}
|
|
2602
2837
|
function buildModelCoverageTool(tracker) {
|
|
2603
|
-
return
|
|
2838
|
+
return tool12({
|
|
2604
2839
|
description: "Check how many registered models you've audited vs how many remain.",
|
|
2605
|
-
inputSchema:
|
|
2840
|
+
inputSchema: z15.object({}),
|
|
2606
2841
|
execute: async () => tracker.coverage()
|
|
2607
2842
|
});
|
|
2608
2843
|
}
|
|
2609
2844
|
function buildFinishTool2(tracker, onFinish) {
|
|
2610
|
-
return
|
|
2845
|
+
return tool12({
|
|
2611
2846
|
description: "Call when entity audit is complete. BLOCKED if there are unaudited models - call model_coverage first to check.",
|
|
2612
|
-
inputSchema:
|
|
2613
|
-
summary:
|
|
2614
|
-
artifacts:
|
|
2847
|
+
inputSchema: z15.object({
|
|
2848
|
+
summary: z15.string().describe("Summary of the audit"),
|
|
2849
|
+
artifacts: z15.array(z15.string()).describe("Files written")
|
|
2615
2850
|
}),
|
|
2616
2851
|
execute: async (input) => {
|
|
2617
2852
|
const cov = tracker.coverage();
|
|
@@ -2639,7 +2874,7 @@ async function findPrismaSchema(projectRoot) {
|
|
|
2639
2874
|
return candidates[0] ?? void 0;
|
|
2640
2875
|
}
|
|
2641
2876
|
async function extractPrismaModels(schemaPath) {
|
|
2642
|
-
const content = await
|
|
2877
|
+
const content = await readFile11(schemaPath, "utf-8");
|
|
2643
2878
|
return content.split("\n").filter((line) => line.startsWith("model ")).map((line) => line.split(/\s+/)[1]).filter((name) => name != null);
|
|
2644
2879
|
}
|
|
2645
2880
|
async function detectFrameworkAndModels(projectRoot) {
|
|
@@ -2662,8 +2897,11 @@ async function runEntityAudit(input) {
|
|
|
2662
2897
|
tracker.initQueue();
|
|
2663
2898
|
preRegisteredCount = detection.models.length;
|
|
2664
2899
|
}
|
|
2665
|
-
const { logger, onStepFinish } = buildDefaultStepLogger("entity-audit",
|
|
2666
|
-
const
|
|
2900
|
+
const { logger, onStepFinish } = buildDefaultStepLogger("entity-audit", MAX_STEPS2);
|
|
2901
|
+
const scopeBlock = input.scopeHint != null ? `
|
|
2902
|
+
${input.scopeHint}
|
|
2903
|
+
` : "";
|
|
2904
|
+
const contextBlock = (input.projectContext ? "\n" + formatContext(input.projectContext) + "\n" : "") + scopeBlock + formatRetryGuidance(input.retryGuidance);
|
|
2667
2905
|
const preRegBlock = preRegisteredCount > 0 ? `
|
|
2668
2906
|
## Pre-registered models (${preRegisteredCount} found via ${detection.framework} schema at ${detection.schemaFile})
|
|
2669
2907
|
|
|
@@ -2693,9 +2931,9 @@ After every 10 mark_model_audited calls, use write_file to update entity-audit.m
|
|
|
2693
2931
|
write_file already targets the output directory - use just the filename.`;
|
|
2694
2932
|
const agentConfig = {
|
|
2695
2933
|
id: "entity-audit",
|
|
2696
|
-
systemPrompt:
|
|
2934
|
+
systemPrompt: SYSTEM_PROMPT4,
|
|
2697
2935
|
model,
|
|
2698
|
-
maxSteps:
|
|
2936
|
+
maxSteps: MAX_STEPS2,
|
|
2699
2937
|
tools: async (heartbeat) => {
|
|
2700
2938
|
const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat);
|
|
2701
2939
|
return {
|
|
@@ -2723,8 +2961,8 @@ ${formatException(err)}`);
|
|
|
2723
2961
|
logger.summary();
|
|
2724
2962
|
const writeCanonicalAudit = async () => {
|
|
2725
2963
|
if (tracker.auditedModels.size === 0) return void 0;
|
|
2726
|
-
const auditPath =
|
|
2727
|
-
await
|
|
2964
|
+
const auditPath = join17(input.outputDir, "entity-audit.md");
|
|
2965
|
+
await writeFile6(auditPath, tracker.generateAuditMarkdown(), "utf-8");
|
|
2728
2966
|
return auditPath;
|
|
2729
2967
|
};
|
|
2730
2968
|
const canonicalPath = await writeCanonicalAudit();
|
|
@@ -2761,9 +2999,9 @@ When done with changes, call finish again.`;
|
|
|
2761
2999
|
}
|
|
2762
3000
|
});
|
|
2763
3001
|
if (!reviewed) {
|
|
2764
|
-
const auditPath =
|
|
3002
|
+
const auditPath = join17(input.outputDir, "entity-audit.md");
|
|
2765
3003
|
try {
|
|
2766
|
-
await
|
|
3004
|
+
await readFile11(auditPath, "utf-8");
|
|
2767
3005
|
return {
|
|
2768
3006
|
success: true,
|
|
2769
3007
|
artifacts: ["entity-audit.md"],
|
|
@@ -2779,7 +3017,7 @@ When done with changes, call finish again.`;
|
|
|
2779
3017
|
summary: agentError ? `Entity audit failed: ${agentError}` : "Entity audit agent stopped without producing entity-audit.md"
|
|
2780
3018
|
};
|
|
2781
3019
|
}
|
|
2782
|
-
var ModelTracker;
|
|
3020
|
+
var MAX_STEPS2, ModelTracker;
|
|
2783
3021
|
var init_entity_audit = __esm({
|
|
2784
3022
|
"src/agents/02-entity-audit/index.ts"() {
|
|
2785
3023
|
"use strict";
|
|
@@ -2794,6 +3032,7 @@ var init_entity_audit = __esm({
|
|
|
2794
3032
|
init_ask_user();
|
|
2795
3033
|
init_audit_table();
|
|
2796
3034
|
init_prompt2();
|
|
3035
|
+
MAX_STEPS2 = 400;
|
|
2797
3036
|
ModelTracker = class {
|
|
2798
3037
|
registered = /* @__PURE__ */ new Set();
|
|
2799
3038
|
auditedModels = /* @__PURE__ */ new Map();
|
|
@@ -2904,11 +3143,11 @@ ${duals.length > 0 ? duals.map((m) => `- **${m.name}** - standalone: ${m.creatio
|
|
|
2904
3143
|
});
|
|
2905
3144
|
|
|
2906
3145
|
// src/core/parse-entity-audit.ts
|
|
2907
|
-
import { readFile as
|
|
2908
|
-
import { join as
|
|
3146
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
3147
|
+
import { join as join18 } from "path";
|
|
2909
3148
|
async function parseEntityNames(outputDir) {
|
|
2910
3149
|
try {
|
|
2911
|
-
const content = await
|
|
3150
|
+
const content = await readFile12(join18(outputDir, "entity-audit.md"), "utf-8");
|
|
2912
3151
|
const names = [];
|
|
2913
3152
|
for (const line of content.split("\n")) {
|
|
2914
3153
|
const match = line.match(/^\s+-\s+name:\s+(.+)$/);
|
|
@@ -2981,13 +3220,13 @@ values; the recipe builder generates the exact records from what you write.`;
|
|
|
2981
3220
|
});
|
|
2982
3221
|
|
|
2983
3222
|
// src/agents/03-scenario-recipe/scenario-table.ts
|
|
2984
|
-
import { readFile as
|
|
2985
|
-
import { join as
|
|
3223
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
3224
|
+
import { join as join19 } from "path";
|
|
2986
3225
|
import matter3 from "gray-matter";
|
|
2987
3226
|
async function parseScenario(outputDir) {
|
|
2988
3227
|
let raw;
|
|
2989
3228
|
try {
|
|
2990
|
-
raw = await
|
|
3229
|
+
raw = await readFile13(join19(outputDir, "scenarios.md"), "utf-8");
|
|
2991
3230
|
} catch {
|
|
2992
3231
|
return { scenarioNames: [], entityTypes: [] };
|
|
2993
3232
|
}
|
|
@@ -3068,22 +3307,22 @@ __export(scenario_recipe_exports, {
|
|
|
3068
3307
|
feedbackToScenario: () => feedbackToScenario,
|
|
3069
3308
|
runScenarioRecipe: () => runScenarioRecipe
|
|
3070
3309
|
});
|
|
3071
|
-
import { readFile as
|
|
3072
|
-
import { join as
|
|
3073
|
-
import { tool as
|
|
3074
|
-
import { z as
|
|
3310
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
3311
|
+
import { join as join20 } from "path";
|
|
3312
|
+
import { tool as tool13 } from "ai";
|
|
3313
|
+
import { z as z16 } from "zod";
|
|
3075
3314
|
function buildFinishTool3(requiredEntities, outputDir, onFinish) {
|
|
3076
|
-
return
|
|
3315
|
+
return tool13({
|
|
3077
3316
|
description: "Call when scenario design is complete and scenarios.md is written. BLOCKED if any required entities are missing from the scenario.",
|
|
3078
|
-
inputSchema:
|
|
3079
|
-
summary:
|
|
3080
|
-
entityCount:
|
|
3081
|
-
artifacts:
|
|
3317
|
+
inputSchema: z16.object({
|
|
3318
|
+
summary: z16.string().describe("Summary of the scenario"),
|
|
3319
|
+
entityCount: z16.number().describe("Number of entity types in the scenario"),
|
|
3320
|
+
artifacts: z16.array(z16.string()).describe("Files written")
|
|
3082
3321
|
}),
|
|
3083
3322
|
execute: async (input) => {
|
|
3084
3323
|
let content;
|
|
3085
3324
|
try {
|
|
3086
|
-
content = await
|
|
3325
|
+
content = await readFile14(join20(outputDir, "scenarios.md"), "utf-8");
|
|
3087
3326
|
} catch {
|
|
3088
3327
|
return { error: "Cannot finish: scenarios.md not found. Write it first." };
|
|
3089
3328
|
}
|
|
@@ -3118,7 +3357,10 @@ async function runScenarioRecipe(input) {
|
|
|
3118
3357
|
const model = getModel(input.modelId);
|
|
3119
3358
|
let result;
|
|
3120
3359
|
const { logger, onStepFinish } = buildDefaultStepLogger("scenario", 40);
|
|
3121
|
-
const
|
|
3360
|
+
const scopeBlock = input.scopeHint != null ? `
|
|
3361
|
+
${input.scopeHint}
|
|
3362
|
+
` : "";
|
|
3363
|
+
const contextBlock = (input.projectContext ? "\n" + formatContext(input.projectContext) + "\n" : "") + scopeBlock + formatRetryGuidance(input.retryGuidance);
|
|
3122
3364
|
const requiredEntities = await parseEntityNames(input.outputDir);
|
|
3123
3365
|
const entityListBlock = requiredEntities.length > 0 ? `
|
|
3124
3366
|
## Required entities (${requiredEntities.length} total - ALL must appear in the scenario)
|
|
@@ -3180,9 +3422,9 @@ When done with changes, call finish again.`;
|
|
|
3180
3422
|
}
|
|
3181
3423
|
});
|
|
3182
3424
|
if (!reviewed) {
|
|
3183
|
-
const scenariosPath =
|
|
3425
|
+
const scenariosPath = join20(input.outputDir, "scenarios.md");
|
|
3184
3426
|
try {
|
|
3185
|
-
await
|
|
3427
|
+
await readFile14(scenariosPath, "utf-8");
|
|
3186
3428
|
return {
|
|
3187
3429
|
success: true,
|
|
3188
3430
|
artifacts: ["scenarios.md"],
|
|
@@ -3243,7 +3485,7 @@ var init_scenario_recipe = __esm({
|
|
|
3243
3485
|
|
|
3244
3486
|
// src/agents/04-recipe-builder/entity-relevance.ts
|
|
3245
3487
|
import { Output, generateText } from "ai";
|
|
3246
|
-
import { z as
|
|
3488
|
+
import { z as z17 } from "zod";
|
|
3247
3489
|
async function callRanker(model, prompt) {
|
|
3248
3490
|
const result = await generateText({
|
|
3249
3491
|
model,
|
|
@@ -3331,19 +3573,19 @@ var init_entity_relevance = __esm({
|
|
|
3331
3573
|
init_esm_shims();
|
|
3332
3574
|
init_errors();
|
|
3333
3575
|
init_model();
|
|
3334
|
-
rankedSchema =
|
|
3335
|
-
ranked:
|
|
3576
|
+
rankedSchema = z17.object({
|
|
3577
|
+
ranked: z17.array(z17.string()).describe("Every entity name, ordered most-important first.")
|
|
3336
3578
|
});
|
|
3337
3579
|
}
|
|
3338
3580
|
});
|
|
3339
3581
|
|
|
3340
3582
|
// src/core/detect-pkg-manager.ts
|
|
3341
3583
|
import { existsSync as existsSync2 } from "fs";
|
|
3342
|
-
import { join as
|
|
3584
|
+
import { join as join21 } from "path";
|
|
3343
3585
|
function detectPackageManager(projectRoot) {
|
|
3344
|
-
if (existsSync2(
|
|
3345
|
-
if (existsSync2(
|
|
3346
|
-
if (existsSync2(
|
|
3586
|
+
if (existsSync2(join21(projectRoot, "bun.lock")) || existsSync2(join21(projectRoot, "bun.lockb"))) return "bun";
|
|
3587
|
+
if (existsSync2(join21(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3588
|
+
if (existsSync2(join21(projectRoot, "yarn.lock"))) return "yarn";
|
|
3347
3589
|
return "npm";
|
|
3348
3590
|
}
|
|
3349
3591
|
function installCommand(pm, ...packages) {
|
|
@@ -3614,8 +3856,8 @@ var init_discover_schema = __esm({
|
|
|
3614
3856
|
});
|
|
3615
3857
|
|
|
3616
3858
|
// src/agents/04-recipe-builder/recipe.ts
|
|
3617
|
-
import { readFile as
|
|
3618
|
-
import { join as
|
|
3859
|
+
import { readFile as readFile15, writeFile as writeFile7 } from "fs/promises";
|
|
3860
|
+
import { join as join22 } from "path";
|
|
3619
3861
|
function collectRefs2(value, out) {
|
|
3620
3862
|
if (Array.isArray(value)) {
|
|
3621
3863
|
for (const v of value) collectRefs2(v, out);
|
|
@@ -3688,7 +3930,7 @@ function buildSubmittableRecipe(create, description) {
|
|
|
3688
3930
|
};
|
|
3689
3931
|
}
|
|
3690
3932
|
async function saveRecipe(outputDir, recipe) {
|
|
3691
|
-
await
|
|
3933
|
+
await writeFile7(join22(outputDir, RECIPE_FILE), JSON.stringify(recipe, null, 2), "utf-8");
|
|
3692
3934
|
}
|
|
3693
3935
|
var RECIPE_FILE;
|
|
3694
3936
|
var init_recipe = __esm({
|
|
@@ -3701,8 +3943,8 @@ var init_recipe = __esm({
|
|
|
3701
3943
|
});
|
|
3702
3944
|
|
|
3703
3945
|
// src/agents/04-recipe-builder/state.ts
|
|
3704
|
-
import { readFile as
|
|
3705
|
-
import { join as
|
|
3946
|
+
import { readFile as readFile16, writeFile as writeFile8 } from "fs/promises";
|
|
3947
|
+
import { join as join23 } from "path";
|
|
3706
3948
|
function adapterKey(a) {
|
|
3707
3949
|
return `${a.language}:${a.framework}`;
|
|
3708
3950
|
}
|
|
@@ -3724,7 +3966,7 @@ function initialRecipeState() {
|
|
|
3724
3966
|
}
|
|
3725
3967
|
async function loadRecipeState(outputDir) {
|
|
3726
3968
|
try {
|
|
3727
|
-
const raw = await
|
|
3969
|
+
const raw = await readFile16(join23(outputDir, STATE_FILE2), "utf-8");
|
|
3728
3970
|
const parsed = JSON.parse(raw);
|
|
3729
3971
|
return parsed;
|
|
3730
3972
|
} catch {
|
|
@@ -3732,7 +3974,7 @@ async function loadRecipeState(outputDir) {
|
|
|
3732
3974
|
}
|
|
3733
3975
|
}
|
|
3734
3976
|
async function saveRecipeState(outputDir, state) {
|
|
3735
|
-
await
|
|
3977
|
+
await writeFile8(join23(outputDir, STATE_FILE2), JSON.stringify(state, null, 2), "utf-8");
|
|
3736
3978
|
}
|
|
3737
3979
|
var ALL_ADAPTERS, ADAPTER_HINTS, STATE_FILE2;
|
|
3738
3980
|
var init_state = __esm({
|
|
@@ -3804,7 +4046,7 @@ var init_state = __esm({
|
|
|
3804
4046
|
|
|
3805
4047
|
// src/agents/04-recipe-builder/phases/failure-classifier.ts
|
|
3806
4048
|
import { Output as Output2, generateText as generateText2 } from "ai";
|
|
3807
|
-
import { z as
|
|
4049
|
+
import { z as z18 } from "zod";
|
|
3808
4050
|
function buildClassifierPrompt(args) {
|
|
3809
4051
|
const errorText = typeof args.error === "string" ? args.error : JSON.stringify(args.error, null, 2);
|
|
3810
4052
|
const phaseLine = args.phase === "teardown" ? `This was a DOWN (teardown) request. Teardown runs the developer's delete logic against data the create() step already accepted, so teardown failures are usually implementation-side (wrong delete order, foreign-key cleanup bugs) rather than caused by the recipe.` : `This was an UP (create) request - the factory tried to insert the recipe records.`;
|
|
@@ -3864,11 +4106,11 @@ var init_failure_classifier = __esm({
|
|
|
3864
4106
|
init_esm_shims();
|
|
3865
4107
|
init_errors();
|
|
3866
4108
|
init_model();
|
|
3867
|
-
classificationSchema =
|
|
3868
|
-
side:
|
|
4109
|
+
classificationSchema = z18.object({
|
|
4110
|
+
side: z18.enum(["recipe", "implementation", "unclear"]).describe(
|
|
3869
4111
|
"recipe = the test data we sent is wrong and regenerating it could fix the failure; implementation = the developer's handler/factory code is wrong and only a code change fixes it; unclear = cannot confidently attribute the failure to either side."
|
|
3870
4112
|
),
|
|
3871
|
-
reason:
|
|
4113
|
+
reason: z18.string().describe("One short, plain-language sentence explaining the verdict for the user. No code, no jargon dumps.")
|
|
3872
4114
|
});
|
|
3873
4115
|
PRIMER = `## Background - what you are looking at
|
|
3874
4116
|
|
|
@@ -3883,13 +4125,13 @@ So a failure has exactly two possible origins, and your only job is to tell them
|
|
|
3883
4125
|
});
|
|
3884
4126
|
|
|
3885
4127
|
// src/agents/04-recipe-builder/phases/entity-loop.ts
|
|
3886
|
-
import { writeFile as
|
|
4128
|
+
import { writeFile as writeFile9, readFile as readFile17 } from "fs/promises";
|
|
3887
4129
|
import { tmpdir } from "os";
|
|
3888
|
-
import { join as
|
|
4130
|
+
import { join as join24 } from "path";
|
|
3889
4131
|
import * as p4 from "@clack/prompts";
|
|
3890
|
-
import { tool as
|
|
4132
|
+
import { tool as tool14 } from "ai";
|
|
3891
4133
|
import spawn2 from "cross-spawn";
|
|
3892
|
-
import { z as
|
|
4134
|
+
import { z as z19 } from "zod";
|
|
3893
4135
|
function summarizeCompletedAliases(completedEntities, excludeName) {
|
|
3894
4136
|
return Object.entries(completedEntities).filter(([name, e]) => name !== excludeName && e.recipeData && e.recipeData.length > 0).map(([name, e]) => `${name}: aliases ${e.recipeData.map((r) => r._alias ?? "?").join(", ")}`).join("\n");
|
|
3895
4137
|
}
|
|
@@ -3904,10 +4146,10 @@ function summarizeEntityAudit(model) {
|
|
|
3904
4146
|
async function proposeRecipeData(entityName, entityIndex, totalEntities, model, outputDir, _projectRoot, completedEntities, schemaSpec) {
|
|
3905
4147
|
let result;
|
|
3906
4148
|
const { logger, onStepFinish } = buildDefaultStepLogger(`propose:${entityName}`, 20);
|
|
3907
|
-
const finishTool =
|
|
4149
|
+
const finishTool = tool14({
|
|
3908
4150
|
description: "Submit the proposed recipe data as a JSON array of records.",
|
|
3909
|
-
inputSchema:
|
|
3910
|
-
records:
|
|
4151
|
+
inputSchema: z19.object({
|
|
4152
|
+
records: z19.array(z19.record(z19.string(), z19.unknown())).describe("Array of record objects for this entity")
|
|
3911
4153
|
}),
|
|
3912
4154
|
execute: async (input) => {
|
|
3913
4155
|
result = input.records;
|
|
@@ -3950,10 +4192,10 @@ Call finish with the JSON array of records.`;
|
|
|
3950
4192
|
}
|
|
3951
4193
|
async function reviseRecipeData(entityName, entityIndex, totalEntities, current, feedback, model, outputDir, completedEntities, schemaSpec) {
|
|
3952
4194
|
let revised;
|
|
3953
|
-
const finishTool =
|
|
4195
|
+
const finishTool = tool14({
|
|
3954
4196
|
description: "Submit the fixed recipe data.",
|
|
3955
|
-
inputSchema:
|
|
3956
|
-
records:
|
|
4197
|
+
inputSchema: z19.object({
|
|
4198
|
+
records: z19.array(z19.record(z19.string(), z19.unknown()))
|
|
3957
4199
|
}),
|
|
3958
4200
|
execute: async (input) => {
|
|
3959
4201
|
revised = input.records;
|
|
@@ -4011,10 +4253,10 @@ Read scenarios.md and entity-audit.md to understand the correct aliases and sche
|
|
|
4011
4253
|
async function generateInstructions(entityName, entityIndex, totalEntities, isFirst, techStack, auditModel, recipeData, model, projectRoot, outputDir) {
|
|
4012
4254
|
let result;
|
|
4013
4255
|
const { logger, onStepFinish } = buildDefaultStepLogger(`instructions:${entityName}`, 15);
|
|
4014
|
-
const finishTool =
|
|
4256
|
+
const finishTool = tool14({
|
|
4015
4257
|
description: "Submit the implementation instructions.",
|
|
4016
|
-
inputSchema:
|
|
4017
|
-
instructions:
|
|
4258
|
+
inputSchema: z19.object({
|
|
4259
|
+
instructions: z19.string().describe("Complete, copy-pasteable implementation instructions")
|
|
4018
4260
|
}),
|
|
4019
4261
|
execute: async (input) => {
|
|
4020
4262
|
result = input.instructions;
|
|
@@ -4096,23 +4338,23 @@ async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed
|
|
|
4096
4338
|
if (p4.isCancel(action)) throw new Error("Recipe review cancelled");
|
|
4097
4339
|
if (action === "keep") return proposed;
|
|
4098
4340
|
if (action === "edit") {
|
|
4099
|
-
const tmpPath =
|
|
4100
|
-
await
|
|
4341
|
+
const tmpPath = join24(tmpdir(), `autonoma-recipe-${entityName}.json`);
|
|
4342
|
+
await writeFile9(tmpPath, JSON.stringify(proposed, null, 2), "utf-8");
|
|
4101
4343
|
const env = readEnv();
|
|
4102
4344
|
const editor = env.EDITOR ?? env.VISUAL ?? "vi";
|
|
4103
4345
|
p4.log.info(`Opening ${editor}... Save and close when done.`);
|
|
4104
|
-
const launched = await new Promise((
|
|
4346
|
+
const launched = await new Promise((resolve6) => {
|
|
4105
4347
|
const proc = spawn2(editor, [tmpPath], { stdio: "inherit" });
|
|
4106
|
-
proc.on("close", () =>
|
|
4348
|
+
proc.on("close", () => resolve6(true));
|
|
4107
4349
|
proc.on("error", (err) => {
|
|
4108
4350
|
p4.log.error(
|
|
4109
4351
|
`Couldn't open ${editor} (${err.message}). Edit this file manually, then choose "edit" again: ${tmpPath}`
|
|
4110
4352
|
);
|
|
4111
|
-
|
|
4353
|
+
resolve6(false);
|
|
4112
4354
|
});
|
|
4113
4355
|
});
|
|
4114
4356
|
if (!launched) continue;
|
|
4115
|
-
const edited = await
|
|
4357
|
+
const edited = await readFile17(tmpPath, "utf-8");
|
|
4116
4358
|
try {
|
|
4117
4359
|
proposed = JSON.parse(edited);
|
|
4118
4360
|
p4.note(JSON.stringify(proposed, null, 2), `Updated data for ${entityName}`, { format: codeNoteFormat });
|
|
@@ -4404,8 +4646,8 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
|
|
|
4404
4646
|
const secret = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
4405
4647
|
state.sharedSecret = secret;
|
|
4406
4648
|
await saveRecipeState(outputDir, state);
|
|
4407
|
-
await
|
|
4408
|
-
|
|
4649
|
+
await writeFile9(
|
|
4650
|
+
join24(outputDir, "autonoma-config.json"),
|
|
4409
4651
|
JSON.stringify({ sharedSecret: secret, endpointUrl: state.sdkEndpointUrl }, null, 2),
|
|
4410
4652
|
"utf-8"
|
|
4411
4653
|
);
|
|
@@ -4416,7 +4658,7 @@ Add this to your server's .env file and restart it.
|
|
|
4416
4658
|
This is a 64-character hex key used for HMAC-SHA256 request signing.
|
|
4417
4659
|
The same value must be set in both your server and the Autonoma dashboard.
|
|
4418
4660
|
|
|
4419
|
-
Saved to: ${
|
|
4661
|
+
Saved to: ${join24(outputDir, "autonoma-config.json")}`,
|
|
4420
4662
|
"Shared secret generated"
|
|
4421
4663
|
);
|
|
4422
4664
|
const secretReady = await p4.confirm({
|
|
@@ -4437,8 +4679,8 @@ Saved to: ${join23(outputDir, "autonoma-config.json")}`,
|
|
|
4437
4679
|
if (p4.isCancel(url)) throw new Error("Entity loop cancelled");
|
|
4438
4680
|
state.sdkEndpointUrl = url.trim() || "http://localhost:3000/api/autonoma";
|
|
4439
4681
|
await saveRecipeState(outputDir, state);
|
|
4440
|
-
await
|
|
4441
|
-
|
|
4682
|
+
await writeFile9(
|
|
4683
|
+
join24(outputDir, "autonoma-config.json"),
|
|
4442
4684
|
JSON.stringify({ sharedSecret: state.sharedSecret, endpointUrl: state.sdkEndpointUrl }, null, 2),
|
|
4443
4685
|
"utf-8"
|
|
4444
4686
|
);
|
|
@@ -4554,14 +4796,14 @@ When done, call finish with the instructions text.`;
|
|
|
4554
4796
|
|
|
4555
4797
|
// src/agents/04-recipe-builder/phases/full-validation.ts
|
|
4556
4798
|
import * as p5 from "@clack/prompts";
|
|
4557
|
-
import { tool as
|
|
4558
|
-
import { z as
|
|
4799
|
+
import { tool as tool15 } from "ai";
|
|
4800
|
+
import { z as z20 } from "zod";
|
|
4559
4801
|
async function reviseFullRecipe(current, feedback, model, outputDir, entityOrder, schemaSpec) {
|
|
4560
4802
|
let revised;
|
|
4561
|
-
const finishTool =
|
|
4803
|
+
const finishTool = tool15({
|
|
4562
4804
|
description: "Submit the revised full recipe: an object mapping each entity name to its array of records.",
|
|
4563
|
-
inputSchema:
|
|
4564
|
-
recipe:
|
|
4805
|
+
inputSchema: z20.object({
|
|
4806
|
+
recipe: z20.record(z20.string(), z20.array(z20.record(z20.string(), z20.unknown())))
|
|
4565
4807
|
}),
|
|
4566
4808
|
execute: async (input) => {
|
|
4567
4809
|
revised = input.recipe;
|
|
@@ -4816,18 +5058,18 @@ var init_submit = __esm({
|
|
|
4816
5058
|
|
|
4817
5059
|
// src/agents/04-recipe-builder/phases/tech-detect.ts
|
|
4818
5060
|
import * as p7 from "@clack/prompts";
|
|
4819
|
-
import { tool as
|
|
4820
|
-
import { z as
|
|
5061
|
+
import { tool as tool16 } from "ai";
|
|
5062
|
+
import { z as z21 } from "zod";
|
|
4821
5063
|
async function detectTechStack(projectRoot, modelId, nonInteractive) {
|
|
4822
5064
|
const model = getModel(modelId);
|
|
4823
5065
|
const ignorePatterns = await loadGitignorePatterns(projectRoot);
|
|
4824
5066
|
let detected;
|
|
4825
5067
|
const { logger, onStepFinish } = buildDefaultStepLogger("tech-detect", 10);
|
|
4826
|
-
const finishTool =
|
|
5068
|
+
const finishTool = tool16({
|
|
4827
5069
|
description: "Report the detected backend technology stack.",
|
|
4828
|
-
inputSchema:
|
|
4829
|
-
language:
|
|
4830
|
-
framework:
|
|
5070
|
+
inputSchema: z21.object({
|
|
5071
|
+
language: z21.string().describe("Programming language: typescript, python, go, ruby, java, php, rust, elixir"),
|
|
5072
|
+
framework: z21.string().describe(
|
|
4831
5073
|
"Web framework: express, node, hono, web, flask, fastapi, django, gin, rails, rack, spring, laravel, axum, actix, plug"
|
|
4832
5074
|
)
|
|
4833
5075
|
}),
|
|
@@ -4838,7 +5080,7 @@ async function detectTechStack(projectRoot, modelId, nonInteractive) {
|
|
|
4838
5080
|
});
|
|
4839
5081
|
const agentConfig = {
|
|
4840
5082
|
id: "tech-detect",
|
|
4841
|
-
systemPrompt:
|
|
5083
|
+
systemPrompt: SYSTEM_PROMPT5,
|
|
4842
5084
|
model,
|
|
4843
5085
|
maxSteps: 10,
|
|
4844
5086
|
tools: (_heartbeat) => ({
|
|
@@ -4894,7 +5136,7 @@ async function detectTechStack(projectRoot, modelId, nonInteractive) {
|
|
|
4894
5136
|
p7.log.success(`Using ${adapterLabel(adapter)} - SDK: ${adapter.sdkPackage}, Adapter: ${adapter.adapterPackage}`);
|
|
4895
5137
|
return adapter;
|
|
4896
5138
|
}
|
|
4897
|
-
var DOCS_BASE,
|
|
5139
|
+
var DOCS_BASE, SYSTEM_PROMPT5;
|
|
4898
5140
|
var init_tech_detect = __esm({
|
|
4899
5141
|
"src/agents/04-recipe-builder/phases/tech-detect.ts"() {
|
|
4900
5142
|
"use strict";
|
|
@@ -4905,7 +5147,7 @@ var init_tech_detect = __esm({
|
|
|
4905
5147
|
init_tools();
|
|
4906
5148
|
init_state();
|
|
4907
5149
|
DOCS_BASE = "https://docs.autonoma.app";
|
|
4908
|
-
|
|
5150
|
+
SYSTEM_PROMPT5 = `You are a backend technology detector. Your job is to identify the programming language and web framework used by a project's backend/API server.
|
|
4909
5151
|
|
|
4910
5152
|
Explore the project files to detect:
|
|
4911
5153
|
1. The programming language (check package.json, requirements.txt, go.mod, Gemfile, pom.xml, composer.json, Cargo.toml, mix.exs)
|
|
@@ -4920,8 +5162,8 @@ var recipe_builder_exports = {};
|
|
|
4920
5162
|
__export(recipe_builder_exports, {
|
|
4921
5163
|
runRecipeBuilder: () => runRecipeBuilder
|
|
4922
5164
|
});
|
|
4923
|
-
import { readFile as
|
|
4924
|
-
import { join as
|
|
5165
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
5166
|
+
import { join as join25 } from "path";
|
|
4925
5167
|
import * as p8 from "@clack/prompts";
|
|
4926
5168
|
async function runRecipeBuilder(input) {
|
|
4927
5169
|
const model = getModel(input.modelId);
|
|
@@ -4935,7 +5177,7 @@ async function runRecipeBuilder(input) {
|
|
|
4935
5177
|
state.techStack = await detectTechStack(input.projectRoot, input.modelId, input.nonInteractive);
|
|
4936
5178
|
let importanceRank;
|
|
4937
5179
|
try {
|
|
4938
|
-
const auditMarkdown = await
|
|
5180
|
+
const auditMarkdown = await readFile18(join25(input.outputDir, "entity-audit.md"), "utf-8");
|
|
4939
5181
|
importanceRank = await rankEntitiesByImportance(models, auditMarkdown, model);
|
|
4940
5182
|
} catch {
|
|
4941
5183
|
importanceRank = void 0;
|
|
@@ -5031,21 +5273,21 @@ var init_recipe_builder = __esm({
|
|
|
5031
5273
|
});
|
|
5032
5274
|
|
|
5033
5275
|
// src/agents/05-test-generator/rubrics.ts
|
|
5034
|
-
import { z as
|
|
5276
|
+
import { z as z22 } from "zod";
|
|
5035
5277
|
function reviewResultSchema(shape) {
|
|
5036
|
-
return
|
|
5278
|
+
return z22.object(shape);
|
|
5037
5279
|
}
|
|
5038
5280
|
var dimensionResultSchema, reviewResultRecordSchema, structuralIntentRubric, flowCompletenessRubric, uiTextRubric, dataAccuracyRubric, ALL_RUBRICS;
|
|
5039
5281
|
var init_rubrics = __esm({
|
|
5040
5282
|
"src/agents/05-test-generator/rubrics.ts"() {
|
|
5041
5283
|
"use strict";
|
|
5042
5284
|
init_esm_shims();
|
|
5043
|
-
dimensionResultSchema =
|
|
5044
|
-
pass:
|
|
5045
|
-
evidence:
|
|
5046
|
-
suggestion:
|
|
5285
|
+
dimensionResultSchema = z22.object({
|
|
5286
|
+
pass: z22.boolean(),
|
|
5287
|
+
evidence: z22.string().describe("What you checked and found - cite file paths, line content, or specific strings"),
|
|
5288
|
+
suggestion: z22.string().optional().describe("What the planner agent should fix, if failing")
|
|
5047
5289
|
});
|
|
5048
|
-
reviewResultRecordSchema =
|
|
5290
|
+
reviewResultRecordSchema = z22.record(z22.string(), dimensionResultSchema);
|
|
5049
5291
|
structuralIntentRubric = {
|
|
5050
5292
|
name: "structural-intent",
|
|
5051
5293
|
maxSteps: 8,
|
|
@@ -5219,12 +5461,12 @@ When done reviewing, call finish with your structured evaluation.`
|
|
|
5219
5461
|
// src/agents/05-test-generator/review-pass.ts
|
|
5220
5462
|
import { basename as basename2 } from "path";
|
|
5221
5463
|
import "ai";
|
|
5222
|
-
import { tool as
|
|
5464
|
+
import { tool as tool17 } from "ai";
|
|
5223
5465
|
async function runReviewPass(testContent, testPath, rubric, projectRoot, model, scenarioData) {
|
|
5224
5466
|
let result;
|
|
5225
5467
|
const agentLabel = `review:${rubric.name}:${basename2(testPath)}`;
|
|
5226
5468
|
const { onStepFinish } = buildDefaultStepLogger(agentLabel, rubric.maxSteps);
|
|
5227
|
-
const finishTool =
|
|
5469
|
+
const finishTool = tool17({
|
|
5228
5470
|
description: "Submit your structured review. Every dimension must have evidence from your investigation.",
|
|
5229
5471
|
inputSchema: rubric.resultSchema,
|
|
5230
5472
|
execute: async (input) => {
|
|
@@ -5282,8 +5524,8 @@ var init_review_pass = __esm({
|
|
|
5282
5524
|
});
|
|
5283
5525
|
|
|
5284
5526
|
// src/agents/05-test-generator/review.ts
|
|
5285
|
-
import { readFile as
|
|
5286
|
-
import { join as
|
|
5527
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
5528
|
+
import { join as join26, relative as relative6, basename as basename3 } from "path";
|
|
5287
5529
|
import "ai";
|
|
5288
5530
|
import { glob as glob5 } from "glob";
|
|
5289
5531
|
async function reviewSingleTest(testContent, testPath, projectRoot, model, scenarioData) {
|
|
@@ -5312,19 +5554,19 @@ async function reviewSingleTest(testContent, testPath, projectRoot, model, scena
|
|
|
5312
5554
|
return merged;
|
|
5313
5555
|
}
|
|
5314
5556
|
async function runConsolidatedReview(outputDir, projectRoot, model) {
|
|
5315
|
-
const testsDir =
|
|
5557
|
+
const testsDir = join26(outputDir, "qa-tests");
|
|
5316
5558
|
const logger = createStepLogger("review", 5);
|
|
5317
5559
|
let scenarioData;
|
|
5318
5560
|
try {
|
|
5319
|
-
scenarioData = await
|
|
5561
|
+
scenarioData = await readFile19(join26(outputDir, "scenarios.md"), "utf-8");
|
|
5320
5562
|
} catch {
|
|
5321
5563
|
}
|
|
5322
|
-
const testFiles = await glob5(
|
|
5564
|
+
const testFiles = await glob5(join26(testsDir, "**/*.md"));
|
|
5323
5565
|
const tests = [];
|
|
5324
5566
|
for (const testPath of testFiles) {
|
|
5325
5567
|
if (basename3(testPath) === "INDEX.md") continue;
|
|
5326
5568
|
if (testPath.includes("/_invalid/")) continue;
|
|
5327
|
-
const content = await
|
|
5569
|
+
const content = await readFile19(testPath, "utf-8");
|
|
5328
5570
|
const flowMatch = content.match(/^---\n[\s\S]*?flow:\s*["']?([^"'\n]+)["']?\s*\n[\s\S]*?---/m);
|
|
5329
5571
|
tests.push({
|
|
5330
5572
|
path: testPath,
|
|
@@ -5407,17 +5649,17 @@ var init_review2 = __esm({
|
|
|
5407
5649
|
});
|
|
5408
5650
|
|
|
5409
5651
|
// src/agents/00b-feature-discovery/index.ts
|
|
5410
|
-
import { readFile as
|
|
5411
|
-
import { join as
|
|
5412
|
-
import { tool as
|
|
5413
|
-
import { z as
|
|
5652
|
+
import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
5653
|
+
import { join as join27 } from "path";
|
|
5654
|
+
import { tool as tool18 } from "ai";
|
|
5655
|
+
import { z as z23 } from "zod";
|
|
5414
5656
|
async function saveFeatures(outputDir, features) {
|
|
5415
5657
|
const obj = Object.fromEntries(features);
|
|
5416
|
-
await
|
|
5658
|
+
await writeFile10(join27(outputDir, FEATURES_FILE), JSON.stringify(obj, null, 2), "utf-8");
|
|
5417
5659
|
}
|
|
5418
5660
|
async function loadFeatures(outputDir) {
|
|
5419
5661
|
try {
|
|
5420
|
-
const raw = await
|
|
5662
|
+
const raw = await readFile20(join27(outputDir, FEATURES_FILE), "utf-8");
|
|
5421
5663
|
const obj = JSON.parse(raw);
|
|
5422
5664
|
return new Map(Object.entries(obj));
|
|
5423
5665
|
} catch {
|
|
@@ -5441,17 +5683,17 @@ ${pagesDescription}
|
|
|
5441
5683
|
Process every page. Call add_feature for each sub-feature you discover. When done, call finish.`;
|
|
5442
5684
|
const agentConfig = {
|
|
5443
5685
|
id: "feature-discovery",
|
|
5444
|
-
systemPrompt:
|
|
5686
|
+
systemPrompt: SYSTEM_PROMPT6,
|
|
5445
5687
|
model,
|
|
5446
5688
|
maxSteps: 300,
|
|
5447
5689
|
tools: async (heartbeat) => {
|
|
5448
5690
|
const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat);
|
|
5449
5691
|
return {
|
|
5450
5692
|
...tools,
|
|
5451
|
-
add_feature:
|
|
5693
|
+
add_feature: tool18({
|
|
5452
5694
|
description: "Add a discovered sub-feature",
|
|
5453
5695
|
inputSchema: Feature.extend({
|
|
5454
|
-
id:
|
|
5696
|
+
id: z23.string().min(1).describe("Unique kebab-case ID (e.g. 'settings-notifications-tab')")
|
|
5455
5697
|
}),
|
|
5456
5698
|
execute: (featureInput) => {
|
|
5457
5699
|
const { id, ...rest } = featureInput;
|
|
@@ -5463,19 +5705,19 @@ Process every page. Call add_feature for each sub-feature you discover. When don
|
|
|
5463
5705
|
return `Feature "${id}" added (${collector.features.size} total)`;
|
|
5464
5706
|
}
|
|
5465
5707
|
}),
|
|
5466
|
-
view_features:
|
|
5708
|
+
view_features: tool18({
|
|
5467
5709
|
description: "View all discovered features so far",
|
|
5468
|
-
inputSchema:
|
|
5710
|
+
inputSchema: z23.object({}),
|
|
5469
5711
|
execute: () => collector.viewFeatures()
|
|
5470
5712
|
}),
|
|
5471
|
-
view_pages:
|
|
5713
|
+
view_pages: tool18({
|
|
5472
5714
|
description: "View the pages list to know what to analyze",
|
|
5473
|
-
inputSchema:
|
|
5715
|
+
inputSchema: z23.object({}),
|
|
5474
5716
|
execute: () => pagesDescription
|
|
5475
5717
|
}),
|
|
5476
|
-
finish:
|
|
5718
|
+
finish: tool18({
|
|
5477
5719
|
description: "Signal that feature discovery is complete",
|
|
5478
|
-
inputSchema:
|
|
5720
|
+
inputSchema: z23.object({ summary: z23.string() }),
|
|
5479
5721
|
execute: async (finishInput) => {
|
|
5480
5722
|
result = {
|
|
5481
5723
|
success: true,
|
|
@@ -5497,7 +5739,7 @@ Process every page. Call add_feature for each sub-feature you discover. When don
|
|
|
5497
5739
|
}
|
|
5498
5740
|
return collector.features;
|
|
5499
5741
|
}
|
|
5500
|
-
var FEATURES_FILE, Feature, FeatureCollector,
|
|
5742
|
+
var FEATURES_FILE, Feature, FeatureCollector, SYSTEM_PROMPT6;
|
|
5501
5743
|
var init_b_feature_discovery = __esm({
|
|
5502
5744
|
"src/agents/00b-feature-discovery/index.ts"() {
|
|
5503
5745
|
"use strict";
|
|
@@ -5506,13 +5748,13 @@ var init_b_feature_discovery = __esm({
|
|
|
5506
5748
|
init_model();
|
|
5507
5749
|
init_tools();
|
|
5508
5750
|
FEATURES_FILE = "features.json";
|
|
5509
|
-
Feature =
|
|
5510
|
-
name:
|
|
5511
|
-
type:
|
|
5512
|
-
parentPagePath:
|
|
5513
|
-
sourceFiles:
|
|
5514
|
-
interactiveElements:
|
|
5515
|
-
description:
|
|
5751
|
+
Feature = z23.object({
|
|
5752
|
+
name: z23.string().min(1).describe("Human-readable name (e.g. 'Settings > Notifications Tab', 'Create Project Modal')"),
|
|
5753
|
+
type: z23.enum(["tab", "modal", "form", "table", "wizard", "nested-route", "complex-component"]),
|
|
5754
|
+
parentPagePath: z23.string().min(1).describe("The page path this feature belongs to (from the pages list)"),
|
|
5755
|
+
sourceFiles: z23.array(z23.string()).min(1).describe("Relative paths to the source files for this sub-feature"),
|
|
5756
|
+
interactiveElements: z23.number().int().min(0).describe("Count of interactive elements found (buttons, inputs, toggles, etc.)"),
|
|
5757
|
+
description: z23.string().min(10).describe("What this sub-feature does")
|
|
5516
5758
|
});
|
|
5517
5759
|
FeatureCollector = class {
|
|
5518
5760
|
features = /* @__PURE__ */ new Map();
|
|
@@ -5543,7 +5785,7 @@ ${page}:`);
|
|
|
5543
5785
|
return lines.join("\n");
|
|
5544
5786
|
}
|
|
5545
5787
|
};
|
|
5546
|
-
|
|
5788
|
+
SYSTEM_PROMPT6 = `You are a feature discovery agent. Your job is to explore each page's source code and discover all sub-features that deserve their own test coverage.
|
|
5547
5789
|
|
|
5548
5790
|
You will be given a list of pages. For each page, you must:
|
|
5549
5791
|
1. Read the page's source file
|
|
@@ -5595,16 +5837,16 @@ Use kebab-case IDs that indicate the parent page and feature type:
|
|
|
5595
5837
|
});
|
|
5596
5838
|
|
|
5597
5839
|
// src/agents/05-test-generator/graph.ts
|
|
5598
|
-
import { readFile as
|
|
5599
|
-
import { join as
|
|
5840
|
+
import { readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
|
|
5841
|
+
import { join as join28 } from "path";
|
|
5600
5842
|
async function saveBfsState(outputDir, state) {
|
|
5601
|
-
const path3 =
|
|
5602
|
-
await
|
|
5843
|
+
const path3 = join28(outputDir, STATE_FILE3);
|
|
5844
|
+
await writeFile11(path3, JSON.stringify(state.serialize(), null, 2), "utf-8");
|
|
5603
5845
|
}
|
|
5604
5846
|
async function loadBfsState(outputDir) {
|
|
5605
|
-
const path3 =
|
|
5847
|
+
const path3 = join28(outputDir, STATE_FILE3);
|
|
5606
5848
|
try {
|
|
5607
|
-
const raw = await
|
|
5849
|
+
const raw = await readFile21(path3, "utf-8");
|
|
5608
5850
|
return CoverageState.deserialize(JSON.parse(raw));
|
|
5609
5851
|
} catch {
|
|
5610
5852
|
return void 0;
|
|
@@ -5695,12 +5937,12 @@ var init_graph = __esm({
|
|
|
5695
5937
|
});
|
|
5696
5938
|
|
|
5697
5939
|
// src/agents/05-test-generator/prompt.ts
|
|
5698
|
-
var
|
|
5940
|
+
var SYSTEM_PROMPT7;
|
|
5699
5941
|
var init_prompt4 = __esm({
|
|
5700
5942
|
"src/agents/05-test-generator/prompt.ts"() {
|
|
5701
5943
|
"use strict";
|
|
5702
5944
|
init_esm_shims();
|
|
5703
|
-
|
|
5945
|
+
SYSTEM_PROMPT7 = `You are an E2E test generator that explores a frontend codebase as a BFS graph. You are ONE long-running agent that maintains all state about what's been explored and what hasn't.
|
|
5704
5946
|
|
|
5705
5947
|
## Your process
|
|
5706
5948
|
|
|
@@ -6131,11 +6373,11 @@ var init_validation = __esm({
|
|
|
6131
6373
|
});
|
|
6132
6374
|
|
|
6133
6375
|
// src/agents/05-test-generator/tools.ts
|
|
6134
|
-
import { mkdir as mkdir3, writeFile as
|
|
6135
|
-
import { dirname as dirname3, join as
|
|
6136
|
-
import { hasToolCall as hasToolCall3, stepCountIs as stepCountIs3, tool as
|
|
6376
|
+
import { mkdir as mkdir3, writeFile as writeFile12 } from "fs/promises";
|
|
6377
|
+
import { dirname as dirname3, join as join29 } from "path";
|
|
6378
|
+
import { hasToolCall as hasToolCall3, stepCountIs as stepCountIs3, tool as tool19, ToolLoopAgent as ToolLoopAgent3 } from "ai";
|
|
6137
6379
|
import matter5 from "gray-matter";
|
|
6138
|
-
import { z as
|
|
6380
|
+
import { z as z24 } from "zod";
|
|
6139
6381
|
function findForbiddenPlaceholder(stepsSection) {
|
|
6140
6382
|
const placeholderPatterns = [
|
|
6141
6383
|
{ pattern: /Dynamic:\s/gi, name: '"Dynamic:" placeholder' },
|
|
@@ -6153,13 +6395,13 @@ function findForbiddenPlaceholder(stepsSection) {
|
|
|
6153
6395
|
return void 0;
|
|
6154
6396
|
}
|
|
6155
6397
|
function buildWriteTestTool(state, outputDir) {
|
|
6156
|
-
return
|
|
6398
|
+
return tool19({
|
|
6157
6399
|
description: "Write a test file to qa-tests/{folder}/{filename}.md. Validates frontmatter before writing. Returns error if frontmatter is invalid.",
|
|
6158
|
-
inputSchema:
|
|
6159
|
-
folder:
|
|
6160
|
-
filename:
|
|
6161
|
-
content:
|
|
6162
|
-
nodeId:
|
|
6400
|
+
inputSchema: z24.object({
|
|
6401
|
+
folder: z24.string().describe("Subfolder name under qa-tests/"),
|
|
6402
|
+
filename: z24.string().describe("File name (e.g. login-valid-credentials.md)"),
|
|
6403
|
+
content: z24.string().describe("Full file content including YAML frontmatter"),
|
|
6404
|
+
nodeId: z24.string().describe("The FeatureNode ID this test belongs to")
|
|
6163
6405
|
}),
|
|
6164
6406
|
execute: async (input) => {
|
|
6165
6407
|
const frontmatter = extractFrontmatter(input.content);
|
|
@@ -6202,11 +6444,11 @@ function buildWriteTestTool(state, outputDir) {
|
|
|
6202
6444
|
error: `Test steps contain ${placeholder.name}: "${placeholder.match}". Use EXACT values from scenarios.md - not placeholders or examples.`
|
|
6203
6445
|
};
|
|
6204
6446
|
}
|
|
6205
|
-
const relPath =
|
|
6206
|
-
const absPath =
|
|
6447
|
+
const relPath = join29("qa-tests", input.folder, input.filename);
|
|
6448
|
+
const absPath = join29(outputDir, relPath);
|
|
6207
6449
|
try {
|
|
6208
6450
|
await mkdir3(dirname3(absPath), { recursive: true });
|
|
6209
|
-
await
|
|
6451
|
+
await writeFile12(absPath, input.content, "utf-8");
|
|
6210
6452
|
state.markTested(input.nodeId, [relPath]);
|
|
6211
6453
|
await saveBfsState(outputDir, state);
|
|
6212
6454
|
return { path: relPath, title: parsed.data.title };
|
|
@@ -6218,16 +6460,16 @@ function buildWriteTestTool(state, outputDir) {
|
|
|
6218
6460
|
});
|
|
6219
6461
|
}
|
|
6220
6462
|
function buildCreateFolderTool(outputDir) {
|
|
6221
|
-
return
|
|
6463
|
+
return tool19({
|
|
6222
6464
|
description: "Create a folder under qa-tests/ for organizing tests.",
|
|
6223
|
-
inputSchema:
|
|
6224
|
-
folder:
|
|
6465
|
+
inputSchema: z24.object({
|
|
6466
|
+
folder: z24.string().describe("Folder name (kebab-case)")
|
|
6225
6467
|
}),
|
|
6226
6468
|
execute: async (input) => {
|
|
6227
|
-
const absPath =
|
|
6469
|
+
const absPath = join29(outputDir, "qa-tests", input.folder);
|
|
6228
6470
|
try {
|
|
6229
6471
|
await mkdir3(absPath, { recursive: true });
|
|
6230
|
-
return { path:
|
|
6472
|
+
return { path: join29("qa-tests", input.folder) };
|
|
6231
6473
|
} catch (err) {
|
|
6232
6474
|
const message = err instanceof Error ? err.message : String(err);
|
|
6233
6475
|
return { error: `Failed to create folder: ${message}` };
|
|
@@ -6236,9 +6478,9 @@ function buildCreateFolderTool(outputDir) {
|
|
|
6236
6478
|
});
|
|
6237
6479
|
}
|
|
6238
6480
|
function buildNextNodeTool(state, outputDir) {
|
|
6239
|
-
return
|
|
6481
|
+
return tool19({
|
|
6240
6482
|
description: "Get the next node to write tests for. If you called next_node before without writing any tests (via write_test), the previous node is auto-skipped. Returns done:true when all nodes are processed.",
|
|
6241
|
-
inputSchema:
|
|
6483
|
+
inputSchema: z24.object({}),
|
|
6242
6484
|
execute: async () => {
|
|
6243
6485
|
const next = state.nextNode();
|
|
6244
6486
|
await saveBfsState(outputDir, state);
|
|
@@ -6265,9 +6507,9 @@ function buildNextNodeTool(state, outputDir) {
|
|
|
6265
6507
|
});
|
|
6266
6508
|
}
|
|
6267
6509
|
function buildGetProgressTool(state) {
|
|
6268
|
-
return
|
|
6510
|
+
return tool19({
|
|
6269
6511
|
description: "Check how many nodes have been tested vs how many remain.",
|
|
6270
|
-
inputSchema:
|
|
6512
|
+
inputSchema: z24.object({}),
|
|
6271
6513
|
execute: async () => {
|
|
6272
6514
|
const stats = state.summary();
|
|
6273
6515
|
const nodes = [...state.nodes.values()].map((n) => ({
|
|
@@ -6281,14 +6523,14 @@ function buildGetProgressTool(state) {
|
|
|
6281
6523
|
});
|
|
6282
6524
|
}
|
|
6283
6525
|
function buildSpawnResearcherTool(model, workingDirectory, onHeartbeat) {
|
|
6284
|
-
return
|
|
6526
|
+
return tool19({
|
|
6285
6527
|
description: "Spawn a research subagent to read and analyze source files without polluting your context. Use for complex sub-features where you don't want to read 20 files yourself.",
|
|
6286
|
-
inputSchema:
|
|
6287
|
-
instruction:
|
|
6528
|
+
inputSchema: z24.object({
|
|
6529
|
+
instruction: z24.string().describe("What to research - be specific about files and what to look for")
|
|
6288
6530
|
}),
|
|
6289
6531
|
execute: async (input) => {
|
|
6290
|
-
const resultSchema2 =
|
|
6291
|
-
findings:
|
|
6532
|
+
const resultSchema2 = z24.object({
|
|
6533
|
+
findings: z24.string().describe("Summary of what was found")
|
|
6292
6534
|
});
|
|
6293
6535
|
let result;
|
|
6294
6536
|
const subagent = new ToolLoopAgent3({
|
|
@@ -6300,7 +6542,7 @@ function buildSpawnResearcherTool(model, workingDirectory, onHeartbeat) {
|
|
|
6300
6542
|
glob: buildGlobTool(workingDirectory),
|
|
6301
6543
|
grep: buildGrepTool(workingDirectory),
|
|
6302
6544
|
read_file: buildReadFileTool(workingDirectory),
|
|
6303
|
-
finish:
|
|
6545
|
+
finish: tool19({
|
|
6304
6546
|
description: "Report your findings.",
|
|
6305
6547
|
inputSchema: resultSchema2,
|
|
6306
6548
|
execute: async (output) => {
|
|
@@ -6342,14 +6584,14 @@ var init_tools2 = __esm({
|
|
|
6342
6584
|
init_tools();
|
|
6343
6585
|
init_graph();
|
|
6344
6586
|
init_validation();
|
|
6345
|
-
testFrontmatterSchema =
|
|
6346
|
-
title:
|
|
6347
|
-
description:
|
|
6348
|
-
intent:
|
|
6349
|
-
criticality:
|
|
6350
|
-
scenario:
|
|
6351
|
-
flow:
|
|
6352
|
-
verification:
|
|
6587
|
+
testFrontmatterSchema = z24.object({
|
|
6588
|
+
title: z24.string().min(1),
|
|
6589
|
+
description: z24.string().min(1),
|
|
6590
|
+
intent: z24.string().min(30, "Intent must be at least 30 characters - describe the BEHAVIOR being tested, not the steps"),
|
|
6591
|
+
criticality: z24.enum(["critical", "high", "mid", "low"]),
|
|
6592
|
+
scenario: z24.string().min(1),
|
|
6593
|
+
flow: z24.string().min(1),
|
|
6594
|
+
verification: z24.string().min(
|
|
6353
6595
|
20,
|
|
6354
6596
|
"Verification must describe WHERE to navigate and WHAT to assert at the source of truth - not UI acknowledgments like toasts"
|
|
6355
6597
|
)
|
|
@@ -6362,10 +6604,10 @@ var test_generator_exports = {};
|
|
|
6362
6604
|
__export(test_generator_exports, {
|
|
6363
6605
|
runTestGenerator: () => runTestGenerator
|
|
6364
6606
|
});
|
|
6365
|
-
import { mkdir as mkdir4, readFile as
|
|
6366
|
-
import { basename as basename4, join as
|
|
6367
|
-
import { tool as
|
|
6368
|
-
import { z as
|
|
6607
|
+
import { mkdir as mkdir4, readFile as readFile22, rmdir, unlink, writeFile as writeFile13 } from "fs/promises";
|
|
6608
|
+
import { basename as basename4, join as join30 } from "path";
|
|
6609
|
+
import { tool as tool20 } from "ai";
|
|
6610
|
+
import { z as z25 } from "zod";
|
|
6369
6611
|
import { glob as glob6 } from "glob";
|
|
6370
6612
|
async function preseedQueue(state, projectRoot, pages, features) {
|
|
6371
6613
|
let seeded = 0;
|
|
@@ -6413,10 +6655,10 @@ async function runTestGenerator(input) {
|
|
|
6413
6655
|
const existingState = await loadBfsState(input.outputDir);
|
|
6414
6656
|
const state = existingState ?? new CoverageState();
|
|
6415
6657
|
let result;
|
|
6416
|
-
const finishTool =
|
|
6658
|
+
const finishTool = tool20({
|
|
6417
6659
|
description: "Call when the BFS queue is empty and all routes have been explored.",
|
|
6418
|
-
inputSchema:
|
|
6419
|
-
summary:
|
|
6660
|
+
inputSchema: z25.object({
|
|
6661
|
+
summary: z25.string().describe("Coverage summary")
|
|
6420
6662
|
}),
|
|
6421
6663
|
execute: async (finishInput) => {
|
|
6422
6664
|
const stats = state.summary();
|
|
@@ -6445,7 +6687,7 @@ async function runTestGenerator(input) {
|
|
|
6445
6687
|
});
|
|
6446
6688
|
let kbContext = "";
|
|
6447
6689
|
try {
|
|
6448
|
-
const autonomaMd = await
|
|
6690
|
+
const autonomaMd = await readFile22(join30(input.outputDir, "AUTONOMA.md"), "utf-8");
|
|
6449
6691
|
kbContext += `
|
|
6450
6692
|
## Knowledge Base (AUTONOMA.md)
|
|
6451
6693
|
|
|
@@ -6454,7 +6696,7 @@ ${autonomaMd}
|
|
|
6454
6696
|
} catch {
|
|
6455
6697
|
}
|
|
6456
6698
|
try {
|
|
6457
|
-
const scenariosMd = await
|
|
6699
|
+
const scenariosMd = await readFile22(join30(input.outputDir, "scenarios.md"), "utf-8");
|
|
6458
6700
|
kbContext += `
|
|
6459
6701
|
## Scenarios
|
|
6460
6702
|
|
|
@@ -6508,7 +6750,7 @@ Do NOT try to finish early. Process EVERY node via next_node until it returns do
|
|
|
6508
6750
|
const listDirectoryFn = await buildListDirectoryTool(input.projectRoot);
|
|
6509
6751
|
const agentConfig = {
|
|
6510
6752
|
id: "test-generator",
|
|
6511
|
-
systemPrompt:
|
|
6753
|
+
systemPrompt: SYSTEM_PROMPT7,
|
|
6512
6754
|
model,
|
|
6513
6755
|
maxSteps: CHUNK_STEPS,
|
|
6514
6756
|
temperature: 0.3,
|
|
@@ -6624,20 +6866,20 @@ IMPORTANT: Do NOT try to finish early. Process every node via next_node until it
|
|
|
6624
6866
|
}
|
|
6625
6867
|
console.log(` Fix pass complete`);
|
|
6626
6868
|
}
|
|
6627
|
-
const allTestFiles = await glob6(
|
|
6869
|
+
const allTestFiles = await glob6(join30(input.outputDir, "qa-tests", "**/*.md"));
|
|
6628
6870
|
let markedInvalid = 0;
|
|
6629
6871
|
for (const testPath of allTestFiles) {
|
|
6630
6872
|
if (basename4(testPath) === "INDEX.md") continue;
|
|
6631
6873
|
if (testPath.includes("/_invalid/")) continue;
|
|
6632
|
-
const content = await
|
|
6874
|
+
const content = await readFile22(testPath, "utf-8");
|
|
6633
6875
|
const validation = validateTestContent(content);
|
|
6634
6876
|
if (!validation.valid) {
|
|
6635
|
-
const invalidDir =
|
|
6877
|
+
const invalidDir = join30(input.outputDir, "qa-tests", "_invalid");
|
|
6636
6878
|
await mkdir4(invalidDir, { recursive: true });
|
|
6637
|
-
const dest =
|
|
6879
|
+
const dest = join30(invalidDir, basename4(testPath));
|
|
6638
6880
|
const annotated = `<!-- VALIDATION ERRORS: ${validation.errors.join("; ")} -->
|
|
6639
6881
|
${content}`;
|
|
6640
|
-
await
|
|
6882
|
+
await writeFile13(dest, annotated, "utf-8");
|
|
6641
6883
|
await unlink(testPath);
|
|
6642
6884
|
markedInvalid++;
|
|
6643
6885
|
}
|
|
@@ -6645,7 +6887,7 @@ ${content}`;
|
|
|
6645
6887
|
if (markedInvalid > 0) {
|
|
6646
6888
|
console.log(` ${markedInvalid} tests still invalid after review cycles - moved to _invalid/`);
|
|
6647
6889
|
}
|
|
6648
|
-
const dirs = await glob6(
|
|
6890
|
+
const dirs = await glob6(join30(input.outputDir, "qa-tests", "**/"), {
|
|
6649
6891
|
dot: false
|
|
6650
6892
|
});
|
|
6651
6893
|
for (const dir of dirs.sort((a, b) => b.length - a.length)) {
|
|
@@ -6737,7 +6979,7 @@ async function generateIndex(outputDir, state) {
|
|
|
6737
6979
|
for (const paths of state.testsWritten.values()) {
|
|
6738
6980
|
for (const p10 of paths) {
|
|
6739
6981
|
try {
|
|
6740
|
-
const content2 = await
|
|
6982
|
+
const content2 = await readFile22(join30(outputDir, p10), "utf-8");
|
|
6741
6983
|
const critMatch = content2.match(/criticality:\s*(\w+)/);
|
|
6742
6984
|
const critVal = critMatch?.[1] ?? "";
|
|
6743
6985
|
if (critCounts.has(critVal)) critCounts.set(critVal, (critCounts.get(critVal) ?? 0) + 1);
|
|
@@ -6782,28 +7024,28 @@ ${folders.map((f) => `| ${f.name} | ${f.test_count} |`).join("\n")}
|
|
|
6782
7024
|
|
|
6783
7025
|
${[...testsByFolder.entries()].flatMap(([_folder, tests]) => tests.map((t) => `- \`${t}\``)).join("\n")}
|
|
6784
7026
|
`;
|
|
6785
|
-
await
|
|
7027
|
+
await writeFile13(join30(outputDir, "qa-tests", "INDEX.md"), content, "utf-8");
|
|
6786
7028
|
}
|
|
6787
7029
|
async function generateJourneyTests(outputDir, model, projectRoot) {
|
|
6788
7030
|
const logger = createStepLogger("journeys", 50);
|
|
6789
7031
|
let autonomaMd = "";
|
|
6790
7032
|
let scenariosMd = "";
|
|
6791
7033
|
try {
|
|
6792
|
-
autonomaMd = await
|
|
7034
|
+
autonomaMd = await readFile22(join30(outputDir, "AUTONOMA.md"), "utf-8");
|
|
6793
7035
|
} catch (err) {
|
|
6794
7036
|
debugLog("AUTONOMA.md not present for journey generation", { err });
|
|
6795
7037
|
}
|
|
6796
7038
|
try {
|
|
6797
|
-
scenariosMd = await
|
|
7039
|
+
scenariosMd = await readFile22(join30(outputDir, "scenarios.md"), "utf-8");
|
|
6798
7040
|
} catch (err) {
|
|
6799
7041
|
debugLog("scenarios.md not present for journey generation", { err });
|
|
6800
7042
|
}
|
|
6801
7043
|
if (!autonomaMd) return 0;
|
|
6802
|
-
const existingTests = await glob6(
|
|
7044
|
+
const existingTests = await glob6(join30(outputDir, "qa-tests", "**/*.md"));
|
|
6803
7045
|
const existingTitles = [];
|
|
6804
7046
|
for (const t of existingTests) {
|
|
6805
7047
|
if (basename4(t) === "INDEX.md") continue;
|
|
6806
|
-
const content = await
|
|
7048
|
+
const content = await readFile22(t, "utf-8");
|
|
6807
7049
|
const titleMatch = content.match(/title:\s*"([^"]+)"/);
|
|
6808
7050
|
if (titleMatch) existingTitles.push(titleMatch[1]);
|
|
6809
7051
|
}
|
|
@@ -6846,9 +7088,9 @@ Write 5-8 journey tests using the write_test tool with folder "journeys". Then c
|
|
|
6846
7088
|
status: "queued"
|
|
6847
7089
|
});
|
|
6848
7090
|
let journeyResult;
|
|
6849
|
-
const journeyFinish =
|
|
7091
|
+
const journeyFinish = tool20({
|
|
6850
7092
|
description: "Signal journey generation is complete.",
|
|
6851
|
-
inputSchema:
|
|
7093
|
+
inputSchema: z25.object({ summary: z25.string() }),
|
|
6852
7094
|
execute: async (finishInput) => {
|
|
6853
7095
|
journeyResult = {
|
|
6854
7096
|
success: true,
|
|
@@ -6860,7 +7102,7 @@ Write 5-8 journey tests using the write_test tool with folder "journeys". Then c
|
|
|
6860
7102
|
});
|
|
6861
7103
|
const config = {
|
|
6862
7104
|
id: "journey-gen",
|
|
6863
|
-
systemPrompt:
|
|
7105
|
+
systemPrompt: SYSTEM_PROMPT7,
|
|
6864
7106
|
model,
|
|
6865
7107
|
maxSteps: 50,
|
|
6866
7108
|
temperature: 0.3,
|
|
@@ -6937,8 +7179,8 @@ function ensureSupportedNode() {
|
|
|
6937
7179
|
ensureSupportedNode();
|
|
6938
7180
|
|
|
6939
7181
|
// src/index.ts
|
|
6940
|
-
import { readFile as
|
|
6941
|
-
import { join as
|
|
7182
|
+
import { readFile as readFile23, writeFile as writeFile14 } from "fs/promises";
|
|
7183
|
+
import { join as join31 } from "path";
|
|
6942
7184
|
import * as p9 from "@clack/prompts";
|
|
6943
7185
|
|
|
6944
7186
|
// src/config.ts
|
|
@@ -7018,6 +7260,8 @@ function loadConfig(args) {
|
|
|
7018
7260
|
projectRoot,
|
|
7019
7261
|
projectSlug,
|
|
7020
7262
|
modelId: args.model ?? env.OPENROUTER_MODEL,
|
|
7263
|
+
frontend: args.frontend,
|
|
7264
|
+
backends: args.backends,
|
|
7021
7265
|
databaseUrl: env.DATABASE_URL,
|
|
7022
7266
|
sdkEndpointUrl: env.SDK_ENDPOINT_URL,
|
|
7023
7267
|
sharedSecret: env.AUTONOMA_SHARED_SECRET,
|
|
@@ -7092,6 +7336,50 @@ function installInterruptHandler(opts) {
|
|
|
7092
7336
|
return iface;
|
|
7093
7337
|
});
|
|
7094
7338
|
}
|
|
7339
|
+
function installTerminationDiagnostics() {
|
|
7340
|
+
const memSnapshot = () => {
|
|
7341
|
+
const m = process.memoryUsage();
|
|
7342
|
+
const mb = (n) => Math.round(n / 1024 / 1024);
|
|
7343
|
+
return `rss=${mb(m.rss)}MB heapUsed=${mb(m.heapUsed)}MB heapTotal=${mb(m.heapTotal)}MB`;
|
|
7344
|
+
};
|
|
7345
|
+
for (const signal of ["SIGTERM", "SIGHUP"]) {
|
|
7346
|
+
const forcedCode = signal === "SIGTERM" ? 143 : 129;
|
|
7347
|
+
process.on(signal, () => {
|
|
7348
|
+
process.stderr.write(`
|
|
7349
|
+
[diagnostics] received ${signal} - external termination (${memSnapshot()})
|
|
7350
|
+
`);
|
|
7351
|
+
terminateGracefully(forcedCode);
|
|
7352
|
+
});
|
|
7353
|
+
}
|
|
7354
|
+
process.on("uncaughtException", (err) => {
|
|
7355
|
+
const detail = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
7356
|
+
process.stderr.write(`
|
|
7357
|
+
[diagnostics] uncaughtException (${memSnapshot()}): ${detail}
|
|
7358
|
+
`);
|
|
7359
|
+
restoreTerminal();
|
|
7360
|
+
process.exit(1);
|
|
7361
|
+
});
|
|
7362
|
+
process.on("unhandledRejection", (reason) => {
|
|
7363
|
+
const detail = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
|
|
7364
|
+
process.stderr.write(`
|
|
7365
|
+
[diagnostics] unhandledRejection (${memSnapshot()}): ${detail}
|
|
7366
|
+
`);
|
|
7367
|
+
restoreTerminal();
|
|
7368
|
+
process.exit(1);
|
|
7369
|
+
});
|
|
7370
|
+
}
|
|
7371
|
+
function terminateGracefully(forcedCode) {
|
|
7372
|
+
if (quitting || onExit == null) {
|
|
7373
|
+
restoreTerminal();
|
|
7374
|
+
process.exit(forcedCode);
|
|
7375
|
+
}
|
|
7376
|
+
quitting = true;
|
|
7377
|
+
setTimeout(() => {
|
|
7378
|
+
restoreTerminal();
|
|
7379
|
+
process.exit(forcedCode);
|
|
7380
|
+
}, FORCE_EXIT_MS).unref?.();
|
|
7381
|
+
onExit(forcedCode);
|
|
7382
|
+
}
|
|
7095
7383
|
function restoreTerminal() {
|
|
7096
7384
|
try {
|
|
7097
7385
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
@@ -7169,15 +7457,31 @@ async function loadGitInfo(outputDir) {
|
|
|
7169
7457
|
|
|
7170
7458
|
// src/index.ts
|
|
7171
7459
|
init_notify();
|
|
7460
|
+
init_project_map();
|
|
7172
7461
|
|
|
7173
7462
|
// src/core/state.ts
|
|
7174
7463
|
init_esm_shims();
|
|
7175
|
-
|
|
7176
|
-
import {
|
|
7464
|
+
init_debug();
|
|
7465
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
7466
|
+
import { join as join9 } from "path";
|
|
7467
|
+
import { z as z3 } from "zod";
|
|
7468
|
+
var StepStatusSchema = z3.enum(["pending", "running", "done", "failed", "paused"]);
|
|
7469
|
+
var PipelineStateSchema = z3.object({
|
|
7470
|
+
steps: z3.object({
|
|
7471
|
+
projectMapper: StepStatusSchema.default("done"),
|
|
7472
|
+
pagesFinder: StepStatusSchema.default("done"),
|
|
7473
|
+
kb: StepStatusSchema.default("done"),
|
|
7474
|
+
entityAudit: StepStatusSchema.default("done"),
|
|
7475
|
+
scenarioRecipe: StepStatusSchema.default("done"),
|
|
7476
|
+
recipeBuilder: StepStatusSchema.default("done"),
|
|
7477
|
+
testGenerator: StepStatusSchema.default("done")
|
|
7478
|
+
})
|
|
7479
|
+
});
|
|
7177
7480
|
var STATE_FILE = ".pipeline-state.json";
|
|
7178
7481
|
function initialState() {
|
|
7179
7482
|
return {
|
|
7180
7483
|
steps: {
|
|
7484
|
+
projectMapper: "pending",
|
|
7181
7485
|
pagesFinder: "pending",
|
|
7182
7486
|
kb: "pending",
|
|
7183
7487
|
entityAudit: "pending",
|
|
@@ -7188,18 +7492,19 @@ function initialState() {
|
|
|
7188
7492
|
};
|
|
7189
7493
|
}
|
|
7190
7494
|
async function loadState(outputDir) {
|
|
7191
|
-
const path3 =
|
|
7495
|
+
const path3 = join9(outputDir, STATE_FILE);
|
|
7192
7496
|
try {
|
|
7193
|
-
const raw = await
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7497
|
+
const raw = await readFile4(path3, "utf-8");
|
|
7498
|
+
return PipelineStateSchema.parse(JSON.parse(raw));
|
|
7499
|
+
} catch (err) {
|
|
7500
|
+
const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
7501
|
+
if (!isMissingFile) debugLog("Failed to load pipeline state, starting fresh", { path: path3, err });
|
|
7197
7502
|
return initialState();
|
|
7198
7503
|
}
|
|
7199
7504
|
}
|
|
7200
7505
|
async function saveState(outputDir, state) {
|
|
7201
|
-
const path3 =
|
|
7202
|
-
await
|
|
7506
|
+
const path3 = join9(outputDir, STATE_FILE);
|
|
7507
|
+
await writeFile4(path3, JSON.stringify(state, null, 2), "utf-8");
|
|
7203
7508
|
}
|
|
7204
7509
|
async function markStep(outputDir, state, step, status) {
|
|
7205
7510
|
const updated = {
|
|
@@ -7210,15 +7515,23 @@ async function markStep(outputDir, state, step, status) {
|
|
|
7210
7515
|
return updated;
|
|
7211
7516
|
}
|
|
7212
7517
|
function nextPendingStep(state) {
|
|
7213
|
-
const order = [
|
|
7518
|
+
const order = [
|
|
7519
|
+
"projectMapper",
|
|
7520
|
+
"pagesFinder",
|
|
7521
|
+
"kb",
|
|
7522
|
+
"entityAudit",
|
|
7523
|
+
"scenarioRecipe",
|
|
7524
|
+
"recipeBuilder",
|
|
7525
|
+
"testGenerator"
|
|
7526
|
+
];
|
|
7214
7527
|
return order.find((s) => state.steps[s] !== "done") ?? void 0;
|
|
7215
7528
|
}
|
|
7216
7529
|
|
|
7217
7530
|
// src/core/upload.ts
|
|
7218
7531
|
init_esm_shims();
|
|
7219
7532
|
init_debug();
|
|
7220
|
-
import { readFile as
|
|
7221
|
-
import { basename, join as
|
|
7533
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
7534
|
+
import { basename, join as join10, relative } from "path";
|
|
7222
7535
|
import * as p from "@clack/prompts";
|
|
7223
7536
|
import { glob } from "glob";
|
|
7224
7537
|
var ARTIFACT_FILES = ["AUTONOMA.md", "scenarios.md", "entity-audit.md"];
|
|
@@ -7226,7 +7539,7 @@ async function readArtifacts(outputDir) {
|
|
|
7226
7539
|
const files = [];
|
|
7227
7540
|
for (const name of ARTIFACT_FILES) {
|
|
7228
7541
|
try {
|
|
7229
|
-
const content = await
|
|
7542
|
+
const content = await readFile5(join10(outputDir, name), "utf-8");
|
|
7230
7543
|
files.push({ name, content });
|
|
7231
7544
|
} catch (err) {
|
|
7232
7545
|
debugLog(`Artifact ${name} not on disk; skipping upload`, { err });
|
|
@@ -7235,13 +7548,13 @@ async function readArtifacts(outputDir) {
|
|
|
7235
7548
|
return files;
|
|
7236
7549
|
}
|
|
7237
7550
|
async function readTestCases(outputDir) {
|
|
7238
|
-
const testsDir =
|
|
7551
|
+
const testsDir = join10(outputDir, "qa-tests");
|
|
7239
7552
|
const matches = await glob("**/*.md", { cwd: testsDir, nodir: true });
|
|
7240
7553
|
const files = [];
|
|
7241
7554
|
for (const match of matches) {
|
|
7242
7555
|
const name = basename(match);
|
|
7243
7556
|
if (name === "INDEX.md") continue;
|
|
7244
|
-
const content = await
|
|
7557
|
+
const content = await readFile5(join10(testsDir, match), "utf-8");
|
|
7245
7558
|
const folderPath = relative(".", match).split("/").slice(0, -1).join("/");
|
|
7246
7559
|
files.push({ name, content, folder: folderPath.length > 0 ? folderPath : void 0 });
|
|
7247
7560
|
}
|
|
@@ -7303,11 +7616,11 @@ process.setSourceMapsEnabled(true);
|
|
|
7303
7616
|
var PAGES_FILE = "pages.json";
|
|
7304
7617
|
async function savePages(outputDir, pages) {
|
|
7305
7618
|
const obj = Object.fromEntries(pages);
|
|
7306
|
-
await
|
|
7619
|
+
await writeFile14(join31(outputDir, PAGES_FILE), JSON.stringify(obj, null, 2), "utf-8");
|
|
7307
7620
|
}
|
|
7308
7621
|
async function loadPages(outputDir) {
|
|
7309
7622
|
try {
|
|
7310
|
-
const raw = await
|
|
7623
|
+
const raw = await readFile23(join31(outputDir, PAGES_FILE), "utf-8");
|
|
7311
7624
|
const obj = JSON.parse(raw);
|
|
7312
7625
|
return new Map(Object.entries(obj));
|
|
7313
7626
|
} catch {
|
|
@@ -7336,6 +7649,7 @@ function strArg(args, key) {
|
|
|
7336
7649
|
return typeof value === "string" ? value : void 0;
|
|
7337
7650
|
}
|
|
7338
7651
|
var STEP_LABELS = {
|
|
7652
|
+
projectMapper: "Map your project structure",
|
|
7339
7653
|
pagesFinder: "Find your pages",
|
|
7340
7654
|
kb: "Build a knowledge base",
|
|
7341
7655
|
entityAudit: "Map your data models",
|
|
@@ -7347,6 +7661,7 @@ function isStepName(value) {
|
|
|
7347
7661
|
return value in STEP_LABELS;
|
|
7348
7662
|
}
|
|
7349
7663
|
var STEP_SUMMARIES = {
|
|
7664
|
+
projectMapper: "Identify your frontend(s), backend(s), and which folders to ignore.",
|
|
7350
7665
|
pagesFinder: "Map every page and route in your app.",
|
|
7351
7666
|
kb: "Learn your app's features, flows, and UI patterns.",
|
|
7352
7667
|
entityAudit: "Find what your app stores (users, orgs, ...) and how each one is created.",
|
|
@@ -7355,6 +7670,7 @@ var STEP_SUMMARIES = {
|
|
|
7355
7670
|
testGenerator: "Write the end-to-end tests, covering every page and feature."
|
|
7356
7671
|
};
|
|
7357
7672
|
var STEP_INTROS = {
|
|
7673
|
+
projectMapper: "Looking at how your codebase is laid out - which folder(s) are the frontend, which are the backend/data layer, and which are unrelated - so every later step scans only what matters instead of the whole repo.",
|
|
7358
7674
|
pagesFinder: "Scanning your codebase to find every page and route, so we know the full surface area that needs test coverage.",
|
|
7359
7675
|
kb: "Reading those pages to learn your app's features, flows, and UI patterns - the context everything after this builds on.",
|
|
7360
7676
|
entityAudit: "Finding the things your app stores (users, organizations, orders, ...) and how each one gets created, so we can generate realistic test data for them.",
|
|
@@ -7362,6 +7678,32 @@ var STEP_INTROS = {
|
|
|
7362
7678
|
recipeBuilder: "Helping you wire up small helpers that create and clean up test data in your own database. We give you a copy-paste guide for each one and test it live against your app running locally - you deploy later, once everything passes.",
|
|
7363
7679
|
testGenerator: "Writing the actual end-to-end tests, covering every page and feature with depth proportional to its complexity."
|
|
7364
7680
|
};
|
|
7681
|
+
async function resolveScopeSelection(map, config, nonInteractive) {
|
|
7682
|
+
if (config.frontend != null) {
|
|
7683
|
+
const requestedFrontend = resolveSelection(map, { frontend: config.frontend, backends: [] }).frontend;
|
|
7684
|
+
const backends = config.backends ?? defaultBackendsFor(map, requestedFrontend);
|
|
7685
|
+
return resolveSelection(map, { frontend: config.frontend, backends });
|
|
7686
|
+
}
|
|
7687
|
+
if (!nonInteractive) return promptScopeSelection(map);
|
|
7688
|
+
return pickDefaultSelection(map);
|
|
7689
|
+
}
|
|
7690
|
+
async function promptScopeSelection(map) {
|
|
7691
|
+
const frontend = await p9.select({
|
|
7692
|
+
message: "Which frontend do you want to plan tests for?",
|
|
7693
|
+
options: map.frontends.map((f) => ({ value: f.path, label: `${f.path} [${f.framework}]`, hint: f.why }))
|
|
7694
|
+
});
|
|
7695
|
+
if (p9.isCancel(frontend)) throw new Error("Cancelled");
|
|
7696
|
+
if (map.backends.length === 0) return { frontend, backends: [] };
|
|
7697
|
+
const needed = defaultBackendsFor(map, frontend);
|
|
7698
|
+
const backends = await p9.multiselect({
|
|
7699
|
+
message: "Which backends does it need? (pre-checked: the ones it depends on)",
|
|
7700
|
+
options: map.backends.map((b) => ({ value: b.path, label: `${b.path} [${b.framework}]`, hint: b.why })),
|
|
7701
|
+
initialValues: needed,
|
|
7702
|
+
required: false
|
|
7703
|
+
});
|
|
7704
|
+
if (p9.isCancel(backends)) throw new Error("Cancelled");
|
|
7705
|
+
return { frontend, backends };
|
|
7706
|
+
}
|
|
7365
7707
|
async function runStep(step, outputDir, state, config, projectContext, nonInteractive, retryGuidance) {
|
|
7366
7708
|
const label = STEP_LABELS[step];
|
|
7367
7709
|
p9.note(STEP_INTROS[step], `Step: ${label}`);
|
|
@@ -7377,13 +7719,52 @@ async function runStep(step, outputDir, state, config, projectContext, nonIntera
|
|
|
7377
7719
|
try {
|
|
7378
7720
|
let result;
|
|
7379
7721
|
switch (step) {
|
|
7722
|
+
case "projectMapper": {
|
|
7723
|
+
const { runProjectMapper: runProjectMapper2 } = await Promise.resolve().then(() => (init_project_mapper(), project_mapper_exports));
|
|
7724
|
+
const map = await runProjectMapper2({
|
|
7725
|
+
projectRoot: config.projectRoot,
|
|
7726
|
+
outputDir,
|
|
7727
|
+
modelId: config.modelId,
|
|
7728
|
+
nonInteractive
|
|
7729
|
+
});
|
|
7730
|
+
if (map == null) {
|
|
7731
|
+
result = { success: false, artifacts: [], summary: "Project mapper did not produce a map." };
|
|
7732
|
+
break;
|
|
7733
|
+
}
|
|
7734
|
+
if (map.frontends.length === 0) {
|
|
7735
|
+
result = {
|
|
7736
|
+
success: false,
|
|
7737
|
+
artifacts: [],
|
|
7738
|
+
summary: "Project mapper found no frontend to test. Point --project at a codebase with a UI."
|
|
7739
|
+
};
|
|
7740
|
+
break;
|
|
7741
|
+
}
|
|
7742
|
+
const selection = await resolveScopeSelection(map, config, nonInteractive);
|
|
7743
|
+
if (selection == null) {
|
|
7744
|
+
await saveProjectMap(outputDir, map);
|
|
7745
|
+
p9.note(renderProjectMap(map), "Project map - candidates (pick one frontend + its backends)");
|
|
7746
|
+
result = {
|
|
7747
|
+
success: false,
|
|
7748
|
+
paused: true,
|
|
7749
|
+
artifacts: [],
|
|
7750
|
+
summary: `Found ${map.frontends.length} candidate frontends. Choose one and its backends, then resume with --frontend <path> --backends <path,path>.`
|
|
7751
|
+
};
|
|
7752
|
+
break;
|
|
7753
|
+
}
|
|
7754
|
+
const scoped = applySelection(map, selection);
|
|
7755
|
+
await saveProjectMap(outputDir, scoped);
|
|
7756
|
+
p9.note(renderProjectMap(scoped), "Project map");
|
|
7757
|
+
break;
|
|
7758
|
+
}
|
|
7380
7759
|
case "pagesFinder": {
|
|
7381
7760
|
const { runPageFinder: runPageFinder2 } = await Promise.resolve().then(() => (init_pages_finder(), pages_finder_exports));
|
|
7761
|
+
const projectMap = await loadProjectMap(outputDir);
|
|
7382
7762
|
const pages = await runPageFinder2({
|
|
7383
7763
|
projectRoot: config.projectRoot,
|
|
7384
7764
|
outputDir,
|
|
7385
7765
|
modelId: config.modelId,
|
|
7386
|
-
nonInteractive
|
|
7766
|
+
nonInteractive,
|
|
7767
|
+
extraMessage: projectMap != null ? formatFrontendScope(projectMap) : void 0
|
|
7387
7768
|
});
|
|
7388
7769
|
await savePages(outputDir, pages);
|
|
7389
7770
|
break;
|
|
@@ -7402,18 +7783,21 @@ async function runStep(step, outputDir, state, config, projectContext, nonIntera
|
|
|
7402
7783
|
}
|
|
7403
7784
|
case "entityAudit": {
|
|
7404
7785
|
const { runEntityAudit: runEntityAudit2 } = await Promise.resolve().then(() => (init_entity_audit(), entity_audit_exports));
|
|
7786
|
+
const auditMap = await loadProjectMap(outputDir);
|
|
7405
7787
|
result = await runEntityAudit2({
|
|
7406
7788
|
projectRoot: config.projectRoot,
|
|
7407
7789
|
outputDir,
|
|
7408
7790
|
modelId: config.modelId,
|
|
7409
7791
|
projectContext,
|
|
7410
7792
|
nonInteractive,
|
|
7411
|
-
retryGuidance
|
|
7793
|
+
retryGuidance,
|
|
7794
|
+
scopeHint: auditMap != null ? formatBackendScope(auditMap) : void 0
|
|
7412
7795
|
});
|
|
7413
7796
|
break;
|
|
7414
7797
|
}
|
|
7415
7798
|
case "scenarioRecipe": {
|
|
7416
7799
|
const { runScenarioRecipe: runScenarioRecipe2 } = await Promise.resolve().then(() => (init_scenario_recipe(), scenario_recipe_exports));
|
|
7800
|
+
const recipeMap = await loadProjectMap(outputDir);
|
|
7417
7801
|
result = await runScenarioRecipe2({
|
|
7418
7802
|
projectRoot: config.projectRoot,
|
|
7419
7803
|
outputDir,
|
|
@@ -7421,7 +7805,8 @@ async function runStep(step, outputDir, state, config, projectContext, nonIntera
|
|
|
7421
7805
|
config,
|
|
7422
7806
|
projectContext,
|
|
7423
7807
|
nonInteractive,
|
|
7424
|
-
retryGuidance
|
|
7808
|
+
retryGuidance,
|
|
7809
|
+
scopeHint: recipeMap != null ? formatBackendScope(recipeMap) : void 0
|
|
7425
7810
|
});
|
|
7426
7811
|
break;
|
|
7427
7812
|
}
|
|
@@ -7574,6 +7959,7 @@ async function gatherProjectContext() {
|
|
|
7574
7959
|
};
|
|
7575
7960
|
}
|
|
7576
7961
|
async function main() {
|
|
7962
|
+
installTerminationDiagnostics();
|
|
7577
7963
|
const args = parseArgs(process.argv.slice(2));
|
|
7578
7964
|
const command = process.argv[2];
|
|
7579
7965
|
if (command === "status") {
|
|
@@ -7591,7 +7977,7 @@ async function main() {
|
|
|
7591
7977
|
if (command === "help" || args.help) {
|
|
7592
7978
|
console.log("Usage:");
|
|
7593
7979
|
console.log(
|
|
7594
|
-
" test-planner [run] [--project <path>] [--model <id>] [--step <name>] [--resume] [--non-interactive]"
|
|
7980
|
+
" test-planner [run] [--project <path>] [--frontend <path>] [--backends <path,path>] [--model <id>] [--step <name>] [--resume] [--non-interactive]"
|
|
7595
7981
|
);
|
|
7596
7982
|
console.log(" test-planner status [--project <path>]");
|
|
7597
7983
|
console.log("");
|
|
@@ -7602,19 +7988,25 @@ async function main() {
|
|
|
7602
7988
|
p9.intro("Let's generate your test suite");
|
|
7603
7989
|
const resumeCommand = `autonoma-planner --resume` + (args.project ? ` --project ${args.project}` : "");
|
|
7604
7990
|
installInterruptHandler({
|
|
7605
|
-
|
|
7991
|
+
// exitCode defaults to 0 for a user-initiated Ctrl+C (progress saved, a clean stop);
|
|
7992
|
+
// an external SIGTERM/SIGHUP passes the conventional 143/129 so the flushed exit still
|
|
7993
|
+
// carries the signal code a reaper/CI reads, not a 0 that looks like normal completion.
|
|
7994
|
+
onExit: (exitCode = 0) => {
|
|
7606
7995
|
track("cli_run_exited");
|
|
7607
7996
|
restoreTerminal();
|
|
7608
7997
|
console.log("");
|
|
7609
7998
|
p9.log.warn(`Your progress is saved. To resume, run:
|
|
7610
7999
|
${resumeCommand}`);
|
|
7611
|
-
void flushAnalytics().finally(() => process.exit(
|
|
8000
|
+
void flushAnalytics().finally(() => process.exit(exitCode));
|
|
7612
8001
|
}
|
|
7613
8002
|
});
|
|
8003
|
+
const backendsArg = strArg(args, "backends");
|
|
7614
8004
|
const config = loadConfig({
|
|
7615
8005
|
project: strArg(args, "project"),
|
|
7616
8006
|
model: strArg(args, "model"),
|
|
7617
|
-
slug: strArg(args, "slug")
|
|
8007
|
+
slug: strArg(args, "slug"),
|
|
8008
|
+
frontend: strArg(args, "frontend"),
|
|
8009
|
+
backends: backendsArg != null ? backendsArg.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0
|
|
7618
8010
|
});
|
|
7619
8011
|
if (!ensureAutonomaAuth()) {
|
|
7620
8012
|
return;
|
|
@@ -7705,12 +8097,20 @@ or reveal hidden files (macOS: Cmd+Shift+. ) to see it.`,
|
|
|
7705
8097
|
p9.outro("Done");
|
|
7706
8098
|
return;
|
|
7707
8099
|
}
|
|
7708
|
-
const startStep = isResuming ? nextPendingStep(state) : "
|
|
8100
|
+
const startStep = isResuming ? nextPendingStep(state) : "projectMapper";
|
|
7709
8101
|
if (!startStep) {
|
|
7710
8102
|
p9.log.success("All steps complete.");
|
|
7711
8103
|
return;
|
|
7712
8104
|
}
|
|
7713
|
-
const steps = [
|
|
8105
|
+
const steps = [
|
|
8106
|
+
"projectMapper",
|
|
8107
|
+
"pagesFinder",
|
|
8108
|
+
"kb",
|
|
8109
|
+
"entityAudit",
|
|
8110
|
+
"scenarioRecipe",
|
|
8111
|
+
"recipeBuilder",
|
|
8112
|
+
"testGenerator"
|
|
8113
|
+
];
|
|
7714
8114
|
const startIdx = steps.indexOf(startStep);
|
|
7715
8115
|
p9.note(steps.map((s, idx) => `${idx + 1}. ${STEP_LABELS[s]} - ${STEP_SUMMARIES[s]}`).join("\n"), "Here's the plan");
|
|
7716
8116
|
try {
|