@autonoma-ai/planner 0.1.18 → 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/dist/index.js CHANGED
@@ -14,11 +14,11 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
- // ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.3_jiti@2.6.1_postcss@8.5.15_tsx@4.21.0_typescript@6.0.3_yaml@2.8.3/node_modules/tsup/assets/esm_shims.js
17
+ // ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.3_jiti@2.6.1_postcss@8.5.15_tsx@4.21.0_typescript@7.0.2_yaml@2.8.3/node_modules/tsup/assets/esm_shims.js
18
18
  import path from "path";
19
19
  import { fileURLToPath } from "url";
20
20
  var init_esm_shims = __esm({
21
- "../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.3_jiti@2.6.1_postcss@8.5.15_tsx@4.21.0_typescript@6.0.3_yaml@2.8.3/node_modules/tsup/assets/esm_shims.js"() {
21
+ "../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.3_jiti@2.6.1_postcss@8.5.15_tsx@4.21.0_typescript@7.0.2_yaml@2.8.3/node_modules/tsup/assets/esm_shims.js"() {
22
22
  "use strict";
23
23
  }
24
24
  });
@@ -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((resolve5) => setTimeout(resolve5, timeoutMs))]);
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({
@@ -218,6 +218,29 @@ var init_analytics = __esm({
218
218
  }
219
219
  });
220
220
 
221
+ // src/core/colors.ts
222
+ function fg(hex) {
223
+ const r = parseInt(hex.slice(1, 3), 16);
224
+ const g = parseInt(hex.slice(3, 5), 16);
225
+ const b = parseInt(hex.slice(5, 7), 16);
226
+ return `${ESC}38;2;${r};${g};${b}m`;
227
+ }
228
+ var ESC, RESET, BOLD, DIM, PRIMARY, SUCCESS, ERROR, WARNING;
229
+ var init_colors = __esm({
230
+ "src/core/colors.ts"() {
231
+ "use strict";
232
+ init_esm_shims();
233
+ ESC = "\x1B[";
234
+ RESET = `${ESC}0m`;
235
+ BOLD = `${ESC}1m`;
236
+ DIM = `${ESC}2m`;
237
+ PRIMARY = fg("#CCFF00");
238
+ SUCCESS = fg("#3FB950");
239
+ ERROR = fg("#FF5C5C");
240
+ WARNING = fg("#FFB020");
241
+ }
242
+ });
243
+
221
244
  // src/core/context.ts
222
245
  import { readFile, writeFile } from "fs/promises";
223
246
  import { join as join5 } from "path";
@@ -265,7 +288,7 @@ var init_context = __esm({
265
288
  // src/core/errors.ts
266
289
  import { APICallError, RetryError, LoadAPIKeyError, InvalidPromptError, NoSuchModelError } from "ai";
267
290
  function sleep(ms) {
268
- return new Promise((resolve5) => setTimeout(resolve5, ms));
291
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
269
292
  }
270
293
  function isUserCancellation(err) {
271
294
  return err instanceof Error && /\bcancell?ed\b/i.test(err.message);
@@ -469,6 +492,151 @@ var init_notify = __esm({
469
492
  }
470
493
  });
471
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
+
472
640
  // src/core/display.ts
473
641
  function formatArgs(input, keys) {
474
642
  const parts = [];
@@ -533,7 +701,7 @@ function createStepLogger(agentId, _maxSteps) {
533
701
  function writeSpinner(message) {
534
702
  const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
535
703
  frameIdx++;
536
- process.stderr.write(`${CLEAR_LINE} ${DIM2}${frame} ${message}${RESET2}`);
704
+ process.stderr.write(`${CLEAR_LINE} ${DIM}${frame} ${message}${RESET}`);
537
705
  lastSpinnerLine = true;
538
706
  }
539
707
  function writePermanent(message) {
@@ -564,55 +732,49 @@ function createStepLogger(agentId, _maxSteps) {
564
732
  case "write_file": {
565
733
  stats.filesWritten++;
566
734
  const path3 = String(tc.input.path ?? tc.input.file_path ?? "");
567
- writePermanent(` ${GREEN}\u270E write ${path3}${RESET2}`);
735
+ writePermanent(` ${SUCCESS}\u270E write ${path3}${RESET}`);
568
736
  break;
569
737
  }
570
738
  case "write_test":
571
739
  stats.filesWritten++;
572
- writePermanent(` ${GREEN}\u270E test ${summary2}${RESET2}`);
740
+ writePermanent(` ${SUCCESS}\u270E test ${summary2}${RESET}`);
573
741
  break;
574
742
  case "finish":
575
- writePermanent(` ${GREEN}${BOLD}\u2713 done:${RESET2} ${GREEN}${agentId}${RESET2}`);
743
+ writePermanent(` ${SUCCESS}${BOLD}\u2713 done:${RESET} ${SUCCESS}${agentId}${RESET}`);
576
744
  break;
577
745
  case "subagent":
578
746
  case "spawn_researcher":
579
- writePermanent(` ${CYAN}\u2295 subagent: ${summary2}${RESET2}`);
747
+ writePermanent(` ${PRIMARY}\u2295 subagent: ${summary2}${RESET}`);
580
748
  break;
581
749
  default:
582
750
  writeSpinner(`${tc.name}${summary2 ? " " + summary2 : ""}`);
583
751
  }
584
752
  }
585
753
  for (const te of info.toolErrors) {
586
- writePermanent(` ${RED}\u2717 ${te.name}: ${te.error}${RESET2}`);
754
+ writePermanent(` ${ERROR}\u2717 ${te.name}: ${te.error}${RESET}`);
587
755
  }
588
756
  for (const f of info.writtenFiles) {
589
- writePermanent(` ${GREEN}\u{1F4C4} wrote: ${f}${RESET2}`);
757
+ writePermanent(` ${SUCCESS}\u{1F4C4} wrote: ${f}${RESET}`);
590
758
  }
591
759
  }
592
760
  function checkpoint(message) {
593
- writePermanent(` ${YELLOW}\u25B8 ${message}${RESET2}`);
761
+ writePermanent(` ${WARNING}\u25B8 ${message}${RESET}`);
594
762
  }
595
763
  function summary() {
596
764
  clearSpinner();
597
765
  if (stats.filesRead > 0 || stats.filesWritten > 0) {
598
- console.log(` ${DIM2}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET2}`);
599
- console.log(` ${DIM2}files read: ${stats.filesRead} | files written: ${stats.filesWritten}${RESET2}`);
766
+ console.log(` ${DIM}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`);
767
+ console.log(` ${DIM}files read: ${stats.filesRead} | files written: ${stats.filesWritten}${RESET}`);
600
768
  }
601
769
  }
602
770
  return { log: log9, checkpoint, summary, stats };
603
771
  }
604
- var DIM2, RESET2, CYAN, GREEN, RED, YELLOW, BOLD, SPINNER_FRAMES, CLEAR_LINE;
772
+ var SPINNER_FRAMES, CLEAR_LINE;
605
773
  var init_display = __esm({
606
774
  "src/core/display.ts"() {
607
775
  "use strict";
608
776
  init_esm_shims();
609
- DIM2 = "\x1B[2m";
610
- RESET2 = "\x1B[0m";
611
- CYAN = "\x1B[36m";
612
- GREEN = "\x1B[32m";
613
- RED = "\x1B[31m";
614
- YELLOW = "\x1B[33m";
615
- BOLD = "\x1B[1m";
777
+ init_colors();
616
778
  SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
617
779
  CLEAR_LINE = "\x1B[2K\r";
618
780
  }
@@ -674,7 +836,7 @@ async function runAgent(config, prompt, extractResult) {
674
836
  const msg = err instanceof Error ? err.message : String(err);
675
837
  throw new AgentError(`agent "${config.id}" (model ${modelIdOf(model)}) failed: ${msg}`, config.id, err);
676
838
  };
677
- const YELLOW4 = "\x1B[33m";
839
+ const YELLOW3 = "\x1B[33m";
678
840
  const RESET8 = "\x1B[0m";
679
841
  for (let retry = 0; retry < MAX_RETRIES; retry++) {
680
842
  const heartbeat = () => {
@@ -699,7 +861,7 @@ async function runAgent(config, prompt, extractResult) {
699
861
  while (extractResult() === void 0 && nudges < MAX_NUDGES) {
700
862
  nudges++;
701
863
  console.log(
702
- ` ${YELLOW4}[${config.id}] agent stopped without finishing - nudging (${nudges}/${MAX_NUDGES})...${RESET8}`
864
+ ` ${YELLOW3}[${config.id}] agent stopped without finishing - nudging (${nudges}/${MAX_NUDGES})...${RESET8}`
703
865
  );
704
866
  track("cli_agent_nudged", { agent: config.id, nudge: nudges });
705
867
  messages.push(...generation.response.messages);
@@ -715,13 +877,13 @@ async function runAgent(config, prompt, extractResult) {
715
877
  if (errorClass === "fatal") fail(err);
716
878
  const msg = err instanceof Error ? err.message : String(err);
717
879
  if (errorClass === "timeout") {
718
- console.log(` ${YELLOW4}[${config.id}] step timed out after ${stepTimeout / 1e3}s${RESET8}`);
880
+ console.log(` ${YELLOW3}[${config.id}] step timed out after ${stepTimeout / 1e3}s${RESET8}`);
719
881
  } else {
720
- console.log(` ${YELLOW4}[${config.id}] provider error: ${msg}${RESET8}`);
882
+ console.log(` ${YELLOW3}[${config.id}] provider error: ${msg}${RESET8}`);
721
883
  }
722
884
  track("cli_agent_retryable_error", { agent: config.id, error_class: errorClass, retry });
723
885
  if (retry < MAX_RETRIES - 1) {
724
- console.log(` ${YELLOW4}[${config.id}] retrying (${retry + 1}/${MAX_RETRIES})...${RESET8}`);
886
+ console.log(` ${YELLOW3}[${config.id}] retrying (${retry + 1}/${MAX_RETRIES})...${RESET8}`);
725
887
  if (errorClass === "transient") {
726
888
  await sleep(Math.min(2e3 * 2 ** retry, 1e4));
727
889
  }
@@ -749,8 +911,8 @@ var init_agent = __esm({
749
911
  });
750
912
 
751
913
  // src/core/gitignore.ts
752
- import { readFile as readFile5 } from "fs/promises";
753
- import { join as join10, relative as relative2 } from "path";
914
+ import { readFile as readFile6 } from "fs/promises";
915
+ import { join as join11, relative as relative2 } from "path";
754
916
  import { glob as glob2 } from "glob";
755
917
  async function loadGitignorePatterns(projectRoot) {
756
918
  const patterns = [
@@ -770,10 +932,10 @@ async function loadGitignorePatterns(projectRoot) {
770
932
  ];
771
933
  const matches = await glob2("**/.gitignore", { cwd: projectRoot, dot: true });
772
934
  for (const match of matches) {
773
- const fullPath = join10(projectRoot, match);
935
+ const fullPath = join11(projectRoot, match);
774
936
  try {
775
- const content = await readFile5(fullPath, "utf-8");
776
- const prefix = relative2(projectRoot, join10(projectRoot, match, ".."));
937
+ const content = await readFile6(fullPath, "utf-8");
938
+ const prefix = relative2(projectRoot, join11(projectRoot, match, ".."));
777
939
  const parsed = parseGitignore(content, prefix);
778
940
  patterns.push(...parsed);
779
941
  } catch (err) {
@@ -836,7 +998,7 @@ var init_exec_error = __esm({
836
998
  import { execFile as execFile3 } from "child_process";
837
999
  import { promisify as promisify2 } from "util";
838
1000
  import { tool } from "ai";
839
- import { z as z2 } from "zod";
1001
+ import { z as z4 } from "zod";
840
1002
  function validateCommand(command, allowed) {
841
1003
  const trimmed = command.trim();
842
1004
  if (trimmed.length === 0) return "Empty command";
@@ -902,8 +1064,8 @@ var init_bash = __esm({
902
1064
  DEFAULT_ALLOWED = /* @__PURE__ */ new Set(["git", "wc", "sort", "head", "tail", "cat", "ls", "find", "diff", "echo"]);
903
1065
  TIMEOUT_MS = 3e4;
904
1066
  MAX_OUTPUT_BYTES = 1024 * 512;
905
- inputSchema = z2.object({
906
- command: z2.string().describe("Shell command to execute")
1067
+ inputSchema = z4.object({
1068
+ command: z4.string().describe("Shell command to execute")
907
1069
  });
908
1070
  }
909
1071
  });
@@ -911,7 +1073,7 @@ var init_bash = __esm({
911
1073
  // src/tools/glob.ts
912
1074
  import { tool as tool2 } from "ai";
913
1075
  import { glob as glob3 } from "glob";
914
- import { z as z3 } from "zod";
1076
+ import { z as z5 } from "zod";
915
1077
  async function executeGlob(pattern, cwd, ignorePatterns = DEFAULT_IGNORE) {
916
1078
  try {
917
1079
  const matches = await glob3(pattern, {
@@ -938,9 +1100,9 @@ var init_glob = __esm({
938
1100
  "src/tools/glob.ts"() {
939
1101
  "use strict";
940
1102
  init_esm_shims();
941
- inputSchema2 = z3.object({
942
- pattern: z3.string().describe("Glob pattern to match files (e.g. '**/*.ts', 'src/**/*.py')"),
943
- cwd: z3.string().optional().describe("Directory to search in. Defaults to working directory.")
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.")
944
1106
  });
945
1107
  DEFAULT_IGNORE = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
946
1108
  }
@@ -950,7 +1112,7 @@ var init_glob = __esm({
950
1112
  import { execFile as execFile4 } from "child_process";
951
1113
  import { promisify as promisify3 } from "util";
952
1114
  import { tool as tool3 } from "ai";
953
- import { z as z4 } from "zod";
1115
+ import { z as z6 } from "zod";
954
1116
  function buildGrepTool(workingDirectory) {
955
1117
  return tool3({
956
1118
  description: "Search file contents with ripgrep. Returns matching lines with file paths and line numbers.",
@@ -994,10 +1156,10 @@ var init_grep = __esm({
994
1156
  init_esm_shims();
995
1157
  init_exec_error();
996
1158
  execFileAsync3 = promisify3(execFile4);
997
- inputSchema3 = z4.object({
998
- pattern: z4.string().describe("Regex pattern to search for in file contents"),
999
- glob: z4.string().optional().describe("Glob to filter files (e.g. '*.ts')"),
1000
- path: z4.string().optional().describe("File or directory to search in")
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")
1001
1163
  });
1002
1164
  }
1003
1165
  });
@@ -1005,10 +1167,10 @@ var init_grep = __esm({
1005
1167
  // src/tools/list-directory.ts
1006
1168
  import { readdir } from "fs/promises";
1007
1169
  import { stat } from "fs/promises";
1008
- import { join as join11, relative as relative3 } from "path";
1170
+ import { join as join12, relative as relative3 } from "path";
1009
1171
  import { tool as tool4 } from "ai";
1010
1172
  import { minimatch } from "minimatch";
1011
- import { z as z5 } from "zod";
1173
+ import { z as z7 } from "zod";
1012
1174
  function buildMatcher(patterns) {
1013
1175
  const positive = patterns.filter((p10) => !p10.startsWith("!"));
1014
1176
  const negative = patterns.filter((p10) => p10.startsWith("!")).map((p10) => p10.slice(1));
@@ -1030,7 +1192,7 @@ async function buildTree(dirPath, maxDepth, currentDepth, isIgnored, relativeBas
1030
1192
  const withTypes = [];
1031
1193
  for (const name of rawEntries) {
1032
1194
  try {
1033
- const s = await stat(join11(dirPath, name));
1195
+ const s = await stat(join12(dirPath, name));
1034
1196
  withTypes.push({ name, isDir: s.isDirectory() });
1035
1197
  } catch {
1036
1198
  withTypes.push({ name, isDir: false });
@@ -1050,7 +1212,7 @@ async function buildTree(dirPath, maxDepth, currentDepth, isIgnored, relativeBas
1050
1212
  }
1051
1213
  if (entry.isDir) {
1052
1214
  const children = await buildTree(
1053
- join11(dirPath, entry.name),
1215
+ join12(dirPath, entry.name),
1054
1216
  maxDepth,
1055
1217
  currentDepth + 1,
1056
1218
  isIgnored,
@@ -1088,10 +1250,10 @@ async function buildListDirectoryTool(workingDirectory) {
1088
1250
  const isIgnored = buildMatcher(patterns);
1089
1251
  return tool4({
1090
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.",
1091
- inputSchema: z5.object({
1092
- path: z5.string().default(".").describe("Directory path relative to project root. Defaults to root."),
1093
- depth: z5.number().min(1).max(15).default(10).describe("Max depth to traverse (1-15). Default 10."),
1094
- gitignore: z5.boolean().describe(
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(
1095
1257
  "Whether to respect the gitignore or to ignore it. true will respect it. false will ignore it. Default true"
1096
1258
  ).default(true)
1097
1259
  }),
@@ -1103,7 +1265,7 @@ async function buildListDirectoryTool(workingDirectory) {
1103
1265
  };
1104
1266
  }
1105
1267
  seen.add(cacheKey);
1106
- const targetDir = input.path === "." ? workingDirectory : join11(workingDirectory, input.path);
1268
+ const targetDir = input.path === "." ? workingDirectory : join12(workingDirectory, input.path);
1107
1269
  try {
1108
1270
  const s = await stat(targetDir);
1109
1271
  if (!s.isDirectory()) {
@@ -1135,10 +1297,10 @@ var init_list_directory = __esm({
1135
1297
  });
1136
1298
 
1137
1299
  // src/tools/read-file.ts
1138
- import { readFile as readFile6 } from "fs/promises";
1300
+ import { readFile as readFile7 } from "fs/promises";
1139
1301
  import { relative as relative4, resolve as resolve2 } from "path";
1140
1302
  import { tool as tool5 } from "ai";
1141
- import { z as z6 } from "zod";
1303
+ import { z as z8 } from "zod";
1142
1304
  function resolveSandboxedPath(workingDirectory, filePath) {
1143
1305
  const absolutePath = resolve2(workingDirectory, filePath);
1144
1306
  const relativePath = relative4(workingDirectory, absolutePath);
@@ -1163,7 +1325,7 @@ async function executeReadFile(workingDirectory, filePath, offset, limit) {
1163
1325
  const resolved = resolveSandboxedPath(workingDirectory, filePath);
1164
1326
  if ("error" in resolved) return resolved;
1165
1327
  try {
1166
- const content = await readFile6(resolved.absolutePath, "utf-8");
1328
+ const content = await readFile7(resolved.absolutePath, "utf-8");
1167
1329
  const sliced = sliceLines(content, offset ?? 0, limit ?? MAX_LINES);
1168
1330
  return {
1169
1331
  path: resolved.relativePath,
@@ -1191,10 +1353,10 @@ var init_read_file = __esm({
1191
1353
  "use strict";
1192
1354
  init_esm_shims();
1193
1355
  MAX_LINES = 2e3;
1194
- inputSchema4 = z6.object({
1195
- filePath: z6.string().describe("Path to the file (absolute or relative to working directory)"),
1196
- offset: z6.number().int().min(0).optional().describe("Line number to start reading from (0-based)"),
1197
- limit: z6.number().int().min(1).optional().describe("Maximum number of lines to read")
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")
1198
1360
  });
1199
1361
  }
1200
1362
  });
@@ -1217,10 +1379,10 @@ var init_pick_string = __esm({
1217
1379
 
1218
1380
  // src/tools/subagent.ts
1219
1381
  import { ToolLoopAgent as ToolLoopAgent2, hasToolCall as hasToolCall2, stepCountIs as stepCountIs2, tool as tool6 } from "ai";
1220
- import { z as z7 } from "zod";
1382
+ import { z as z9 } from "zod";
1221
1383
  function buildSubagentTools(workingDirectory, onFileRead) {
1222
1384
  const baseReadFile = buildReadFileTool(workingDirectory);
1223
- const readFile23 = onFileRead ? tool6({
1385
+ const readFile24 = onFileRead ? tool6({
1224
1386
  description: baseReadFile.description,
1225
1387
  inputSchema: baseReadFile.inputSchema,
1226
1388
  execute: async (input, options) => {
@@ -1233,7 +1395,7 @@ function buildSubagentTools(workingDirectory, onFileRead) {
1233
1395
  bash: buildBashTool(workingDirectory),
1234
1396
  glob: buildGlobTool(workingDirectory),
1235
1397
  grep: buildGrepTool(workingDirectory),
1236
- read_file: readFile23
1398
+ read_file: readFile24
1237
1399
  };
1238
1400
  }
1239
1401
  function buildSubagentTool(model, workingDirectory, onHeartbeat, onFileRead) {
@@ -1284,11 +1446,11 @@ var init_subagent = __esm({
1284
1446
  init_glob();
1285
1447
  init_grep();
1286
1448
  init_read_file();
1287
- inputSchema5 = z7.object({
1288
- instruction: z7.string().describe("Focused task for the subagent. Be specific about files and patterns to investigate.")
1449
+ inputSchema5 = z9.object({
1450
+ instruction: z9.string().describe("Focused task for the subagent. Be specific about files and patterns to investigate.")
1289
1451
  });
1290
- resultSchema = z7.object({
1291
- findings: z7.string().describe("Summary of what was found")
1452
+ resultSchema = z9.object({
1453
+ findings: z9.string().describe("Summary of what was found")
1292
1454
  });
1293
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).
1294
1456
 
@@ -1299,10 +1461,10 @@ Be thorough but focused - only investigate what's relevant to your instruction.`
1299
1461
  });
1300
1462
 
1301
1463
  // src/tools/write-file.ts
1302
- import { writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
1464
+ import { writeFile as writeFile5, mkdir as mkdir2 } from "fs/promises";
1303
1465
  import { dirname as dirname2, relative as relative5, resolve as resolve3 } from "path";
1304
1466
  import { tool as tool7 } from "ai";
1305
- import { z as z8 } from "zod";
1467
+ import { z as z10 } from "zod";
1306
1468
  async function executeWriteFile(outputDirectory, filePath, content) {
1307
1469
  const cleaned = filePath.replace(/^autonoma\//, "");
1308
1470
  const absolutePath = resolve3(outputDirectory, cleaned);
@@ -1312,7 +1474,7 @@ async function executeWriteFile(outputDirectory, filePath, content) {
1312
1474
  }
1313
1475
  try {
1314
1476
  await mkdir2(dirname2(absolutePath), { recursive: true });
1315
- await writeFile4(absolutePath, content, "utf-8");
1477
+ await writeFile5(absolutePath, content, "utf-8");
1316
1478
  return { path: relativePath, bytesWritten: content.length };
1317
1479
  } catch (err) {
1318
1480
  const message = err instanceof Error ? err.message : String(err);
@@ -1331,9 +1493,9 @@ var init_write_file = __esm({
1331
1493
  "src/tools/write-file.ts"() {
1332
1494
  "use strict";
1333
1495
  init_esm_shims();
1334
- inputSchema6 = z8.object({
1335
- filePath: z8.string().describe("Path to write (absolute or relative to output directory)"),
1336
- content: z8.string().describe("File content to write")
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")
1337
1499
  });
1338
1500
  }
1339
1501
  });
@@ -1341,16 +1503,21 @@ var init_write_file = __esm({
1341
1503
  // src/tools/ask-user.ts
1342
1504
  import * as p2 from "@clack/prompts";
1343
1505
  import { tool as tool8 } from "ai";
1344
- import { z as z9 } from "zod";
1506
+ import { z as z11 } from "zod";
1345
1507
  function buildAskUserTool() {
1346
1508
  return tool8({
1347
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.",
1348
- inputSchema: z9.object({
1349
- question: z9.string().describe(
1510
+ inputSchema: z11.object({
1511
+ question: z11.string().describe(
1350
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?'"
1351
1513
  )
1352
1514
  }),
1353
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
+ }
1354
1521
  const answer = await p2.text({ message: input.question });
1355
1522
  if (p2.isCancel(answer)) return { answer: "User skipped this question" };
1356
1523
  return { answer };
@@ -1394,6 +1561,58 @@ var init_tools = __esm({
1394
1561
  }
1395
1562
  });
1396
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
+
1397
1616
  // src/agents/00-pages-finder/index.ts
1398
1617
  var pages_finder_exports = {};
1399
1618
  __export(pages_finder_exports, {
@@ -1401,8 +1620,8 @@ __export(pages_finder_exports, {
1401
1620
  });
1402
1621
  import { existsSync } from "fs";
1403
1622
  import * as path2 from "path";
1404
- import { tool as tool9 } from "ai";
1405
- import { z as z10 } from "zod";
1623
+ import { tool as tool10 } from "ai";
1624
+ import { z as z12 } from "zod";
1406
1625
  async function runPageFinder(input) {
1407
1626
  const model = getModel(input.modelId);
1408
1627
  const pageCollector = new PageCollector();
@@ -1421,7 +1640,7 @@ ${input.extraMessage}`;
1421
1640
  const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat);
1422
1641
  return {
1423
1642
  ...tools,
1424
- add_page: tool9({
1643
+ add_page: tool10({
1425
1644
  description: "use this tool to add a page that you found",
1426
1645
  inputSchema: Page,
1427
1646
  execute: (input2) => {
@@ -1433,9 +1652,9 @@ ${input.extraMessage}`;
1433
1652
  return `page ${JSON.stringify(input2)} added`;
1434
1653
  }
1435
1654
  }),
1436
- view_pages: tool9({
1655
+ view_pages: tool10({
1437
1656
  description: "use this tool to view all the pages that you already added",
1438
- inputSchema: z10.object(),
1657
+ inputSchema: z12.object(),
1439
1658
  execute: () => pageCollector.viewPages()
1440
1659
  })
1441
1660
  };
@@ -1454,10 +1673,10 @@ var init_pages_finder = __esm({
1454
1673
  init_agent();
1455
1674
  init_model();
1456
1675
  init_tools();
1457
- Page = z10.object({
1458
- route: z10.string().min(1),
1459
- path: z10.string().min(1),
1460
- description: z10.string().min(10)
1676
+ Page = z12.object({
1677
+ route: z12.string().min(1),
1678
+ path: z12.string().min(1),
1679
+ description: z12.string().min(10)
1461
1680
  });
1462
1681
  PageCollector = class {
1463
1682
  // the key is the path
@@ -1487,13 +1706,13 @@ var init_pages_finder = __esm({
1487
1706
 
1488
1707
  // src/core/review.ts
1489
1708
  import { access } from "fs/promises";
1490
- import { join as join12, isAbsolute } from "path";
1709
+ import { join as join13, isAbsolute } from "path";
1491
1710
  import * as p3 from "@clack/prompts";
1492
1711
  import spawn from "cross-spawn";
1493
1712
  import which from "which";
1494
1713
  function resolvePath(artifact, outputDir) {
1495
1714
  if (isAbsolute(artifact)) return artifact;
1496
- return join12(outputDir, artifact);
1715
+ return join13(outputDir, artifact);
1497
1716
  }
1498
1717
  async function detectEditors() {
1499
1718
  if (cachedEditors) return cachedEditors;
@@ -1508,17 +1727,17 @@ async function detectEditors() {
1508
1727
  async function launchEditor(editor, files) {
1509
1728
  const args = editor.args(files);
1510
1729
  const isTerminalEditor = TERMINAL_EDITORS.has(editor.command);
1511
- await new Promise((resolve5) => {
1730
+ await new Promise((resolve6) => {
1512
1731
  let settled = false;
1513
1732
  const settle = () => {
1514
1733
  if (settled) return;
1515
1734
  settled = true;
1516
- resolve5();
1735
+ resolve6();
1517
1736
  };
1518
1737
  const proc = spawn(editor.command, args, { stdio: "inherit" });
1519
1738
  proc.on("error", (err) => {
1520
1739
  p3.log.warn(`Couldn't open ${editor.label} (${err.message}). Review the files manually:`);
1521
- for (const f of files) console.log(` ${CYAN2}${f}${RESET3}`);
1740
+ for (const f of files) console.log(` ${CYAN}${f}${RESET3}`);
1522
1741
  settle();
1523
1742
  });
1524
1743
  if (isTerminalEditor) {
@@ -1532,7 +1751,7 @@ async function openInEditor(files) {
1532
1751
  const editors = await detectEditors();
1533
1752
  if (editors.length === 0) {
1534
1753
  p3.log.warn("No editors found. Review the files manually:");
1535
- for (const f of files) console.log(` ${CYAN2}${f}${RESET3}`);
1754
+ for (const f of files) console.log(` ${CYAN}${f}${RESET3}`);
1536
1755
  return;
1537
1756
  }
1538
1757
  if (preferredEditor) {
@@ -1571,11 +1790,11 @@ async function openInEditor(files) {
1571
1790
  }
1572
1791
  async function showResults(result, options) {
1573
1792
  console.log("");
1574
- console.log(` ${GREEN2}[${options.agentId}] Step complete.${RESET3}`);
1793
+ console.log(` ${GREEN}[${options.agentId}] Step complete.${RESET3}`);
1575
1794
  if (result.artifacts.length === 0) {
1576
1795
  const knownFiles = ["AUTONOMA.md", "entity-audit.md", "scenarios.md"];
1577
1796
  for (const f of knownFiles) {
1578
- const fullPath = join12(options.outputDir, f);
1797
+ const fullPath = join13(options.outputDir, f);
1579
1798
  try {
1580
1799
  await access(fullPath);
1581
1800
  result.artifacts.push(f);
@@ -1590,7 +1809,7 @@ async function showResults(result, options) {
1590
1809
  for (const a of result.artifacts) {
1591
1810
  const fullPath = resolvePath(a, options.outputDir);
1592
1811
  resolvedPaths.push(fullPath);
1593
- console.log(` ${CYAN2}${fullPath}${RESET3}`);
1812
+ console.log(` ${CYAN}${fullPath}${RESET3}`);
1594
1813
  }
1595
1814
  }
1596
1815
  if (result.summary) {
@@ -1641,7 +1860,7 @@ async function reviewLoop(result, options) {
1641
1860
  await showResults(result, options);
1642
1861
  }
1643
1862
  }
1644
- var DIM3, CYAN2, GREEN2, RESET3, EDITORS, cachedEditors, preferredEditor, TERMINAL_EDITORS;
1863
+ var DIM3, CYAN, GREEN, RESET3, EDITORS, cachedEditors, preferredEditor, TERMINAL_EDITORS;
1645
1864
  var init_review = __esm({
1646
1865
  "src/core/review.ts"() {
1647
1866
  "use strict";
@@ -1649,8 +1868,8 @@ var init_review = __esm({
1649
1868
  init_debug();
1650
1869
  init_notify();
1651
1870
  DIM3 = "\x1B[2m";
1652
- CYAN2 = "\x1B[36m";
1653
- GREEN2 = "\x1B[32m";
1871
+ CYAN = "\x1B[36m";
1872
+ GREEN = "\x1B[32m";
1654
1873
  RESET3 = "\x1B[0m";
1655
1874
  EDITORS = [
1656
1875
  { command: "cursor", label: "Cursor", args: (f) => f },
@@ -1664,13 +1883,13 @@ var init_review = __esm({
1664
1883
  });
1665
1884
 
1666
1885
  // src/agents/01-kb-generator/flows.ts
1667
- import { readFile as readFile7 } from "fs/promises";
1668
- import { join as join13 } from "path";
1886
+ import { readFile as readFile8 } from "fs/promises";
1887
+ import { join as join14 } from "path";
1669
1888
  import matter from "gray-matter";
1670
1889
  async function parseCoreFlows(outputDir) {
1671
1890
  let raw;
1672
1891
  try {
1673
- raw = await readFile7(join13(outputDir, "AUTONOMA.md"), "utf-8");
1892
+ raw = await readFile8(join14(outputDir, "AUTONOMA.md"), "utf-8");
1674
1893
  } catch {
1675
1894
  return [];
1676
1895
  }
@@ -1714,7 +1933,7 @@ function renderFlowsTable(flows) {
1714
1933
  const sep = `${DIM4}${"\u2500".repeat(numW + nameW + critW + DESC_MAX + 6)}${RESET4}`;
1715
1934
  const body = rows.map((r) => {
1716
1935
  const line = `${pad(r.num, numW)} ${pad(r.name, nameW)} ${pad(r.crit, critW)} ${r.desc}`;
1717
- return r.crit === "core" ? `${YELLOW2}${line}${RESET4}` : line;
1936
+ return r.crit === "core" ? `${YELLOW}${line}${RESET4}` : line;
1718
1937
  }).join("\n");
1719
1938
  const caption = `${DIM4}${flows.length} flows \xB7 ${coreCount} marked core${RESET4}`;
1720
1939
  return `${header}
@@ -1722,25 +1941,25 @@ ${sep}
1722
1941
  ${body}
1723
1942
  ${caption}`;
1724
1943
  }
1725
- var RESET4, DIM4, YELLOW2, BOLD2;
1944
+ var RESET4, DIM4, YELLOW, BOLD2;
1726
1945
  var init_flows = __esm({
1727
1946
  "src/agents/01-kb-generator/flows.ts"() {
1728
1947
  "use strict";
1729
1948
  init_esm_shims();
1730
1949
  RESET4 = "\x1B[0m";
1731
1950
  DIM4 = "\x1B[2m";
1732
- YELLOW2 = "\x1B[33m";
1951
+ YELLOW = "\x1B[33m";
1733
1952
  BOLD2 = "\x1B[1m";
1734
1953
  }
1735
1954
  });
1736
1955
 
1737
1956
  // src/agents/01-kb-generator/prompt.ts
1738
- var SYSTEM_PROMPT2;
1957
+ var SYSTEM_PROMPT3;
1739
1958
  var init_prompt = __esm({
1740
1959
  "src/agents/01-kb-generator/prompt.ts"() {
1741
1960
  "use strict";
1742
1961
  init_esm_shims();
1743
- SYSTEM_PROMPT2 = `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.
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.
1744
1963
 
1745
1964
  ## Your output
1746
1965
 
@@ -1883,15 +2102,15 @@ var kb_generator_exports = {};
1883
2102
  __export(kb_generator_exports, {
1884
2103
  runKBGenerator: () => runKBGenerator
1885
2104
  });
1886
- import { readFile as readFile8 } from "fs/promises";
1887
- import { join as join14 } from "path";
1888
- import { tool as tool10 } from "ai";
1889
- import { z as z11 } from "zod";
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";
1890
2109
  function buildRegisterPagesTool(tracker) {
1891
- return tool10({
2110
+ return tool11({
1892
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.",
1893
- inputSchema: z11.object({
1894
- pages: z11.array(z11.string()).describe("All page file paths found by glob")
2112
+ inputSchema: z13.object({
2113
+ pages: z13.array(z13.string()).describe("All page file paths found by glob")
1895
2114
  }),
1896
2115
  execute: async (input) => {
1897
2116
  tracker.register(input.pages);
@@ -1903,25 +2122,33 @@ function buildRegisterPagesTool(tracker) {
1903
2122
  });
1904
2123
  }
1905
2124
  function buildPageCoverageTool(tracker) {
1906
- return tool10({
2125
+ return tool11({
1907
2126
  description: "Check how many registered pages you've read vs how many remain.",
1908
- inputSchema: z11.object({}),
2127
+ inputSchema: z13.object({}),
1909
2128
  execute: async () => tracker.coverage()
1910
2129
  });
1911
2130
  }
2131
+ function requiredReads(total) {
2132
+ if (total <= FULL_COVERAGE_MAX_ROUTES) return total;
2133
+ return Math.ceil(total * LARGE_APP_COVERAGE_FLOOR);
2134
+ }
1912
2135
  function buildFinishTool(tracker, onFinish) {
1913
- return tool10({
1914
- description: "Call when you have finished generating the knowledge base. BLOCKED if there are unread pages - call page_coverage first to check.",
1915
- inputSchema: z11.object({
1916
- summary: z11.string().describe("Summary of what was generated"),
1917
- artifacts: z11.array(z11.string()).describe("List of files written")
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")
1918
2141
  }),
1919
2142
  execute: async (input) => {
1920
2143
  const cov = tracker.coverage();
1921
- if (cov.unread.length > 0) {
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` : "";
1922
2149
  return {
1923
- error: `Cannot finish: ${cov.unread.length}/${cov.total} pages not yet read. Read these files first:
1924
- ${cov.unread.join("\n")}`
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}`
1925
2152
  };
1926
2153
  }
1927
2154
  onFinish({
@@ -1934,7 +2161,7 @@ ${cov.unread.join("\n")}`
1934
2161
  });
1935
2162
  }
1936
2163
  function buildTrackedReadTool(tracker, baseTool) {
1937
- return tool10({
2164
+ return tool11({
1938
2165
  description: baseTool.description,
1939
2166
  inputSchema: baseTool.inputSchema,
1940
2167
  execute: async (input, options) => {
@@ -1944,13 +2171,36 @@ function buildTrackedReadTool(tracker, baseTool) {
1944
2171
  }
1945
2172
  });
1946
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
+ }
1947
2194
  async function runKBGenerator(input) {
1948
2195
  const model = getModel(input.modelId);
1949
2196
  let result;
1950
- const tracker = new PageTracker();
2197
+ const setResult = (r) => {
2198
+ result = r;
2199
+ };
1951
2200
  const { logger, onStepFinish } = buildDefaultStepLogger("kb", 150);
1952
2201
  const contextBlock = (input.projectContext ? "\n" + formatContext(input.projectContext) + "\n" : "") + formatRetryGuidance(input.retryGuidance);
1953
2202
  const pages = input.projectContext?.pages;
2203
+ const tracker = new PageTracker(input.projectRoot);
1954
2204
  if (pages?.length) {
1955
2205
  tracker.register(pages.map((p10) => p10.path));
1956
2206
  }
@@ -1962,7 +2212,7 @@ Pages have already been discovered (${pages.length} routes pre-registered). You
1962
2212
  2. Read EVERY registered page file with read_file - the system tracks this
1963
2213
  3. Write AUTONOMA.md progressively as you go (update it after each major area)
1964
2214
  4. Call page_coverage to verify you've read all pages
1965
- 5. Call finish - it will REJECT if pages are unread
2215
+ 5. Call finish - it will REJECT if you have not read enough of the registered routes
1966
2216
 
1967
2217
  Output files:
1968
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.
@@ -1974,34 +2224,15 @@ MANDATORY PROCESS:
1974
2224
  4. Read EVERY registered page file with read_file - the system tracks this
1975
2225
  5. Write AUTONOMA.md progressively as you go (update it after each major area)
1976
2226
  6. Call page_coverage to verify you've read all pages
1977
- 7. Call finish - it will REJECT if pages are unread
2227
+ 7. Call finish - it will REJECT if you have not read enough of the registered routes
1978
2228
 
1979
2229
  Output files:
1980
2230
  1. AUTONOMA.md - with YAML frontmatter (app_name, app_description, core_flows, feature_count)`;
1981
- const agentConfig = {
1982
- id: "kb-generator",
1983
- systemPrompt: SYSTEM_PROMPT2,
1984
- model,
1985
- maxSteps: 150,
1986
- tools: async (heartbeat) => {
1987
- const onFileRead = (path3) => tracker.markRead(path3);
1988
- const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat, onFileRead);
1989
- return {
1990
- ...tools,
1991
- read_file: buildTrackedReadTool(tracker, tools.read_file),
1992
- register_pages: buildRegisterPagesTool(tracker),
1993
- page_coverage: buildPageCoverageTool(tracker),
1994
- finish: buildFinishTool(tracker, (r) => {
1995
- result = r;
1996
- })
1997
- };
1998
- },
1999
- onStepFinish
2000
- };
2231
+ const agentConfig = buildKbAgentConfig(tracker, model, input, onStepFinish, setResult);
2001
2232
  await runAgent(agentConfig, prompt, () => result);
2002
2233
  logger.summary();
2003
- const autonomaPath = join14(input.outputDir, "AUTONOMA.md");
2004
- const autonomaExists = await readFile8(autonomaPath, "utf-8").then(() => true).catch((err) => {
2234
+ const autonomaPath = join15(input.outputDir, "AUTONOMA.md");
2235
+ const autonomaExists = await readFile9(autonomaPath, "utf-8").then(() => true).catch((err) => {
2005
2236
  debugLog("AUTONOMA.md not found while checking step completion", { err });
2006
2237
  return false;
2007
2238
  });
@@ -2012,6 +2243,13 @@ Output files:
2012
2243
  summary: "Knowledge base generated."
2013
2244
  };
2014
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);
2015
2253
  const declaredCriticalFlows = input.projectContext?.criticalFlows?.trim();
2016
2254
  if (result?.success && declaredCriticalFlows) {
2017
2255
  const beforeSelfReview = result;
@@ -2028,7 +2266,7 @@ Read your AUTONOMA.md output. For EACH critical flow the user named:
2028
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.
2029
2267
 
2030
2268
  When AUTONOMA.md correctly reflects every declared critical flow, call finish.`;
2031
- await runAgent(agentConfig, selfReviewPrompt, () => result);
2269
+ await runAgent(finalConfig, selfReviewPrompt, () => result);
2032
2270
  if (!result) result = beforeSelfReview;
2033
2271
  }
2034
2272
  const reviewed = await reviewLoop(result, {
@@ -2049,7 +2287,7 @@ When AUTONOMA.md correctly reflects every declared critical flow, call finish.`;
2049
2287
  Read your previous output file (AUTONOMA.md) from the output directory to see what you produced.
2050
2288
  Adjust based on the feedback. You can read source files again if needed.
2051
2289
  Call page_coverage to see current state. When done with changes, call finish again.`;
2052
- await runAgent(agentConfig, feedbackPrompt, () => result);
2290
+ await runAgent(finalConfig, feedbackPrompt, () => result);
2053
2291
  return result;
2054
2292
  }
2055
2293
  });
@@ -2059,7 +2297,7 @@ Call page_coverage to see current state. When done with changes, call finish aga
2059
2297
  summary: "KB generator agent stopped without producing AUTONOMA.md"
2060
2298
  };
2061
2299
  }
2062
- var PageTracker;
2300
+ var PageTracker, FULL_COVERAGE_MAX_ROUTES, LARGE_APP_COVERAGE_FLOOR;
2063
2301
  var init_kb_generator = __esm({
2064
2302
  "src/agents/01-kb-generator/index.ts"() {
2065
2303
  "use strict";
@@ -2074,14 +2312,26 @@ var init_kb_generator = __esm({
2074
2312
  init_flows();
2075
2313
  init_prompt();
2076
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;
2077
2323
  registered = /* @__PURE__ */ new Set();
2078
2324
  read = /* @__PURE__ */ new Set();
2325
+ normalize(filePath) {
2326
+ return resolve5(this.projectRoot, filePath);
2327
+ }
2079
2328
  register(pages) {
2080
- for (const p10 of pages) this.registered.add(p10);
2329
+ for (const p10 of pages) this.registered.add(this.normalize(p10));
2081
2330
  }
2082
2331
  markRead(filePath) {
2083
- if (this.registered.has(filePath)) {
2084
- this.read.add(filePath);
2332
+ const normalized = this.normalize(filePath);
2333
+ if (this.registered.has(normalized)) {
2334
+ this.read.add(normalized);
2085
2335
  }
2086
2336
  }
2087
2337
  unread() {
@@ -2095,16 +2345,18 @@ var init_kb_generator = __esm({
2095
2345
  };
2096
2346
  }
2097
2347
  };
2348
+ FULL_COVERAGE_MAX_ROUTES = 40;
2349
+ LARGE_APP_COVERAGE_FLOOR = 0.5;
2098
2350
  }
2099
2351
  });
2100
2352
 
2101
2353
  // src/agents/04-recipe-builder/entity-order.ts
2102
- import { readFile as readFile9 } from "fs/promises";
2103
- import { join as join15 } from "path";
2354
+ import { readFile as readFile10 } from "fs/promises";
2355
+ import { join as join16 } from "path";
2104
2356
  import matter2 from "gray-matter";
2105
- import { z as z12 } from "zod";
2357
+ import { z as z14 } from "zod";
2106
2358
  async function parseEntityAudit(outputDir) {
2107
- const raw = await readFile9(join15(outputDir, "entity-audit.md"), "utf-8");
2359
+ const raw = await readFile10(join16(outputDir, "entity-audit.md"), "utf-8");
2108
2360
  try {
2109
2361
  const parsed = frontmatterSchema.safeParse(matter2(raw).data);
2110
2362
  if (parsed.success && parsed.data.models.length > 0) {
@@ -2265,22 +2517,22 @@ var init_entity_order = __esm({
2265
2517
  "src/agents/04-recipe-builder/entity-order.ts"() {
2266
2518
  "use strict";
2267
2519
  init_esm_shims();
2268
- createdBySchema = z12.object({
2269
- owner: z12.string(),
2270
- via: z12.string().optional(),
2271
- why: z12.string().optional()
2520
+ createdBySchema = z14.object({
2521
+ owner: z14.string(),
2522
+ via: z14.string().optional(),
2523
+ why: z14.string().optional()
2272
2524
  });
2273
- auditedModelSchema = z12.object({
2274
- name: z12.string(),
2275
- independently_created: z12.coerce.boolean().default(false),
2276
- creation_file: z12.string().optional(),
2277
- creation_function: z12.string().optional(),
2278
- side_effects: z12.array(z12.string()).optional(),
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(),
2279
2531
  // Tolerate a stray `created_by:` with no entries (parsed as null by YAML).
2280
- created_by: z12.array(createdBySchema).nullish().transform((v) => v ?? [])
2532
+ created_by: z14.array(createdBySchema).nullish().transform((v) => v ?? [])
2281
2533
  });
2282
- frontmatterSchema = z12.object({
2283
- models: z12.array(auditedModelSchema).nullish().transform((v) => v ?? [])
2534
+ frontmatterSchema = z14.object({
2535
+ models: z14.array(auditedModelSchema).nullish().transform((v) => v ?? [])
2284
2536
  });
2285
2537
  }
2286
2538
  });
@@ -2325,7 +2577,7 @@ function renderEntityAuditTable(models) {
2325
2577
  const sep = `${DIM5}${"\u2500".repeat(numW + nameW + critW + SRC_MAX + 6)}${RESET5}`;
2326
2578
  const body = rows.map((r) => {
2327
2579
  const line = `${pad2(r.num, numW)} ${pad2(r.name, nameW)} ${pad2(r.creation, critW)} ${r.source}`;
2328
- return r.creation === "standalone" ? `${YELLOW3}${line}${RESET5}` : line;
2580
+ return r.creation === "standalone" ? `${YELLOW2}${line}${RESET5}` : line;
2329
2581
  }).join("\n");
2330
2582
  const caption = `${DIM5}${models.length} models \xB7 ${standalone} standalone \xB7 ${models.length - standalone} side-effect-only${RESET5}`;
2331
2583
  return `${header}
@@ -2333,7 +2585,7 @@ ${sep}
2333
2585
  ${body}
2334
2586
  ${caption}`;
2335
2587
  }
2336
- var RESET5, DIM5, YELLOW3, BOLD3;
2588
+ var RESET5, DIM5, YELLOW2, BOLD3;
2337
2589
  var init_audit_table = __esm({
2338
2590
  "src/agents/02-entity-audit/audit-table.ts"() {
2339
2591
  "use strict";
@@ -2341,18 +2593,18 @@ var init_audit_table = __esm({
2341
2593
  init_entity_order();
2342
2594
  RESET5 = "\x1B[0m";
2343
2595
  DIM5 = "\x1B[2m";
2344
- YELLOW3 = "\x1B[33m";
2596
+ YELLOW2 = "\x1B[33m";
2345
2597
  BOLD3 = "\x1B[1m";
2346
2598
  }
2347
2599
  });
2348
2600
 
2349
2601
  // src/agents/02-entity-audit/prompt.ts
2350
- var SYSTEM_PROMPT3;
2602
+ var SYSTEM_PROMPT4;
2351
2603
  var init_prompt2 = __esm({
2352
2604
  "src/agents/02-entity-audit/prompt.ts"() {
2353
2605
  "use strict";
2354
2606
  init_esm_shims();
2355
- SYSTEM_PROMPT3 = `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.
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.
2356
2608
 
2357
2609
  ## The two orthogonal questions
2358
2610
 
@@ -2490,17 +2742,17 @@ var entity_audit_exports = {};
2490
2742
  __export(entity_audit_exports, {
2491
2743
  runEntityAudit: () => runEntityAudit
2492
2744
  });
2493
- import { readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
2494
- import { join as join16 } from "path";
2495
- import { tool as tool11 } from "ai";
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";
2496
2748
  import { glob as glob4 } from "glob";
2497
- import { z as z13 } from "zod";
2749
+ import { z as z15 } from "zod";
2498
2750
  function buildRegisterModelsTool(tracker) {
2499
- return tool11({
2751
+ return tool12({
2500
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.",
2501
- inputSchema: z13.object({
2502
- models: z13.array(z13.string()).describe("All model/table names found by grep"),
2503
- framework: z13.string().describe("Database framework detected (e.g. 'sqlalchemy', 'prisma', 'drizzle')")
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')")
2504
2756
  }),
2505
2757
  execute: async (input) => {
2506
2758
  tracker.register(input.models);
@@ -2514,9 +2766,9 @@ function buildRegisterModelsTool(tracker) {
2514
2766
  });
2515
2767
  }
2516
2768
  function buildNextModelTool(tracker) {
2517
- return tool11({
2769
+ return tool12({
2518
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.",
2519
- inputSchema: z13.object({}),
2771
+ inputSchema: z15.object({}),
2520
2772
  execute: async () => {
2521
2773
  const next = tracker.nextModel();
2522
2774
  if (!next) {
@@ -2534,19 +2786,19 @@ function buildNextModelTool(tracker) {
2534
2786
  });
2535
2787
  }
2536
2788
  function buildMarkModelAuditedTool(tracker) {
2537
- return tool11({
2789
+ return tool12({
2538
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).",
2539
- inputSchema: z13.object({
2540
- model: z13.string().describe("Model name"),
2541
- independently_created: z13.boolean(),
2542
- creation_file: z13.string().optional().describe("File containing the creation function"),
2543
- creation_function: z13.string().optional().describe("Function/method name (e.g. 'UserService.create' or 'create_user')"),
2544
- side_effects: z13.array(z13.string()).optional().describe("Side effects of creation (e.g. 'creates default Settings row', 'hashes password')"),
2545
- created_by: z13.array(
2546
- z13.object({
2547
- owner: z13.string().describe("Owner model name"),
2548
- via: z13.string().optional().describe("Function that creates this model (e.g. 'OrganizationService.create')"),
2549
- why: z13.string().optional().describe("One sentence explaining why this model is created as a side effect")
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")
2550
2802
  })
2551
2803
  ).describe("List of owner models that create this as a side effect, empty array if none")
2552
2804
  }),
@@ -2583,18 +2835,18 @@ function buildMarkModelAuditedTool(tracker) {
2583
2835
  });
2584
2836
  }
2585
2837
  function buildModelCoverageTool(tracker) {
2586
- return tool11({
2838
+ return tool12({
2587
2839
  description: "Check how many registered models you've audited vs how many remain.",
2588
- inputSchema: z13.object({}),
2840
+ inputSchema: z15.object({}),
2589
2841
  execute: async () => tracker.coverage()
2590
2842
  });
2591
2843
  }
2592
2844
  function buildFinishTool2(tracker, onFinish) {
2593
- return tool11({
2845
+ return tool12({
2594
2846
  description: "Call when entity audit is complete. BLOCKED if there are unaudited models - call model_coverage first to check.",
2595
- inputSchema: z13.object({
2596
- summary: z13.string().describe("Summary of the audit"),
2597
- artifacts: z13.array(z13.string()).describe("Files written")
2847
+ inputSchema: z15.object({
2848
+ summary: z15.string().describe("Summary of the audit"),
2849
+ artifacts: z15.array(z15.string()).describe("Files written")
2598
2850
  }),
2599
2851
  execute: async (input) => {
2600
2852
  const cov = tracker.coverage();
@@ -2622,7 +2874,7 @@ async function findPrismaSchema(projectRoot) {
2622
2874
  return candidates[0] ?? void 0;
2623
2875
  }
2624
2876
  async function extractPrismaModels(schemaPath) {
2625
- const content = await readFile10(schemaPath, "utf-8");
2877
+ const content = await readFile11(schemaPath, "utf-8");
2626
2878
  return content.split("\n").filter((line) => line.startsWith("model ")).map((line) => line.split(/\s+/)[1]).filter((name) => name != null);
2627
2879
  }
2628
2880
  async function detectFrameworkAndModels(projectRoot) {
@@ -2645,8 +2897,11 @@ async function runEntityAudit(input) {
2645
2897
  tracker.initQueue();
2646
2898
  preRegisteredCount = detection.models.length;
2647
2899
  }
2648
- const { logger, onStepFinish } = buildDefaultStepLogger("entity-audit", 200);
2649
- const contextBlock = (input.projectContext ? "\n" + formatContext(input.projectContext) + "\n" : "") + formatRetryGuidance(input.retryGuidance);
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);
2650
2905
  const preRegBlock = preRegisteredCount > 0 ? `
2651
2906
  ## Pre-registered models (${preRegisteredCount} found via ${detection.framework} schema at ${detection.schemaFile})
2652
2907
 
@@ -2676,9 +2931,9 @@ After every 10 mark_model_audited calls, use write_file to update entity-audit.m
2676
2931
  write_file already targets the output directory - use just the filename.`;
2677
2932
  const agentConfig = {
2678
2933
  id: "entity-audit",
2679
- systemPrompt: SYSTEM_PROMPT3,
2934
+ systemPrompt: SYSTEM_PROMPT4,
2680
2935
  model,
2681
- maxSteps: 200,
2936
+ maxSteps: MAX_STEPS2,
2682
2937
  tools: async (heartbeat) => {
2683
2938
  const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat);
2684
2939
  return {
@@ -2706,8 +2961,8 @@ ${formatException(err)}`);
2706
2961
  logger.summary();
2707
2962
  const writeCanonicalAudit = async () => {
2708
2963
  if (tracker.auditedModels.size === 0) return void 0;
2709
- const auditPath = join16(input.outputDir, "entity-audit.md");
2710
- await writeFile5(auditPath, tracker.generateAuditMarkdown(), "utf-8");
2964
+ const auditPath = join17(input.outputDir, "entity-audit.md");
2965
+ await writeFile6(auditPath, tracker.generateAuditMarkdown(), "utf-8");
2711
2966
  return auditPath;
2712
2967
  };
2713
2968
  const canonicalPath = await writeCanonicalAudit();
@@ -2744,9 +2999,9 @@ When done with changes, call finish again.`;
2744
2999
  }
2745
3000
  });
2746
3001
  if (!reviewed) {
2747
- const auditPath = join16(input.outputDir, "entity-audit.md");
3002
+ const auditPath = join17(input.outputDir, "entity-audit.md");
2748
3003
  try {
2749
- await readFile10(auditPath, "utf-8");
3004
+ await readFile11(auditPath, "utf-8");
2750
3005
  return {
2751
3006
  success: true,
2752
3007
  artifacts: ["entity-audit.md"],
@@ -2762,7 +3017,7 @@ When done with changes, call finish again.`;
2762
3017
  summary: agentError ? `Entity audit failed: ${agentError}` : "Entity audit agent stopped without producing entity-audit.md"
2763
3018
  };
2764
3019
  }
2765
- var ModelTracker;
3020
+ var MAX_STEPS2, ModelTracker;
2766
3021
  var init_entity_audit = __esm({
2767
3022
  "src/agents/02-entity-audit/index.ts"() {
2768
3023
  "use strict";
@@ -2777,6 +3032,7 @@ var init_entity_audit = __esm({
2777
3032
  init_ask_user();
2778
3033
  init_audit_table();
2779
3034
  init_prompt2();
3035
+ MAX_STEPS2 = 400;
2780
3036
  ModelTracker = class {
2781
3037
  registered = /* @__PURE__ */ new Set();
2782
3038
  auditedModels = /* @__PURE__ */ new Map();
@@ -2887,11 +3143,11 @@ ${duals.length > 0 ? duals.map((m) => `- **${m.name}** - standalone: ${m.creatio
2887
3143
  });
2888
3144
 
2889
3145
  // src/core/parse-entity-audit.ts
2890
- import { readFile as readFile11 } from "fs/promises";
2891
- import { join as join17 } from "path";
3146
+ import { readFile as readFile12 } from "fs/promises";
3147
+ import { join as join18 } from "path";
2892
3148
  async function parseEntityNames(outputDir) {
2893
3149
  try {
2894
- const content = await readFile11(join17(outputDir, "entity-audit.md"), "utf-8");
3150
+ const content = await readFile12(join18(outputDir, "entity-audit.md"), "utf-8");
2895
3151
  const names = [];
2896
3152
  for (const line of content.split("\n")) {
2897
3153
  const match = line.match(/^\s+-\s+name:\s+(.+)$/);
@@ -2964,13 +3220,13 @@ values; the recipe builder generates the exact records from what you write.`;
2964
3220
  });
2965
3221
 
2966
3222
  // src/agents/03-scenario-recipe/scenario-table.ts
2967
- import { readFile as readFile12 } from "fs/promises";
2968
- import { join as join18 } from "path";
3223
+ import { readFile as readFile13 } from "fs/promises";
3224
+ import { join as join19 } from "path";
2969
3225
  import matter3 from "gray-matter";
2970
3226
  async function parseScenario(outputDir) {
2971
3227
  let raw;
2972
3228
  try {
2973
- raw = await readFile12(join18(outputDir, "scenarios.md"), "utf-8");
3229
+ raw = await readFile13(join19(outputDir, "scenarios.md"), "utf-8");
2974
3230
  } catch {
2975
3231
  return { scenarioNames: [], entityTypes: [] };
2976
3232
  }
@@ -3051,22 +3307,22 @@ __export(scenario_recipe_exports, {
3051
3307
  feedbackToScenario: () => feedbackToScenario,
3052
3308
  runScenarioRecipe: () => runScenarioRecipe
3053
3309
  });
3054
- import { readFile as readFile13 } from "fs/promises";
3055
- import { join as join19 } from "path";
3056
- import { tool as tool12 } from "ai";
3057
- import { z as z14 } from "zod";
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";
3058
3314
  function buildFinishTool3(requiredEntities, outputDir, onFinish) {
3059
- return tool12({
3315
+ return tool13({
3060
3316
  description: "Call when scenario design is complete and scenarios.md is written. BLOCKED if any required entities are missing from the scenario.",
3061
- inputSchema: z14.object({
3062
- summary: z14.string().describe("Summary of the scenario"),
3063
- entityCount: z14.number().describe("Number of entity types in the scenario"),
3064
- artifacts: z14.array(z14.string()).describe("Files written")
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")
3065
3321
  }),
3066
3322
  execute: async (input) => {
3067
3323
  let content;
3068
3324
  try {
3069
- content = await readFile13(join19(outputDir, "scenarios.md"), "utf-8");
3325
+ content = await readFile14(join20(outputDir, "scenarios.md"), "utf-8");
3070
3326
  } catch {
3071
3327
  return { error: "Cannot finish: scenarios.md not found. Write it first." };
3072
3328
  }
@@ -3101,7 +3357,10 @@ async function runScenarioRecipe(input) {
3101
3357
  const model = getModel(input.modelId);
3102
3358
  let result;
3103
3359
  const { logger, onStepFinish } = buildDefaultStepLogger("scenario", 40);
3104
- const contextBlock = (input.projectContext ? "\n" + formatContext(input.projectContext) + "\n" : "") + formatRetryGuidance(input.retryGuidance);
3360
+ const scopeBlock = input.scopeHint != null ? `
3361
+ ${input.scopeHint}
3362
+ ` : "";
3363
+ const contextBlock = (input.projectContext ? "\n" + formatContext(input.projectContext) + "\n" : "") + scopeBlock + formatRetryGuidance(input.retryGuidance);
3105
3364
  const requiredEntities = await parseEntityNames(input.outputDir);
3106
3365
  const entityListBlock = requiredEntities.length > 0 ? `
3107
3366
  ## Required entities (${requiredEntities.length} total - ALL must appear in the scenario)
@@ -3163,9 +3422,9 @@ When done with changes, call finish again.`;
3163
3422
  }
3164
3423
  });
3165
3424
  if (!reviewed) {
3166
- const scenariosPath = join19(input.outputDir, "scenarios.md");
3425
+ const scenariosPath = join20(input.outputDir, "scenarios.md");
3167
3426
  try {
3168
- await readFile13(scenariosPath, "utf-8");
3427
+ await readFile14(scenariosPath, "utf-8");
3169
3428
  return {
3170
3429
  success: true,
3171
3430
  artifacts: ["scenarios.md"],
@@ -3226,7 +3485,7 @@ var init_scenario_recipe = __esm({
3226
3485
 
3227
3486
  // src/agents/04-recipe-builder/entity-relevance.ts
3228
3487
  import { Output, generateText } from "ai";
3229
- import { z as z15 } from "zod";
3488
+ import { z as z17 } from "zod";
3230
3489
  async function callRanker(model, prompt) {
3231
3490
  const result = await generateText({
3232
3491
  model,
@@ -3314,19 +3573,19 @@ var init_entity_relevance = __esm({
3314
3573
  init_esm_shims();
3315
3574
  init_errors();
3316
3575
  init_model();
3317
- rankedSchema = z15.object({
3318
- ranked: z15.array(z15.string()).describe("Every entity name, ordered most-important first.")
3576
+ rankedSchema = z17.object({
3577
+ ranked: z17.array(z17.string()).describe("Every entity name, ordered most-important first.")
3319
3578
  });
3320
3579
  }
3321
3580
  });
3322
3581
 
3323
3582
  // src/core/detect-pkg-manager.ts
3324
3583
  import { existsSync as existsSync2 } from "fs";
3325
- import { join as join20 } from "path";
3584
+ import { join as join21 } from "path";
3326
3585
  function detectPackageManager(projectRoot) {
3327
- if (existsSync2(join20(projectRoot, "bun.lock")) || existsSync2(join20(projectRoot, "bun.lockb"))) return "bun";
3328
- if (existsSync2(join20(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
3329
- if (existsSync2(join20(projectRoot, "yarn.lock"))) return "yarn";
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";
3330
3589
  return "npm";
3331
3590
  }
3332
3591
  function installCommand(pm, ...packages) {
@@ -3597,8 +3856,8 @@ var init_discover_schema = __esm({
3597
3856
  });
3598
3857
 
3599
3858
  // src/agents/04-recipe-builder/recipe.ts
3600
- import { readFile as readFile14, writeFile as writeFile6 } from "fs/promises";
3601
- import { join as join21 } from "path";
3859
+ import { readFile as readFile15, writeFile as writeFile7 } from "fs/promises";
3860
+ import { join as join22 } from "path";
3602
3861
  function collectRefs2(value, out) {
3603
3862
  if (Array.isArray(value)) {
3604
3863
  for (const v of value) collectRefs2(v, out);
@@ -3671,7 +3930,7 @@ function buildSubmittableRecipe(create, description) {
3671
3930
  };
3672
3931
  }
3673
3932
  async function saveRecipe(outputDir, recipe) {
3674
- await writeFile6(join21(outputDir, RECIPE_FILE), JSON.stringify(recipe, null, 2), "utf-8");
3933
+ await writeFile7(join22(outputDir, RECIPE_FILE), JSON.stringify(recipe, null, 2), "utf-8");
3675
3934
  }
3676
3935
  var RECIPE_FILE;
3677
3936
  var init_recipe = __esm({
@@ -3684,8 +3943,8 @@ var init_recipe = __esm({
3684
3943
  });
3685
3944
 
3686
3945
  // src/agents/04-recipe-builder/state.ts
3687
- import { readFile as readFile15, writeFile as writeFile7 } from "fs/promises";
3688
- import { join as join22 } from "path";
3946
+ import { readFile as readFile16, writeFile as writeFile8 } from "fs/promises";
3947
+ import { join as join23 } from "path";
3689
3948
  function adapterKey(a) {
3690
3949
  return `${a.language}:${a.framework}`;
3691
3950
  }
@@ -3707,7 +3966,7 @@ function initialRecipeState() {
3707
3966
  }
3708
3967
  async function loadRecipeState(outputDir) {
3709
3968
  try {
3710
- const raw = await readFile15(join22(outputDir, STATE_FILE2), "utf-8");
3969
+ const raw = await readFile16(join23(outputDir, STATE_FILE2), "utf-8");
3711
3970
  const parsed = JSON.parse(raw);
3712
3971
  return parsed;
3713
3972
  } catch {
@@ -3715,7 +3974,7 @@ async function loadRecipeState(outputDir) {
3715
3974
  }
3716
3975
  }
3717
3976
  async function saveRecipeState(outputDir, state) {
3718
- await writeFile7(join22(outputDir, STATE_FILE2), JSON.stringify(state, null, 2), "utf-8");
3977
+ await writeFile8(join23(outputDir, STATE_FILE2), JSON.stringify(state, null, 2), "utf-8");
3719
3978
  }
3720
3979
  var ALL_ADAPTERS, ADAPTER_HINTS, STATE_FILE2;
3721
3980
  var init_state = __esm({
@@ -3787,7 +4046,7 @@ var init_state = __esm({
3787
4046
 
3788
4047
  // src/agents/04-recipe-builder/phases/failure-classifier.ts
3789
4048
  import { Output as Output2, generateText as generateText2 } from "ai";
3790
- import { z as z16 } from "zod";
4049
+ import { z as z18 } from "zod";
3791
4050
  function buildClassifierPrompt(args) {
3792
4051
  const errorText = typeof args.error === "string" ? args.error : JSON.stringify(args.error, null, 2);
3793
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.`;
@@ -3847,11 +4106,11 @@ var init_failure_classifier = __esm({
3847
4106
  init_esm_shims();
3848
4107
  init_errors();
3849
4108
  init_model();
3850
- classificationSchema = z16.object({
3851
- side: z16.enum(["recipe", "implementation", "unclear"]).describe(
4109
+ classificationSchema = z18.object({
4110
+ side: z18.enum(["recipe", "implementation", "unclear"]).describe(
3852
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."
3853
4112
  ),
3854
- reason: z16.string().describe("One short, plain-language sentence explaining the verdict for the user. No code, no jargon dumps.")
4113
+ reason: z18.string().describe("One short, plain-language sentence explaining the verdict for the user. No code, no jargon dumps.")
3855
4114
  });
3856
4115
  PRIMER = `## Background - what you are looking at
3857
4116
 
@@ -3866,13 +4125,13 @@ So a failure has exactly two possible origins, and your only job is to tell them
3866
4125
  });
3867
4126
 
3868
4127
  // src/agents/04-recipe-builder/phases/entity-loop.ts
3869
- import { writeFile as writeFile8, readFile as readFile16 } from "fs/promises";
4128
+ import { writeFile as writeFile9, readFile as readFile17 } from "fs/promises";
3870
4129
  import { tmpdir } from "os";
3871
- import { join as join23 } from "path";
4130
+ import { join as join24 } from "path";
3872
4131
  import * as p4 from "@clack/prompts";
3873
- import { tool as tool13 } from "ai";
4132
+ import { tool as tool14 } from "ai";
3874
4133
  import spawn2 from "cross-spawn";
3875
- import { z as z17 } from "zod";
4134
+ import { z as z19 } from "zod";
3876
4135
  function summarizeCompletedAliases(completedEntities, excludeName) {
3877
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");
3878
4137
  }
@@ -3887,10 +4146,10 @@ function summarizeEntityAudit(model) {
3887
4146
  async function proposeRecipeData(entityName, entityIndex, totalEntities, model, outputDir, _projectRoot, completedEntities, schemaSpec) {
3888
4147
  let result;
3889
4148
  const { logger, onStepFinish } = buildDefaultStepLogger(`propose:${entityName}`, 20);
3890
- const finishTool = tool13({
4149
+ const finishTool = tool14({
3891
4150
  description: "Submit the proposed recipe data as a JSON array of records.",
3892
- inputSchema: z17.object({
3893
- records: z17.array(z17.record(z17.string(), z17.unknown())).describe("Array of record objects for this entity")
4151
+ inputSchema: z19.object({
4152
+ records: z19.array(z19.record(z19.string(), z19.unknown())).describe("Array of record objects for this entity")
3894
4153
  }),
3895
4154
  execute: async (input) => {
3896
4155
  result = input.records;
@@ -3933,10 +4192,10 @@ Call finish with the JSON array of records.`;
3933
4192
  }
3934
4193
  async function reviseRecipeData(entityName, entityIndex, totalEntities, current, feedback, model, outputDir, completedEntities, schemaSpec) {
3935
4194
  let revised;
3936
- const finishTool = tool13({
4195
+ const finishTool = tool14({
3937
4196
  description: "Submit the fixed recipe data.",
3938
- inputSchema: z17.object({
3939
- records: z17.array(z17.record(z17.string(), z17.unknown()))
4197
+ inputSchema: z19.object({
4198
+ records: z19.array(z19.record(z19.string(), z19.unknown()))
3940
4199
  }),
3941
4200
  execute: async (input) => {
3942
4201
  revised = input.records;
@@ -3994,10 +4253,10 @@ Read scenarios.md and entity-audit.md to understand the correct aliases and sche
3994
4253
  async function generateInstructions(entityName, entityIndex, totalEntities, isFirst, techStack, auditModel, recipeData, model, projectRoot, outputDir) {
3995
4254
  let result;
3996
4255
  const { logger, onStepFinish } = buildDefaultStepLogger(`instructions:${entityName}`, 15);
3997
- const finishTool = tool13({
4256
+ const finishTool = tool14({
3998
4257
  description: "Submit the implementation instructions.",
3999
- inputSchema: z17.object({
4000
- instructions: z17.string().describe("Complete, copy-pasteable implementation instructions")
4258
+ inputSchema: z19.object({
4259
+ instructions: z19.string().describe("Complete, copy-pasteable implementation instructions")
4001
4260
  }),
4002
4261
  execute: async (input) => {
4003
4262
  result = input.instructions;
@@ -4079,23 +4338,23 @@ async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed
4079
4338
  if (p4.isCancel(action)) throw new Error("Recipe review cancelled");
4080
4339
  if (action === "keep") return proposed;
4081
4340
  if (action === "edit") {
4082
- const tmpPath = join23(tmpdir(), `autonoma-recipe-${entityName}.json`);
4083
- await writeFile8(tmpPath, JSON.stringify(proposed, null, 2), "utf-8");
4341
+ const tmpPath = join24(tmpdir(), `autonoma-recipe-${entityName}.json`);
4342
+ await writeFile9(tmpPath, JSON.stringify(proposed, null, 2), "utf-8");
4084
4343
  const env = readEnv();
4085
4344
  const editor = env.EDITOR ?? env.VISUAL ?? "vi";
4086
4345
  p4.log.info(`Opening ${editor}... Save and close when done.`);
4087
- const launched = await new Promise((resolve5) => {
4346
+ const launched = await new Promise((resolve6) => {
4088
4347
  const proc = spawn2(editor, [tmpPath], { stdio: "inherit" });
4089
- proc.on("close", () => resolve5(true));
4348
+ proc.on("close", () => resolve6(true));
4090
4349
  proc.on("error", (err) => {
4091
4350
  p4.log.error(
4092
4351
  `Couldn't open ${editor} (${err.message}). Edit this file manually, then choose "edit" again: ${tmpPath}`
4093
4352
  );
4094
- resolve5(false);
4353
+ resolve6(false);
4095
4354
  });
4096
4355
  });
4097
4356
  if (!launched) continue;
4098
- const edited = await readFile16(tmpPath, "utf-8");
4357
+ const edited = await readFile17(tmpPath, "utf-8");
4099
4358
  try {
4100
4359
  proposed = JSON.parse(edited);
4101
4360
  p4.note(JSON.stringify(proposed, null, 2), `Updated data for ${entityName}`, { format: codeNoteFormat });
@@ -4387,8 +4646,8 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
4387
4646
  const secret = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
4388
4647
  state.sharedSecret = secret;
4389
4648
  await saveRecipeState(outputDir, state);
4390
- await writeFile8(
4391
- join23(outputDir, "autonoma-config.json"),
4649
+ await writeFile9(
4650
+ join24(outputDir, "autonoma-config.json"),
4392
4651
  JSON.stringify({ sharedSecret: secret, endpointUrl: state.sdkEndpointUrl }, null, 2),
4393
4652
  "utf-8"
4394
4653
  );
@@ -4399,7 +4658,7 @@ Add this to your server's .env file and restart it.
4399
4658
  This is a 64-character hex key used for HMAC-SHA256 request signing.
4400
4659
  The same value must be set in both your server and the Autonoma dashboard.
4401
4660
 
4402
- Saved to: ${join23(outputDir, "autonoma-config.json")}`,
4661
+ Saved to: ${join24(outputDir, "autonoma-config.json")}`,
4403
4662
  "Shared secret generated"
4404
4663
  );
4405
4664
  const secretReady = await p4.confirm({
@@ -4420,8 +4679,8 @@ Saved to: ${join23(outputDir, "autonoma-config.json")}`,
4420
4679
  if (p4.isCancel(url)) throw new Error("Entity loop cancelled");
4421
4680
  state.sdkEndpointUrl = url.trim() || "http://localhost:3000/api/autonoma";
4422
4681
  await saveRecipeState(outputDir, state);
4423
- await writeFile8(
4424
- join23(outputDir, "autonoma-config.json"),
4682
+ await writeFile9(
4683
+ join24(outputDir, "autonoma-config.json"),
4425
4684
  JSON.stringify({ sharedSecret: state.sharedSecret, endpointUrl: state.sdkEndpointUrl }, null, 2),
4426
4685
  "utf-8"
4427
4686
  );
@@ -4537,14 +4796,14 @@ When done, call finish with the instructions text.`;
4537
4796
 
4538
4797
  // src/agents/04-recipe-builder/phases/full-validation.ts
4539
4798
  import * as p5 from "@clack/prompts";
4540
- import { tool as tool14 } from "ai";
4541
- import { z as z18 } from "zod";
4799
+ import { tool as tool15 } from "ai";
4800
+ import { z as z20 } from "zod";
4542
4801
  async function reviseFullRecipe(current, feedback, model, outputDir, entityOrder, schemaSpec) {
4543
4802
  let revised;
4544
- const finishTool = tool14({
4803
+ const finishTool = tool15({
4545
4804
  description: "Submit the revised full recipe: an object mapping each entity name to its array of records.",
4546
- inputSchema: z18.object({
4547
- recipe: z18.record(z18.string(), z18.array(z18.record(z18.string(), z18.unknown())))
4805
+ inputSchema: z20.object({
4806
+ recipe: z20.record(z20.string(), z20.array(z20.record(z20.string(), z20.unknown())))
4548
4807
  }),
4549
4808
  execute: async (input) => {
4550
4809
  revised = input.recipe;
@@ -4799,18 +5058,18 @@ var init_submit = __esm({
4799
5058
 
4800
5059
  // src/agents/04-recipe-builder/phases/tech-detect.ts
4801
5060
  import * as p7 from "@clack/prompts";
4802
- import { tool as tool15 } from "ai";
4803
- import { z as z19 } from "zod";
5061
+ import { tool as tool16 } from "ai";
5062
+ import { z as z21 } from "zod";
4804
5063
  async function detectTechStack(projectRoot, modelId, nonInteractive) {
4805
5064
  const model = getModel(modelId);
4806
5065
  const ignorePatterns = await loadGitignorePatterns(projectRoot);
4807
5066
  let detected;
4808
5067
  const { logger, onStepFinish } = buildDefaultStepLogger("tech-detect", 10);
4809
- const finishTool = tool15({
5068
+ const finishTool = tool16({
4810
5069
  description: "Report the detected backend technology stack.",
4811
- inputSchema: z19.object({
4812
- language: z19.string().describe("Programming language: typescript, python, go, ruby, java, php, rust, elixir"),
4813
- framework: z19.string().describe(
5070
+ inputSchema: z21.object({
5071
+ language: z21.string().describe("Programming language: typescript, python, go, ruby, java, php, rust, elixir"),
5072
+ framework: z21.string().describe(
4814
5073
  "Web framework: express, node, hono, web, flask, fastapi, django, gin, rails, rack, spring, laravel, axum, actix, plug"
4815
5074
  )
4816
5075
  }),
@@ -4821,7 +5080,7 @@ async function detectTechStack(projectRoot, modelId, nonInteractive) {
4821
5080
  });
4822
5081
  const agentConfig = {
4823
5082
  id: "tech-detect",
4824
- systemPrompt: SYSTEM_PROMPT4,
5083
+ systemPrompt: SYSTEM_PROMPT5,
4825
5084
  model,
4826
5085
  maxSteps: 10,
4827
5086
  tools: (_heartbeat) => ({
@@ -4877,7 +5136,7 @@ async function detectTechStack(projectRoot, modelId, nonInteractive) {
4877
5136
  p7.log.success(`Using ${adapterLabel(adapter)} - SDK: ${adapter.sdkPackage}, Adapter: ${adapter.adapterPackage}`);
4878
5137
  return adapter;
4879
5138
  }
4880
- var DOCS_BASE, SYSTEM_PROMPT4;
5139
+ var DOCS_BASE, SYSTEM_PROMPT5;
4881
5140
  var init_tech_detect = __esm({
4882
5141
  "src/agents/04-recipe-builder/phases/tech-detect.ts"() {
4883
5142
  "use strict";
@@ -4888,7 +5147,7 @@ var init_tech_detect = __esm({
4888
5147
  init_tools();
4889
5148
  init_state();
4890
5149
  DOCS_BASE = "https://docs.autonoma.app";
4891
- SYSTEM_PROMPT4 = `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.
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.
4892
5151
 
4893
5152
  Explore the project files to detect:
4894
5153
  1. The programming language (check package.json, requirements.txt, go.mod, Gemfile, pom.xml, composer.json, Cargo.toml, mix.exs)
@@ -4903,8 +5162,8 @@ var recipe_builder_exports = {};
4903
5162
  __export(recipe_builder_exports, {
4904
5163
  runRecipeBuilder: () => runRecipeBuilder
4905
5164
  });
4906
- import { readFile as readFile17 } from "fs/promises";
4907
- import { join as join24 } from "path";
5165
+ import { readFile as readFile18 } from "fs/promises";
5166
+ import { join as join25 } from "path";
4908
5167
  import * as p8 from "@clack/prompts";
4909
5168
  async function runRecipeBuilder(input) {
4910
5169
  const model = getModel(input.modelId);
@@ -4918,7 +5177,7 @@ async function runRecipeBuilder(input) {
4918
5177
  state.techStack = await detectTechStack(input.projectRoot, input.modelId, input.nonInteractive);
4919
5178
  let importanceRank;
4920
5179
  try {
4921
- const auditMarkdown = await readFile17(join24(input.outputDir, "entity-audit.md"), "utf-8");
5180
+ const auditMarkdown = await readFile18(join25(input.outputDir, "entity-audit.md"), "utf-8");
4922
5181
  importanceRank = await rankEntitiesByImportance(models, auditMarkdown, model);
4923
5182
  } catch {
4924
5183
  importanceRank = void 0;
@@ -5014,21 +5273,21 @@ var init_recipe_builder = __esm({
5014
5273
  });
5015
5274
 
5016
5275
  // src/agents/05-test-generator/rubrics.ts
5017
- import { z as z20 } from "zod";
5276
+ import { z as z22 } from "zod";
5018
5277
  function reviewResultSchema(shape) {
5019
- return z20.object(shape);
5278
+ return z22.object(shape);
5020
5279
  }
5021
5280
  var dimensionResultSchema, reviewResultRecordSchema, structuralIntentRubric, flowCompletenessRubric, uiTextRubric, dataAccuracyRubric, ALL_RUBRICS;
5022
5281
  var init_rubrics = __esm({
5023
5282
  "src/agents/05-test-generator/rubrics.ts"() {
5024
5283
  "use strict";
5025
5284
  init_esm_shims();
5026
- dimensionResultSchema = z20.object({
5027
- pass: z20.boolean(),
5028
- evidence: z20.string().describe("What you checked and found - cite file paths, line content, or specific strings"),
5029
- suggestion: z20.string().optional().describe("What the planner agent should fix, if failing")
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")
5030
5289
  });
5031
- reviewResultRecordSchema = z20.record(z20.string(), dimensionResultSchema);
5290
+ reviewResultRecordSchema = z22.record(z22.string(), dimensionResultSchema);
5032
5291
  structuralIntentRubric = {
5033
5292
  name: "structural-intent",
5034
5293
  maxSteps: 8,
@@ -5202,12 +5461,12 @@ When done reviewing, call finish with your structured evaluation.`
5202
5461
  // src/agents/05-test-generator/review-pass.ts
5203
5462
  import { basename as basename2 } from "path";
5204
5463
  import "ai";
5205
- import { tool as tool16 } from "ai";
5464
+ import { tool as tool17 } from "ai";
5206
5465
  async function runReviewPass(testContent, testPath, rubric, projectRoot, model, scenarioData) {
5207
5466
  let result;
5208
5467
  const agentLabel = `review:${rubric.name}:${basename2(testPath)}`;
5209
5468
  const { onStepFinish } = buildDefaultStepLogger(agentLabel, rubric.maxSteps);
5210
- const finishTool = tool16({
5469
+ const finishTool = tool17({
5211
5470
  description: "Submit your structured review. Every dimension must have evidence from your investigation.",
5212
5471
  inputSchema: rubric.resultSchema,
5213
5472
  execute: async (input) => {
@@ -5265,8 +5524,8 @@ var init_review_pass = __esm({
5265
5524
  });
5266
5525
 
5267
5526
  // src/agents/05-test-generator/review.ts
5268
- import { readFile as readFile18 } from "fs/promises";
5269
- import { join as join25, relative as relative6, basename as basename3 } from "path";
5527
+ import { readFile as readFile19 } from "fs/promises";
5528
+ import { join as join26, relative as relative6, basename as basename3 } from "path";
5270
5529
  import "ai";
5271
5530
  import { glob as glob5 } from "glob";
5272
5531
  async function reviewSingleTest(testContent, testPath, projectRoot, model, scenarioData) {
@@ -5295,19 +5554,19 @@ async function reviewSingleTest(testContent, testPath, projectRoot, model, scena
5295
5554
  return merged;
5296
5555
  }
5297
5556
  async function runConsolidatedReview(outputDir, projectRoot, model) {
5298
- const testsDir = join25(outputDir, "qa-tests");
5557
+ const testsDir = join26(outputDir, "qa-tests");
5299
5558
  const logger = createStepLogger("review", 5);
5300
5559
  let scenarioData;
5301
5560
  try {
5302
- scenarioData = await readFile18(join25(outputDir, "scenarios.md"), "utf-8");
5561
+ scenarioData = await readFile19(join26(outputDir, "scenarios.md"), "utf-8");
5303
5562
  } catch {
5304
5563
  }
5305
- const testFiles = await glob5(join25(testsDir, "**/*.md"));
5564
+ const testFiles = await glob5(join26(testsDir, "**/*.md"));
5306
5565
  const tests = [];
5307
5566
  for (const testPath of testFiles) {
5308
5567
  if (basename3(testPath) === "INDEX.md") continue;
5309
5568
  if (testPath.includes("/_invalid/")) continue;
5310
- const content = await readFile18(testPath, "utf-8");
5569
+ const content = await readFile19(testPath, "utf-8");
5311
5570
  const flowMatch = content.match(/^---\n[\s\S]*?flow:\s*["']?([^"'\n]+)["']?\s*\n[\s\S]*?---/m);
5312
5571
  tests.push({
5313
5572
  path: testPath,
@@ -5390,17 +5649,17 @@ var init_review2 = __esm({
5390
5649
  });
5391
5650
 
5392
5651
  // src/agents/00b-feature-discovery/index.ts
5393
- import { readFile as readFile19, writeFile as writeFile9 } from "fs/promises";
5394
- import { join as join26 } from "path";
5395
- import { tool as tool17 } from "ai";
5396
- import { z as z21 } from "zod";
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";
5397
5656
  async function saveFeatures(outputDir, features) {
5398
5657
  const obj = Object.fromEntries(features);
5399
- await writeFile9(join26(outputDir, FEATURES_FILE), JSON.stringify(obj, null, 2), "utf-8");
5658
+ await writeFile10(join27(outputDir, FEATURES_FILE), JSON.stringify(obj, null, 2), "utf-8");
5400
5659
  }
5401
5660
  async function loadFeatures(outputDir) {
5402
5661
  try {
5403
- const raw = await readFile19(join26(outputDir, FEATURES_FILE), "utf-8");
5662
+ const raw = await readFile20(join27(outputDir, FEATURES_FILE), "utf-8");
5404
5663
  const obj = JSON.parse(raw);
5405
5664
  return new Map(Object.entries(obj));
5406
5665
  } catch {
@@ -5424,17 +5683,17 @@ ${pagesDescription}
5424
5683
  Process every page. Call add_feature for each sub-feature you discover. When done, call finish.`;
5425
5684
  const agentConfig = {
5426
5685
  id: "feature-discovery",
5427
- systemPrompt: SYSTEM_PROMPT5,
5686
+ systemPrompt: SYSTEM_PROMPT6,
5428
5687
  model,
5429
5688
  maxSteps: 300,
5430
5689
  tools: async (heartbeat) => {
5431
5690
  const tools = await buildCodebaseTools(model, input.projectRoot, input.outputDir, heartbeat);
5432
5691
  return {
5433
5692
  ...tools,
5434
- add_feature: tool17({
5693
+ add_feature: tool18({
5435
5694
  description: "Add a discovered sub-feature",
5436
5695
  inputSchema: Feature.extend({
5437
- id: z21.string().min(1).describe("Unique kebab-case ID (e.g. 'settings-notifications-tab')")
5696
+ id: z23.string().min(1).describe("Unique kebab-case ID (e.g. 'settings-notifications-tab')")
5438
5697
  }),
5439
5698
  execute: (featureInput) => {
5440
5699
  const { id, ...rest } = featureInput;
@@ -5446,19 +5705,19 @@ Process every page. Call add_feature for each sub-feature you discover. When don
5446
5705
  return `Feature "${id}" added (${collector.features.size} total)`;
5447
5706
  }
5448
5707
  }),
5449
- view_features: tool17({
5708
+ view_features: tool18({
5450
5709
  description: "View all discovered features so far",
5451
- inputSchema: z21.object({}),
5710
+ inputSchema: z23.object({}),
5452
5711
  execute: () => collector.viewFeatures()
5453
5712
  }),
5454
- view_pages: tool17({
5713
+ view_pages: tool18({
5455
5714
  description: "View the pages list to know what to analyze",
5456
- inputSchema: z21.object({}),
5715
+ inputSchema: z23.object({}),
5457
5716
  execute: () => pagesDescription
5458
5717
  }),
5459
- finish: tool17({
5718
+ finish: tool18({
5460
5719
  description: "Signal that feature discovery is complete",
5461
- inputSchema: z21.object({ summary: z21.string() }),
5720
+ inputSchema: z23.object({ summary: z23.string() }),
5462
5721
  execute: async (finishInput) => {
5463
5722
  result = {
5464
5723
  success: true,
@@ -5480,7 +5739,7 @@ Process every page. Call add_feature for each sub-feature you discover. When don
5480
5739
  }
5481
5740
  return collector.features;
5482
5741
  }
5483
- var FEATURES_FILE, Feature, FeatureCollector, SYSTEM_PROMPT5;
5742
+ var FEATURES_FILE, Feature, FeatureCollector, SYSTEM_PROMPT6;
5484
5743
  var init_b_feature_discovery = __esm({
5485
5744
  "src/agents/00b-feature-discovery/index.ts"() {
5486
5745
  "use strict";
@@ -5489,13 +5748,13 @@ var init_b_feature_discovery = __esm({
5489
5748
  init_model();
5490
5749
  init_tools();
5491
5750
  FEATURES_FILE = "features.json";
5492
- Feature = z21.object({
5493
- name: z21.string().min(1).describe("Human-readable name (e.g. 'Settings > Notifications Tab', 'Create Project Modal')"),
5494
- type: z21.enum(["tab", "modal", "form", "table", "wizard", "nested-route", "complex-component"]),
5495
- parentPagePath: z21.string().min(1).describe("The page path this feature belongs to (from the pages list)"),
5496
- sourceFiles: z21.array(z21.string()).min(1).describe("Relative paths to the source files for this sub-feature"),
5497
- interactiveElements: z21.number().int().min(0).describe("Count of interactive elements found (buttons, inputs, toggles, etc.)"),
5498
- description: z21.string().min(10).describe("What this sub-feature does")
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")
5499
5758
  });
5500
5759
  FeatureCollector = class {
5501
5760
  features = /* @__PURE__ */ new Map();
@@ -5526,7 +5785,7 @@ ${page}:`);
5526
5785
  return lines.join("\n");
5527
5786
  }
5528
5787
  };
5529
- SYSTEM_PROMPT5 = `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.
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.
5530
5789
 
5531
5790
  You will be given a list of pages. For each page, you must:
5532
5791
  1. Read the page's source file
@@ -5578,16 +5837,16 @@ Use kebab-case IDs that indicate the parent page and feature type:
5578
5837
  });
5579
5838
 
5580
5839
  // src/agents/05-test-generator/graph.ts
5581
- import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
5582
- import { join as join27 } from "path";
5840
+ import { readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
5841
+ import { join as join28 } from "path";
5583
5842
  async function saveBfsState(outputDir, state) {
5584
- const path3 = join27(outputDir, STATE_FILE3);
5585
- await writeFile10(path3, JSON.stringify(state.serialize(), null, 2), "utf-8");
5843
+ const path3 = join28(outputDir, STATE_FILE3);
5844
+ await writeFile11(path3, JSON.stringify(state.serialize(), null, 2), "utf-8");
5586
5845
  }
5587
5846
  async function loadBfsState(outputDir) {
5588
- const path3 = join27(outputDir, STATE_FILE3);
5847
+ const path3 = join28(outputDir, STATE_FILE3);
5589
5848
  try {
5590
- const raw = await readFile20(path3, "utf-8");
5849
+ const raw = await readFile21(path3, "utf-8");
5591
5850
  return CoverageState.deserialize(JSON.parse(raw));
5592
5851
  } catch {
5593
5852
  return void 0;
@@ -5678,12 +5937,12 @@ var init_graph = __esm({
5678
5937
  });
5679
5938
 
5680
5939
  // src/agents/05-test-generator/prompt.ts
5681
- var SYSTEM_PROMPT6;
5940
+ var SYSTEM_PROMPT7;
5682
5941
  var init_prompt4 = __esm({
5683
5942
  "src/agents/05-test-generator/prompt.ts"() {
5684
5943
  "use strict";
5685
5944
  init_esm_shims();
5686
- SYSTEM_PROMPT6 = `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.
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.
5687
5946
 
5688
5947
  ## Your process
5689
5948
 
@@ -6114,11 +6373,11 @@ var init_validation = __esm({
6114
6373
  });
6115
6374
 
6116
6375
  // src/agents/05-test-generator/tools.ts
6117
- import { mkdir as mkdir3, writeFile as writeFile11 } from "fs/promises";
6118
- import { dirname as dirname3, join as join28 } from "path";
6119
- import { hasToolCall as hasToolCall3, stepCountIs as stepCountIs3, tool as tool18, ToolLoopAgent as ToolLoopAgent3 } from "ai";
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";
6120
6379
  import matter5 from "gray-matter";
6121
- import { z as z22 } from "zod";
6380
+ import { z as z24 } from "zod";
6122
6381
  function findForbiddenPlaceholder(stepsSection) {
6123
6382
  const placeholderPatterns = [
6124
6383
  { pattern: /Dynamic:\s/gi, name: '"Dynamic:" placeholder' },
@@ -6136,13 +6395,13 @@ function findForbiddenPlaceholder(stepsSection) {
6136
6395
  return void 0;
6137
6396
  }
6138
6397
  function buildWriteTestTool(state, outputDir) {
6139
- return tool18({
6398
+ return tool19({
6140
6399
  description: "Write a test file to qa-tests/{folder}/{filename}.md. Validates frontmatter before writing. Returns error if frontmatter is invalid.",
6141
- inputSchema: z22.object({
6142
- folder: z22.string().describe("Subfolder name under qa-tests/"),
6143
- filename: z22.string().describe("File name (e.g. login-valid-credentials.md)"),
6144
- content: z22.string().describe("Full file content including YAML frontmatter"),
6145
- nodeId: z22.string().describe("The FeatureNode ID this test belongs to")
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")
6146
6405
  }),
6147
6406
  execute: async (input) => {
6148
6407
  const frontmatter = extractFrontmatter(input.content);
@@ -6185,11 +6444,11 @@ function buildWriteTestTool(state, outputDir) {
6185
6444
  error: `Test steps contain ${placeholder.name}: "${placeholder.match}". Use EXACT values from scenarios.md - not placeholders or examples.`
6186
6445
  };
6187
6446
  }
6188
- const relPath = join28("qa-tests", input.folder, input.filename);
6189
- const absPath = join28(outputDir, relPath);
6447
+ const relPath = join29("qa-tests", input.folder, input.filename);
6448
+ const absPath = join29(outputDir, relPath);
6190
6449
  try {
6191
6450
  await mkdir3(dirname3(absPath), { recursive: true });
6192
- await writeFile11(absPath, input.content, "utf-8");
6451
+ await writeFile12(absPath, input.content, "utf-8");
6193
6452
  state.markTested(input.nodeId, [relPath]);
6194
6453
  await saveBfsState(outputDir, state);
6195
6454
  return { path: relPath, title: parsed.data.title };
@@ -6201,16 +6460,16 @@ function buildWriteTestTool(state, outputDir) {
6201
6460
  });
6202
6461
  }
6203
6462
  function buildCreateFolderTool(outputDir) {
6204
- return tool18({
6463
+ return tool19({
6205
6464
  description: "Create a folder under qa-tests/ for organizing tests.",
6206
- inputSchema: z22.object({
6207
- folder: z22.string().describe("Folder name (kebab-case)")
6465
+ inputSchema: z24.object({
6466
+ folder: z24.string().describe("Folder name (kebab-case)")
6208
6467
  }),
6209
6468
  execute: async (input) => {
6210
- const absPath = join28(outputDir, "qa-tests", input.folder);
6469
+ const absPath = join29(outputDir, "qa-tests", input.folder);
6211
6470
  try {
6212
6471
  await mkdir3(absPath, { recursive: true });
6213
- return { path: join28("qa-tests", input.folder) };
6472
+ return { path: join29("qa-tests", input.folder) };
6214
6473
  } catch (err) {
6215
6474
  const message = err instanceof Error ? err.message : String(err);
6216
6475
  return { error: `Failed to create folder: ${message}` };
@@ -6219,9 +6478,9 @@ function buildCreateFolderTool(outputDir) {
6219
6478
  });
6220
6479
  }
6221
6480
  function buildNextNodeTool(state, outputDir) {
6222
- return tool18({
6481
+ return tool19({
6223
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.",
6224
- inputSchema: z22.object({}),
6483
+ inputSchema: z24.object({}),
6225
6484
  execute: async () => {
6226
6485
  const next = state.nextNode();
6227
6486
  await saveBfsState(outputDir, state);
@@ -6248,9 +6507,9 @@ function buildNextNodeTool(state, outputDir) {
6248
6507
  });
6249
6508
  }
6250
6509
  function buildGetProgressTool(state) {
6251
- return tool18({
6510
+ return tool19({
6252
6511
  description: "Check how many nodes have been tested vs how many remain.",
6253
- inputSchema: z22.object({}),
6512
+ inputSchema: z24.object({}),
6254
6513
  execute: async () => {
6255
6514
  const stats = state.summary();
6256
6515
  const nodes = [...state.nodes.values()].map((n) => ({
@@ -6264,14 +6523,14 @@ function buildGetProgressTool(state) {
6264
6523
  });
6265
6524
  }
6266
6525
  function buildSpawnResearcherTool(model, workingDirectory, onHeartbeat) {
6267
- return tool18({
6526
+ return tool19({
6268
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.",
6269
- inputSchema: z22.object({
6270
- instruction: z22.string().describe("What to research - be specific about files and what to look for")
6528
+ inputSchema: z24.object({
6529
+ instruction: z24.string().describe("What to research - be specific about files and what to look for")
6271
6530
  }),
6272
6531
  execute: async (input) => {
6273
- const resultSchema2 = z22.object({
6274
- findings: z22.string().describe("Summary of what was found")
6532
+ const resultSchema2 = z24.object({
6533
+ findings: z24.string().describe("Summary of what was found")
6275
6534
  });
6276
6535
  let result;
6277
6536
  const subagent = new ToolLoopAgent3({
@@ -6283,7 +6542,7 @@ function buildSpawnResearcherTool(model, workingDirectory, onHeartbeat) {
6283
6542
  glob: buildGlobTool(workingDirectory),
6284
6543
  grep: buildGrepTool(workingDirectory),
6285
6544
  read_file: buildReadFileTool(workingDirectory),
6286
- finish: tool18({
6545
+ finish: tool19({
6287
6546
  description: "Report your findings.",
6288
6547
  inputSchema: resultSchema2,
6289
6548
  execute: async (output) => {
@@ -6325,14 +6584,14 @@ var init_tools2 = __esm({
6325
6584
  init_tools();
6326
6585
  init_graph();
6327
6586
  init_validation();
6328
- testFrontmatterSchema = z22.object({
6329
- title: z22.string().min(1),
6330
- description: z22.string().min(1),
6331
- intent: z22.string().min(30, "Intent must be at least 30 characters - describe the BEHAVIOR being tested, not the steps"),
6332
- criticality: z22.enum(["critical", "high", "mid", "low"]),
6333
- scenario: z22.string().min(1),
6334
- flow: z22.string().min(1),
6335
- verification: z22.string().min(
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(
6336
6595
  20,
6337
6596
  "Verification must describe WHERE to navigate and WHAT to assert at the source of truth - not UI acknowledgments like toasts"
6338
6597
  )
@@ -6345,10 +6604,10 @@ var test_generator_exports = {};
6345
6604
  __export(test_generator_exports, {
6346
6605
  runTestGenerator: () => runTestGenerator
6347
6606
  });
6348
- import { mkdir as mkdir4, readFile as readFile21, rmdir, unlink, writeFile as writeFile12 } from "fs/promises";
6349
- import { basename as basename4, join as join29 } from "path";
6350
- import { tool as tool19 } from "ai";
6351
- import { z as z23 } from "zod";
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";
6352
6611
  import { glob as glob6 } from "glob";
6353
6612
  async function preseedQueue(state, projectRoot, pages, features) {
6354
6613
  let seeded = 0;
@@ -6396,10 +6655,10 @@ async function runTestGenerator(input) {
6396
6655
  const existingState = await loadBfsState(input.outputDir);
6397
6656
  const state = existingState ?? new CoverageState();
6398
6657
  let result;
6399
- const finishTool = tool19({
6658
+ const finishTool = tool20({
6400
6659
  description: "Call when the BFS queue is empty and all routes have been explored.",
6401
- inputSchema: z23.object({
6402
- summary: z23.string().describe("Coverage summary")
6660
+ inputSchema: z25.object({
6661
+ summary: z25.string().describe("Coverage summary")
6403
6662
  }),
6404
6663
  execute: async (finishInput) => {
6405
6664
  const stats = state.summary();
@@ -6428,7 +6687,7 @@ async function runTestGenerator(input) {
6428
6687
  });
6429
6688
  let kbContext = "";
6430
6689
  try {
6431
- const autonomaMd = await readFile21(join29(input.outputDir, "AUTONOMA.md"), "utf-8");
6690
+ const autonomaMd = await readFile22(join30(input.outputDir, "AUTONOMA.md"), "utf-8");
6432
6691
  kbContext += `
6433
6692
  ## Knowledge Base (AUTONOMA.md)
6434
6693
 
@@ -6437,7 +6696,7 @@ ${autonomaMd}
6437
6696
  } catch {
6438
6697
  }
6439
6698
  try {
6440
- const scenariosMd = await readFile21(join29(input.outputDir, "scenarios.md"), "utf-8");
6699
+ const scenariosMd = await readFile22(join30(input.outputDir, "scenarios.md"), "utf-8");
6441
6700
  kbContext += `
6442
6701
  ## Scenarios
6443
6702
 
@@ -6491,7 +6750,7 @@ Do NOT try to finish early. Process EVERY node via next_node until it returns do
6491
6750
  const listDirectoryFn = await buildListDirectoryTool(input.projectRoot);
6492
6751
  const agentConfig = {
6493
6752
  id: "test-generator",
6494
- systemPrompt: SYSTEM_PROMPT6,
6753
+ systemPrompt: SYSTEM_PROMPT7,
6495
6754
  model,
6496
6755
  maxSteps: CHUNK_STEPS,
6497
6756
  temperature: 0.3,
@@ -6607,20 +6866,20 @@ IMPORTANT: Do NOT try to finish early. Process every node via next_node until it
6607
6866
  }
6608
6867
  console.log(` Fix pass complete`);
6609
6868
  }
6610
- const allTestFiles = await glob6(join29(input.outputDir, "qa-tests", "**/*.md"));
6869
+ const allTestFiles = await glob6(join30(input.outputDir, "qa-tests", "**/*.md"));
6611
6870
  let markedInvalid = 0;
6612
6871
  for (const testPath of allTestFiles) {
6613
6872
  if (basename4(testPath) === "INDEX.md") continue;
6614
6873
  if (testPath.includes("/_invalid/")) continue;
6615
- const content = await readFile21(testPath, "utf-8");
6874
+ const content = await readFile22(testPath, "utf-8");
6616
6875
  const validation = validateTestContent(content);
6617
6876
  if (!validation.valid) {
6618
- const invalidDir = join29(input.outputDir, "qa-tests", "_invalid");
6877
+ const invalidDir = join30(input.outputDir, "qa-tests", "_invalid");
6619
6878
  await mkdir4(invalidDir, { recursive: true });
6620
- const dest = join29(invalidDir, basename4(testPath));
6879
+ const dest = join30(invalidDir, basename4(testPath));
6621
6880
  const annotated = `<!-- VALIDATION ERRORS: ${validation.errors.join("; ")} -->
6622
6881
  ${content}`;
6623
- await writeFile12(dest, annotated, "utf-8");
6882
+ await writeFile13(dest, annotated, "utf-8");
6624
6883
  await unlink(testPath);
6625
6884
  markedInvalid++;
6626
6885
  }
@@ -6628,7 +6887,7 @@ ${content}`;
6628
6887
  if (markedInvalid > 0) {
6629
6888
  console.log(` ${markedInvalid} tests still invalid after review cycles - moved to _invalid/`);
6630
6889
  }
6631
- const dirs = await glob6(join29(input.outputDir, "qa-tests", "**/"), {
6890
+ const dirs = await glob6(join30(input.outputDir, "qa-tests", "**/"), {
6632
6891
  dot: false
6633
6892
  });
6634
6893
  for (const dir of dirs.sort((a, b) => b.length - a.length)) {
@@ -6720,7 +6979,7 @@ async function generateIndex(outputDir, state) {
6720
6979
  for (const paths of state.testsWritten.values()) {
6721
6980
  for (const p10 of paths) {
6722
6981
  try {
6723
- const content2 = await readFile21(join29(outputDir, p10), "utf-8");
6982
+ const content2 = await readFile22(join30(outputDir, p10), "utf-8");
6724
6983
  const critMatch = content2.match(/criticality:\s*(\w+)/);
6725
6984
  const critVal = critMatch?.[1] ?? "";
6726
6985
  if (critCounts.has(critVal)) critCounts.set(critVal, (critCounts.get(critVal) ?? 0) + 1);
@@ -6765,28 +7024,28 @@ ${folders.map((f) => `| ${f.name} | ${f.test_count} |`).join("\n")}
6765
7024
 
6766
7025
  ${[...testsByFolder.entries()].flatMap(([_folder, tests]) => tests.map((t) => `- \`${t}\``)).join("\n")}
6767
7026
  `;
6768
- await writeFile12(join29(outputDir, "qa-tests", "INDEX.md"), content, "utf-8");
7027
+ await writeFile13(join30(outputDir, "qa-tests", "INDEX.md"), content, "utf-8");
6769
7028
  }
6770
7029
  async function generateJourneyTests(outputDir, model, projectRoot) {
6771
7030
  const logger = createStepLogger("journeys", 50);
6772
7031
  let autonomaMd = "";
6773
7032
  let scenariosMd = "";
6774
7033
  try {
6775
- autonomaMd = await readFile21(join29(outputDir, "AUTONOMA.md"), "utf-8");
7034
+ autonomaMd = await readFile22(join30(outputDir, "AUTONOMA.md"), "utf-8");
6776
7035
  } catch (err) {
6777
7036
  debugLog("AUTONOMA.md not present for journey generation", { err });
6778
7037
  }
6779
7038
  try {
6780
- scenariosMd = await readFile21(join29(outputDir, "scenarios.md"), "utf-8");
7039
+ scenariosMd = await readFile22(join30(outputDir, "scenarios.md"), "utf-8");
6781
7040
  } catch (err) {
6782
7041
  debugLog("scenarios.md not present for journey generation", { err });
6783
7042
  }
6784
7043
  if (!autonomaMd) return 0;
6785
- const existingTests = await glob6(join29(outputDir, "qa-tests", "**/*.md"));
7044
+ const existingTests = await glob6(join30(outputDir, "qa-tests", "**/*.md"));
6786
7045
  const existingTitles = [];
6787
7046
  for (const t of existingTests) {
6788
7047
  if (basename4(t) === "INDEX.md") continue;
6789
- const content = await readFile21(t, "utf-8");
7048
+ const content = await readFile22(t, "utf-8");
6790
7049
  const titleMatch = content.match(/title:\s*"([^"]+)"/);
6791
7050
  if (titleMatch) existingTitles.push(titleMatch[1]);
6792
7051
  }
@@ -6829,9 +7088,9 @@ Write 5-8 journey tests using the write_test tool with folder "journeys". Then c
6829
7088
  status: "queued"
6830
7089
  });
6831
7090
  let journeyResult;
6832
- const journeyFinish = tool19({
7091
+ const journeyFinish = tool20({
6833
7092
  description: "Signal journey generation is complete.",
6834
- inputSchema: z23.object({ summary: z23.string() }),
7093
+ inputSchema: z25.object({ summary: z25.string() }),
6835
7094
  execute: async (finishInput) => {
6836
7095
  journeyResult = {
6837
7096
  success: true,
@@ -6843,7 +7102,7 @@ Write 5-8 journey tests using the write_test tool with folder "journeys". Then c
6843
7102
  });
6844
7103
  const config = {
6845
7104
  id: "journey-gen",
6846
- systemPrompt: SYSTEM_PROMPT6,
7105
+ systemPrompt: SYSTEM_PROMPT7,
6847
7106
  model,
6848
7107
  maxSteps: 50,
6849
7108
  temperature: 0.3,
@@ -6920,8 +7179,8 @@ function ensureSupportedNode() {
6920
7179
  ensureSupportedNode();
6921
7180
 
6922
7181
  // src/index.ts
6923
- import { readFile as readFile22, writeFile as writeFile13 } from "fs/promises";
6924
- import { join as join30 } from "path";
7182
+ import { readFile as readFile23, writeFile as writeFile14 } from "fs/promises";
7183
+ import { join as join31 } from "path";
6925
7184
  import * as p9 from "@clack/prompts";
6926
7185
 
6927
7186
  // src/config.ts
@@ -7001,6 +7260,8 @@ function loadConfig(args) {
7001
7260
  projectRoot,
7002
7261
  projectSlug,
7003
7262
  modelId: args.model ?? env.OPENROUTER_MODEL,
7263
+ frontend: args.frontend,
7264
+ backends: args.backends,
7004
7265
  databaseUrl: env.DATABASE_URL,
7005
7266
  sdkEndpointUrl: env.SDK_ENDPOINT_URL,
7006
7267
  sharedSecret: env.AUTONOMA_SHARED_SECRET,
@@ -7015,6 +7276,7 @@ function loadConfig(args) {
7015
7276
 
7016
7277
  // src/index.ts
7017
7278
  init_analytics();
7279
+ init_colors();
7018
7280
  init_context();
7019
7281
  init_errors();
7020
7282
 
@@ -7023,10 +7285,10 @@ init_esm_shims();
7023
7285
  init_debug();
7024
7286
  import readline from "readline";
7025
7287
  import { settings } from "@clack/core";
7026
- var DIM = "\x1B[2m";
7027
- var RESET = "\x1B[0m";
7288
+ var DIM2 = "\x1B[2m";
7289
+ var RESET2 = "\x1B[0m";
7028
7290
  var SHOW_CURSOR = "\x1B[?25h";
7029
- var EXIT_HINT = `${DIM}(press Ctrl+C again to exit)${RESET}`;
7291
+ var EXIT_HINT = `${DIM2}(press Ctrl+C again to exit)${RESET2}`;
7030
7292
  var ARM_WINDOW_MS = 3e3;
7031
7293
  var FORCE_EXIT_MS = 2500;
7032
7294
  var installed = false;
@@ -7074,6 +7336,50 @@ function installInterruptHandler(opts) {
7074
7336
  return iface;
7075
7337
  });
7076
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
+ }
7077
7383
  function restoreTerminal() {
7078
7384
  try {
7079
7385
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
@@ -7151,15 +7457,31 @@ async function loadGitInfo(outputDir) {
7151
7457
 
7152
7458
  // src/index.ts
7153
7459
  init_notify();
7460
+ init_project_map();
7154
7461
 
7155
7462
  // src/core/state.ts
7156
7463
  init_esm_shims();
7157
- import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
7158
- import { join as join8 } from "path";
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
+ });
7159
7480
  var STATE_FILE = ".pipeline-state.json";
7160
7481
  function initialState() {
7161
7482
  return {
7162
7483
  steps: {
7484
+ projectMapper: "pending",
7163
7485
  pagesFinder: "pending",
7164
7486
  kb: "pending",
7165
7487
  entityAudit: "pending",
@@ -7170,18 +7492,19 @@ function initialState() {
7170
7492
  };
7171
7493
  }
7172
7494
  async function loadState(outputDir) {
7173
- const path3 = join8(outputDir, STATE_FILE);
7495
+ const path3 = join9(outputDir, STATE_FILE);
7174
7496
  try {
7175
- const raw = await readFile3(path3, "utf-8");
7176
- const parsed = JSON.parse(raw);
7177
- return parsed;
7178
- } catch {
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 });
7179
7502
  return initialState();
7180
7503
  }
7181
7504
  }
7182
7505
  async function saveState(outputDir, state) {
7183
- const path3 = join8(outputDir, STATE_FILE);
7184
- await writeFile3(path3, JSON.stringify(state, null, 2), "utf-8");
7506
+ const path3 = join9(outputDir, STATE_FILE);
7507
+ await writeFile4(path3, JSON.stringify(state, null, 2), "utf-8");
7185
7508
  }
7186
7509
  async function markStep(outputDir, state, step, status) {
7187
7510
  const updated = {
@@ -7192,15 +7515,23 @@ async function markStep(outputDir, state, step, status) {
7192
7515
  return updated;
7193
7516
  }
7194
7517
  function nextPendingStep(state) {
7195
- const order = ["pagesFinder", "kb", "entityAudit", "scenarioRecipe", "recipeBuilder", "testGenerator"];
7518
+ const order = [
7519
+ "projectMapper",
7520
+ "pagesFinder",
7521
+ "kb",
7522
+ "entityAudit",
7523
+ "scenarioRecipe",
7524
+ "recipeBuilder",
7525
+ "testGenerator"
7526
+ ];
7196
7527
  return order.find((s) => state.steps[s] !== "done") ?? void 0;
7197
7528
  }
7198
7529
 
7199
7530
  // src/core/upload.ts
7200
7531
  init_esm_shims();
7201
7532
  init_debug();
7202
- import { readFile as readFile4 } from "fs/promises";
7203
- import { basename, join as join9, relative } from "path";
7533
+ import { readFile as readFile5 } from "fs/promises";
7534
+ import { basename, join as join10, relative } from "path";
7204
7535
  import * as p from "@clack/prompts";
7205
7536
  import { glob } from "glob";
7206
7537
  var ARTIFACT_FILES = ["AUTONOMA.md", "scenarios.md", "entity-audit.md"];
@@ -7208,7 +7539,7 @@ async function readArtifacts(outputDir) {
7208
7539
  const files = [];
7209
7540
  for (const name of ARTIFACT_FILES) {
7210
7541
  try {
7211
- const content = await readFile4(join9(outputDir, name), "utf-8");
7542
+ const content = await readFile5(join10(outputDir, name), "utf-8");
7212
7543
  files.push({ name, content });
7213
7544
  } catch (err) {
7214
7545
  debugLog(`Artifact ${name} not on disk; skipping upload`, { err });
@@ -7217,13 +7548,13 @@ async function readArtifacts(outputDir) {
7217
7548
  return files;
7218
7549
  }
7219
7550
  async function readTestCases(outputDir) {
7220
- const testsDir = join9(outputDir, "qa-tests");
7551
+ const testsDir = join10(outputDir, "qa-tests");
7221
7552
  const matches = await glob("**/*.md", { cwd: testsDir, nodir: true });
7222
7553
  const files = [];
7223
7554
  for (const match of matches) {
7224
7555
  const name = basename(match);
7225
7556
  if (name === "INDEX.md") continue;
7226
- const content = await readFile4(join9(testsDir, match), "utf-8");
7557
+ const content = await readFile5(join10(testsDir, match), "utf-8");
7227
7558
  const folderPath = relative(".", match).split("/").slice(0, -1).join("/");
7228
7559
  files.push({ name, content, folder: folderPath.length > 0 ? folderPath : void 0 });
7229
7560
  }
@@ -7285,11 +7616,11 @@ process.setSourceMapsEnabled(true);
7285
7616
  var PAGES_FILE = "pages.json";
7286
7617
  async function savePages(outputDir, pages) {
7287
7618
  const obj = Object.fromEntries(pages);
7288
- await writeFile13(join30(outputDir, PAGES_FILE), JSON.stringify(obj, null, 2), "utf-8");
7619
+ await writeFile14(join31(outputDir, PAGES_FILE), JSON.stringify(obj, null, 2), "utf-8");
7289
7620
  }
7290
7621
  async function loadPages(outputDir) {
7291
7622
  try {
7292
- const raw = await readFile22(join30(outputDir, PAGES_FILE), "utf-8");
7623
+ const raw = await readFile23(join31(outputDir, PAGES_FILE), "utf-8");
7293
7624
  const obj = JSON.parse(raw);
7294
7625
  return new Map(Object.entries(obj));
7295
7626
  } catch {
@@ -7318,6 +7649,7 @@ function strArg(args, key) {
7318
7649
  return typeof value === "string" ? value : void 0;
7319
7650
  }
7320
7651
  var STEP_LABELS = {
7652
+ projectMapper: "Map your project structure",
7321
7653
  pagesFinder: "Find your pages",
7322
7654
  kb: "Build a knowledge base",
7323
7655
  entityAudit: "Map your data models",
@@ -7329,6 +7661,7 @@ function isStepName(value) {
7329
7661
  return value in STEP_LABELS;
7330
7662
  }
7331
7663
  var STEP_SUMMARIES = {
7664
+ projectMapper: "Identify your frontend(s), backend(s), and which folders to ignore.",
7332
7665
  pagesFinder: "Map every page and route in your app.",
7333
7666
  kb: "Learn your app's features, flows, and UI patterns.",
7334
7667
  entityAudit: "Find what your app stores (users, orgs, ...) and how each one is created.",
@@ -7337,6 +7670,7 @@ var STEP_SUMMARIES = {
7337
7670
  testGenerator: "Write the end-to-end tests, covering every page and feature."
7338
7671
  };
7339
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.",
7340
7674
  pagesFinder: "Scanning your codebase to find every page and route, so we know the full surface area that needs test coverage.",
7341
7675
  kb: "Reading those pages to learn your app's features, flows, and UI patterns - the context everything after this builds on.",
7342
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.",
@@ -7344,6 +7678,32 @@ var STEP_INTROS = {
7344
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.",
7345
7679
  testGenerator: "Writing the actual end-to-end tests, covering every page and feature with depth proportional to its complexity."
7346
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
+ }
7347
7707
  async function runStep(step, outputDir, state, config, projectContext, nonInteractive, retryGuidance) {
7348
7708
  const label = STEP_LABELS[step];
7349
7709
  p9.note(STEP_INTROS[step], `Step: ${label}`);
@@ -7359,13 +7719,52 @@ async function runStep(step, outputDir, state, config, projectContext, nonIntera
7359
7719
  try {
7360
7720
  let result;
7361
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
+ }
7362
7759
  case "pagesFinder": {
7363
7760
  const { runPageFinder: runPageFinder2 } = await Promise.resolve().then(() => (init_pages_finder(), pages_finder_exports));
7761
+ const projectMap = await loadProjectMap(outputDir);
7364
7762
  const pages = await runPageFinder2({
7365
7763
  projectRoot: config.projectRoot,
7366
7764
  outputDir,
7367
7765
  modelId: config.modelId,
7368
- nonInteractive
7766
+ nonInteractive,
7767
+ extraMessage: projectMap != null ? formatFrontendScope(projectMap) : void 0
7369
7768
  });
7370
7769
  await savePages(outputDir, pages);
7371
7770
  break;
@@ -7384,18 +7783,21 @@ async function runStep(step, outputDir, state, config, projectContext, nonIntera
7384
7783
  }
7385
7784
  case "entityAudit": {
7386
7785
  const { runEntityAudit: runEntityAudit2 } = await Promise.resolve().then(() => (init_entity_audit(), entity_audit_exports));
7786
+ const auditMap = await loadProjectMap(outputDir);
7387
7787
  result = await runEntityAudit2({
7388
7788
  projectRoot: config.projectRoot,
7389
7789
  outputDir,
7390
7790
  modelId: config.modelId,
7391
7791
  projectContext,
7392
7792
  nonInteractive,
7393
- retryGuidance
7793
+ retryGuidance,
7794
+ scopeHint: auditMap != null ? formatBackendScope(auditMap) : void 0
7394
7795
  });
7395
7796
  break;
7396
7797
  }
7397
7798
  case "scenarioRecipe": {
7398
7799
  const { runScenarioRecipe: runScenarioRecipe2 } = await Promise.resolve().then(() => (init_scenario_recipe(), scenario_recipe_exports));
7800
+ const recipeMap = await loadProjectMap(outputDir);
7399
7801
  result = await runScenarioRecipe2({
7400
7802
  projectRoot: config.projectRoot,
7401
7803
  outputDir,
@@ -7403,7 +7805,8 @@ async function runStep(step, outputDir, state, config, projectContext, nonIntera
7403
7805
  config,
7404
7806
  projectContext,
7405
7807
  nonInteractive,
7406
- retryGuidance
7808
+ retryGuidance,
7809
+ scopeHint: recipeMap != null ? formatBackendScope(recipeMap) : void 0
7407
7810
  });
7408
7811
  break;
7409
7812
  }
@@ -7517,14 +7920,14 @@ async function showStatus(outputDir) {
7517
7920
  }
7518
7921
  }
7519
7922
  var BANNER = `
7520
- \x1B[36m\x1B[1m \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557
7923
+ ${PRIMARY}${BOLD} \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557
7521
7924
  \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
7522
7925
  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
7523
7926
  \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551
7524
7927
  \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
7525
7928
  \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
7526
- \x1B[0m
7527
- \x1B[2m E2E Test Planner - Generate exhaustive test suites from your codebase\x1B[0m
7929
+ ${RESET}
7930
+ ${DIM} E2E Test Planner - Generate exhaustive test suites from your codebase${RESET}
7528
7931
  `;
7529
7932
  function ensureAutonomaAuth() {
7530
7933
  if (readEnv().AUTONOMA_API_TOKEN?.trim()) return true;
@@ -7556,6 +7959,7 @@ async function gatherProjectContext() {
7556
7959
  };
7557
7960
  }
7558
7961
  async function main() {
7962
+ installTerminationDiagnostics();
7559
7963
  const args = parseArgs(process.argv.slice(2));
7560
7964
  const command = process.argv[2];
7561
7965
  if (command === "status") {
@@ -7573,7 +7977,7 @@ async function main() {
7573
7977
  if (command === "help" || args.help) {
7574
7978
  console.log("Usage:");
7575
7979
  console.log(
7576
- " 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]"
7577
7981
  );
7578
7982
  console.log(" test-planner status [--project <path>]");
7579
7983
  console.log("");
@@ -7584,19 +7988,25 @@ async function main() {
7584
7988
  p9.intro("Let's generate your test suite");
7585
7989
  const resumeCommand = `autonoma-planner --resume` + (args.project ? ` --project ${args.project}` : "");
7586
7990
  installInterruptHandler({
7587
- onExit: () => {
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) => {
7588
7995
  track("cli_run_exited");
7589
7996
  restoreTerminal();
7590
7997
  console.log("");
7591
7998
  p9.log.warn(`Your progress is saved. To resume, run:
7592
7999
  ${resumeCommand}`);
7593
- void flushAnalytics().finally(() => process.exit(0));
8000
+ void flushAnalytics().finally(() => process.exit(exitCode));
7594
8001
  }
7595
8002
  });
8003
+ const backendsArg = strArg(args, "backends");
7596
8004
  const config = loadConfig({
7597
8005
  project: strArg(args, "project"),
7598
8006
  model: strArg(args, "model"),
7599
- 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
7600
8010
  });
7601
8011
  if (!ensureAutonomaAuth()) {
7602
8012
  return;
@@ -7687,12 +8097,20 @@ or reveal hidden files (macOS: Cmd+Shift+. ) to see it.`,
7687
8097
  p9.outro("Done");
7688
8098
  return;
7689
8099
  }
7690
- const startStep = isResuming ? nextPendingStep(state) : "pagesFinder";
8100
+ const startStep = isResuming ? nextPendingStep(state) : "projectMapper";
7691
8101
  if (!startStep) {
7692
8102
  p9.log.success("All steps complete.");
7693
8103
  return;
7694
8104
  }
7695
- const steps = ["pagesFinder", "kb", "entityAudit", "scenarioRecipe", "recipeBuilder", "testGenerator"];
8105
+ const steps = [
8106
+ "projectMapper",
8107
+ "pagesFinder",
8108
+ "kb",
8109
+ "entityAudit",
8110
+ "scenarioRecipe",
8111
+ "recipeBuilder",
8112
+ "testGenerator"
8113
+ ];
7696
8114
  const startIdx = steps.indexOf(startStep);
7697
8115
  p9.note(steps.map((s, idx) => `${idx + 1}. ${STEP_LABELS[s]} - ${STEP_SUMMARIES[s]}`).join("\n"), "Here's the plan");
7698
8116
  try {