@autonoma-ai/planner 0.1.20 → 0.1.21

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
@@ -23,6 +23,202 @@ var init_esm_shims = __esm({
23
23
  }
24
24
  });
25
25
 
26
+ // src/core/to-record.ts
27
+ function toRecord(value) {
28
+ if (typeof value !== "object" || value === null) return {};
29
+ const record = {};
30
+ for (const [key, entry] of Object.entries(value)) {
31
+ record[key] = entry;
32
+ }
33
+ return record;
34
+ }
35
+ var init_to_record = __esm({
36
+ "src/core/to-record.ts"() {
37
+ "use strict";
38
+ init_esm_shims();
39
+ }
40
+ });
41
+
42
+ // src/agents/04-recipe-builder/recipe.ts
43
+ import { readFile, writeFile } from "fs/promises";
44
+ import { join } from "path";
45
+ function collectRefs(value, out) {
46
+ if (Array.isArray(value)) {
47
+ for (const v of value) collectRefs(v, out);
48
+ } else if (value !== null && typeof value === "object") {
49
+ const obj = toRecord(value);
50
+ if (typeof obj._ref === "string") out.add(obj._ref);
51
+ for (const v of Object.values(obj)) collectRefs(v, out);
52
+ }
53
+ }
54
+ function buildSingleEntityRecipe(entityName, models, entityOrder, allEntities) {
55
+ const modelMap = new Map(models.map((m) => [m.name, m]));
56
+ const aliasOwner = /* @__PURE__ */ new Map();
57
+ for (const [name, entity] of Object.entries(allEntities)) {
58
+ for (const rec of entity?.recipeData ?? []) {
59
+ if (typeof rec._alias === "string") aliasOwner.set(rec._alias, name);
60
+ }
61
+ }
62
+ const recipe = {};
63
+ const done = /* @__PURE__ */ new Set();
64
+ const onStack = /* @__PURE__ */ new Set();
65
+ function include(name) {
66
+ if (done.has(name) || onStack.has(name)) return;
67
+ onStack.add(name);
68
+ const records = allEntities[name]?.recipeData ?? [];
69
+ for (const dep of modelMap.get(name)?.created_by ?? []) {
70
+ if (entityOrder.includes(dep.owner)) include(dep.owner);
71
+ }
72
+ const refs = /* @__PURE__ */ new Set();
73
+ collectRefs(records, refs);
74
+ for (const alias of refs) {
75
+ const owner = aliasOwner.get(alias);
76
+ if (owner && owner !== name) include(owner);
77
+ }
78
+ onStack.delete(name);
79
+ done.add(name);
80
+ if (records.length > 0) recipe[name] = records;
81
+ }
82
+ include(entityName);
83
+ return recipe;
84
+ }
85
+ function buildFullRecipe(entityOrder, allEntities) {
86
+ const recipe = {};
87
+ for (const name of entityOrder) {
88
+ const entity = allEntities[name];
89
+ if (entity?.recipeData && entity.recipeData.length > 0) {
90
+ recipe[name] = entity.recipeData;
91
+ }
92
+ }
93
+ return recipe;
94
+ }
95
+ function buildSubmittableRecipe(create, description) {
96
+ return {
97
+ version: 1,
98
+ source: {
99
+ discoverPath: "discover.json",
100
+ scenariosPath: "scenarios.md"
101
+ },
102
+ validationMode: "endpoint-lifecycle",
103
+ recipes: [
104
+ {
105
+ name: "standard",
106
+ description,
107
+ create,
108
+ validation: {
109
+ status: "validated",
110
+ method: "endpoint-up-down"
111
+ }
112
+ }
113
+ ]
114
+ };
115
+ }
116
+ async function saveRecipe(outputDir, recipe) {
117
+ await writeFile(join(outputDir, RECIPE_FILE), JSON.stringify(recipe, null, 2), "utf-8");
118
+ }
119
+ async function loadRecipe(outputDir) {
120
+ try {
121
+ const raw = await readFile(join(outputDir, RECIPE_FILE), "utf-8");
122
+ const parsed = JSON.parse(raw);
123
+ return parsed;
124
+ } catch {
125
+ return void 0;
126
+ }
127
+ }
128
+ var RECIPE_FILE;
129
+ var init_recipe = __esm({
130
+ "src/agents/04-recipe-builder/recipe.ts"() {
131
+ "use strict";
132
+ init_esm_shims();
133
+ init_to_record();
134
+ RECIPE_FILE = "recipe.json";
135
+ }
136
+ });
137
+
138
+ // src/agents/04-recipe-builder/phases/submit.ts
139
+ import * as p from "@clack/prompts";
140
+ async function runSubmit(state, outputDir, autonomaApiUrl, autonomaApiToken, autonomaGenerationId) {
141
+ const fullCreate = buildFullRecipe(state.entityOrder, state.entities);
142
+ const recipe = buildSubmittableRecipe(fullCreate, "Standard test scenario with realistic data");
143
+ await saveRecipe(outputDir, recipe);
144
+ p.log.success(`Recipe saved to ${RECIPE_FILE2}`);
145
+ const uploaded = await submitRecipe(recipe, {
146
+ apiUrl: autonomaApiUrl,
147
+ apiToken: autonomaApiToken,
148
+ generationId: autonomaGenerationId
149
+ });
150
+ return { recipePath: RECIPE_FILE2, uploaded };
151
+ }
152
+ async function uploadRecipeFromDisk(outputDir, creds) {
153
+ const recipe = await loadRecipe(outputDir);
154
+ if (recipe == null) {
155
+ p.log.error(
156
+ `No ${RECIPE_FILE2} found in ${outputDir}. Run the planner's recipe step first to generate it, then retry.`
157
+ );
158
+ return false;
159
+ }
160
+ return submitRecipe(recipe, creds);
161
+ }
162
+ async function submitRecipe(recipe, creds) {
163
+ const { apiUrl, apiToken, generationId } = creds;
164
+ if (!apiUrl || !apiToken || !generationId) {
165
+ p.log.info(
166
+ "Autonoma API credentials not configured - recipe saved locally, not uploaded. Set AUTONOMA_API_URL, AUTONOMA_API_TOKEN and AUTONOMA_GENERATION_ID, then run `" + UPLOAD_COMMAND + "`."
167
+ );
168
+ return false;
169
+ }
170
+ const url = `${apiUrl.replace(/\/+$/, "")}/v1/setup/setups/${generationId}/scenario-recipe-versions`;
171
+ p.log.step("Submitting recipe to Autonoma...");
172
+ let res;
173
+ try {
174
+ res = await fetch(url, {
175
+ method: "POST",
176
+ headers: {
177
+ "Content-Type": "application/json",
178
+ Authorization: `Bearer ${apiToken}`
179
+ },
180
+ body: JSON.stringify(recipe)
181
+ });
182
+ } catch (err) {
183
+ p.log.error(`Recipe submission failed (network error): ${err instanceof Error ? err.message : String(err)}`);
184
+ printRecipeForRecovery(recipe);
185
+ return false;
186
+ }
187
+ if (res.ok) {
188
+ p.log.success(`Recipe submitted successfully (HTTP ${res.status})`);
189
+ return true;
190
+ }
191
+ const text6 = await res.text();
192
+ p.log.error(`Recipe submission failed (HTTP ${res.status}): ${text6}`);
193
+ printRecipeForRecovery(recipe);
194
+ return false;
195
+ }
196
+ function printRecipeForRecovery(recipe) {
197
+ console.log(
198
+ [
199
+ "",
200
+ "\u2500".repeat(72),
201
+ "RECIPE NOT UPLOADED - copy the JSON below into a recipe.json and re-upload with:",
202
+ ` ${UPLOAD_COMMAND}`,
203
+ "(with the same AUTONOMA_API_URL / AUTONOMA_API_TOKEN / AUTONOMA_GENERATION_ID env vars set)",
204
+ "\u2500".repeat(72),
205
+ JSON.stringify(recipe, null, 2),
206
+ "\u2500".repeat(72),
207
+ ""
208
+ ].join("\n")
209
+ );
210
+ }
211
+ var RECIPE_FILE2, UPLOAD_COMMAND;
212
+ var init_submit = __esm({
213
+ "src/agents/04-recipe-builder/phases/submit.ts"() {
214
+ "use strict";
215
+ init_esm_shims();
216
+ init_recipe();
217
+ RECIPE_FILE2 = "recipe.json";
218
+ UPLOAD_COMMAND = "npx @autonoma-ai/planner@latest upload";
219
+ }
220
+ });
221
+
26
222
  // src/env.ts
27
223
  import { createEnv } from "@t3-oss/env-core";
28
224
  import { z } from "zod";
@@ -82,14 +278,14 @@ var init_debug = __esm({
82
278
 
83
279
  // src/core/version.ts
84
280
  import { readFileSync as readFileSync3 } from "fs";
85
- import { dirname, join as join3 } from "path";
281
+ import { dirname, join as join4 } from "path";
86
282
  import { fileURLToPath as fileURLToPath2 } from "url";
87
283
  function resolveVersion() {
88
284
  try {
89
285
  const here = dirname(fileURLToPath2(import.meta.url));
90
286
  for (const rel of ["../package.json", "../../package.json", "../../../package.json"]) {
91
287
  try {
92
- const pkg = JSON.parse(readFileSync3(join3(here, rel), "utf-8"));
288
+ const pkg = JSON.parse(readFileSync3(join4(here, rel), "utf-8"));
93
289
  if (pkg?.name === PACKAGE_NAME && typeof pkg.version === "string") {
94
290
  return pkg.version;
95
291
  }
@@ -114,7 +310,7 @@ var init_version = __esm({
114
310
  import { randomUUID } from "crypto";
115
311
  import { readFileSync as readFileSync4, writeFileSync, mkdirSync } from "fs";
116
312
  import { homedir as homedir2 } from "os";
117
- import { join as join4 } from "path";
313
+ import { join as join5 } from "path";
118
314
  function resolveKey() {
119
315
  return (readEnv().AUTONOMA_POSTHOG_KEY ?? POSTHOG_PUBLIC_KEY).trim();
120
316
  }
@@ -209,8 +405,8 @@ var init_analytics = __esm({
209
405
  init_env();
210
406
  init_debug();
211
407
  init_version();
212
- AUTONOMA_HOME2 = join4(homedir2(), ".autonoma");
213
- DEVICE_ID_PATH = join4(AUTONOMA_HOME2, ".device-id");
408
+ AUTONOMA_HOME2 = join5(homedir2(), ".autonoma");
409
+ DEVICE_ID_PATH = join5(AUTONOMA_HOME2, ".device-id");
214
410
  POSTHOG_PUBLIC_KEY = "phc_mUOwUj62r8vyiisFPvXLC3G5RftETIBMnKNSHqTBdka";
215
411
  DEFAULT_HOST = "https://us.i.posthog.com";
216
412
  RUN_ID = randomUUID();
@@ -242,14 +438,14 @@ var init_colors = __esm({
242
438
  });
243
439
 
244
440
  // src/core/context.ts
245
- import { readFile, writeFile } from "fs/promises";
246
- import { join as join5 } from "path";
441
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
442
+ import { join as join6 } from "path";
247
443
  async function saveContext(outputDir, ctx) {
248
- await writeFile(join5(outputDir, CONTEXT_FILE), JSON.stringify(ctx, null, 2), "utf-8");
444
+ await writeFile2(join6(outputDir, CONTEXT_FILE), JSON.stringify(ctx, null, 2), "utf-8");
249
445
  }
250
446
  async function loadContext(outputDir) {
251
447
  try {
252
- const raw = await readFile(join5(outputDir, CONTEXT_FILE), "utf-8");
448
+ const raw = await readFile2(join6(outputDir, CONTEXT_FILE), "utf-8");
253
449
  const parsed = JSON.parse(raw);
254
450
  return parsed;
255
451
  } catch {
@@ -493,16 +689,16 @@ var init_notify = __esm({
493
689
  });
494
690
 
495
691
  // src/core/project-map.ts
496
- import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
497
- import { join as join8 } from "path";
692
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
693
+ import { join as join9 } from "path";
498
694
  import { z as z2 } from "zod";
499
695
  async function saveProjectMap(outputDir, map) {
500
- await writeFile3(join8(outputDir, PROJECT_MAP_FILE), JSON.stringify(map, null, 2), "utf-8");
696
+ await writeFile4(join9(outputDir, PROJECT_MAP_FILE), JSON.stringify(map, null, 2), "utf-8");
501
697
  }
502
698
  async function loadProjectMap(outputDir) {
503
- const path3 = join8(outputDir, PROJECT_MAP_FILE);
699
+ const path3 = join9(outputDir, PROJECT_MAP_FILE);
504
700
  try {
505
- const raw = await readFile3(path3, "utf-8");
701
+ const raw = await readFile4(path3, "utf-8");
506
702
  const parsed = ProjectMapSchema.safeParse(JSON.parse(raw));
507
703
  if (parsed.success) return parsed.data;
508
704
  debugLog("project-map.json failed schema validation, ignoring it", { path: path3, issues: parsed.error.issues });
@@ -911,8 +1107,8 @@ var init_agent = __esm({
911
1107
  });
912
1108
 
913
1109
  // src/core/gitignore.ts
914
- import { readFile as readFile6 } from "fs/promises";
915
- import { join as join11, relative as relative2 } from "path";
1110
+ import { readFile as readFile7 } from "fs/promises";
1111
+ import { join as join12, relative as relative2 } from "path";
916
1112
  import { glob as glob2 } from "glob";
917
1113
  async function loadGitignorePatterns(projectRoot) {
918
1114
  const patterns = [
@@ -932,10 +1128,10 @@ async function loadGitignorePatterns(projectRoot) {
932
1128
  ];
933
1129
  const matches = await glob2("**/.gitignore", { cwd: projectRoot, dot: true });
934
1130
  for (const match of matches) {
935
- const fullPath = join11(projectRoot, match);
1131
+ const fullPath = join12(projectRoot, match);
936
1132
  try {
937
- const content = await readFile6(fullPath, "utf-8");
938
- const prefix = relative2(projectRoot, join11(projectRoot, match, ".."));
1133
+ const content = await readFile7(fullPath, "utf-8");
1134
+ const prefix = relative2(projectRoot, join12(projectRoot, match, ".."));
939
1135
  const parsed = parseGitignore(content, prefix);
940
1136
  patterns.push(...parsed);
941
1137
  } catch (err) {
@@ -1167,7 +1363,7 @@ var init_grep = __esm({
1167
1363
  // src/tools/list-directory.ts
1168
1364
  import { readdir } from "fs/promises";
1169
1365
  import { stat } from "fs/promises";
1170
- import { join as join12, relative as relative3 } from "path";
1366
+ import { join as join13, relative as relative3 } from "path";
1171
1367
  import { tool as tool4 } from "ai";
1172
1368
  import { minimatch } from "minimatch";
1173
1369
  import { z as z7 } from "zod";
@@ -1192,7 +1388,7 @@ async function buildTree(dirPath, maxDepth, currentDepth, isIgnored, relativeBas
1192
1388
  const withTypes = [];
1193
1389
  for (const name of rawEntries) {
1194
1390
  try {
1195
- const s = await stat(join12(dirPath, name));
1391
+ const s = await stat(join13(dirPath, name));
1196
1392
  withTypes.push({ name, isDir: s.isDirectory() });
1197
1393
  } catch {
1198
1394
  withTypes.push({ name, isDir: false });
@@ -1212,7 +1408,7 @@ async function buildTree(dirPath, maxDepth, currentDepth, isIgnored, relativeBas
1212
1408
  }
1213
1409
  if (entry.isDir) {
1214
1410
  const children = await buildTree(
1215
- join12(dirPath, entry.name),
1411
+ join13(dirPath, entry.name),
1216
1412
  maxDepth,
1217
1413
  currentDepth + 1,
1218
1414
  isIgnored,
@@ -1265,7 +1461,7 @@ async function buildListDirectoryTool(workingDirectory) {
1265
1461
  };
1266
1462
  }
1267
1463
  seen.add(cacheKey);
1268
- const targetDir = input.path === "." ? workingDirectory : join12(workingDirectory, input.path);
1464
+ const targetDir = input.path === "." ? workingDirectory : join13(workingDirectory, input.path);
1269
1465
  try {
1270
1466
  const s = await stat(targetDir);
1271
1467
  if (!s.isDirectory()) {
@@ -1297,7 +1493,7 @@ var init_list_directory = __esm({
1297
1493
  });
1298
1494
 
1299
1495
  // src/tools/read-file.ts
1300
- import { readFile as readFile7 } from "fs/promises";
1496
+ import { readFile as readFile8 } from "fs/promises";
1301
1497
  import { relative as relative4, resolve as resolve2 } from "path";
1302
1498
  import { tool as tool5 } from "ai";
1303
1499
  import { z as z8 } from "zod";
@@ -1325,7 +1521,7 @@ async function executeReadFile(workingDirectory, filePath, offset, limit) {
1325
1521
  const resolved = resolveSandboxedPath(workingDirectory, filePath);
1326
1522
  if ("error" in resolved) return resolved;
1327
1523
  try {
1328
- const content = await readFile7(resolved.absolutePath, "utf-8");
1524
+ const content = await readFile8(resolved.absolutePath, "utf-8");
1329
1525
  const sliced = sliceLines(content, offset ?? 0, limit ?? MAX_LINES);
1330
1526
  return {
1331
1527
  path: resolved.relativePath,
@@ -1461,7 +1657,7 @@ Be thorough but focused - only investigate what's relevant to your instruction.`
1461
1657
  });
1462
1658
 
1463
1659
  // src/tools/write-file.ts
1464
- import { writeFile as writeFile5, mkdir as mkdir2 } from "fs/promises";
1660
+ import { writeFile as writeFile6, mkdir as mkdir2 } from "fs/promises";
1465
1661
  import { dirname as dirname2, relative as relative5, resolve as resolve3 } from "path";
1466
1662
  import { tool as tool7 } from "ai";
1467
1663
  import { z as z10 } from "zod";
@@ -1474,7 +1670,7 @@ async function executeWriteFile(outputDirectory, filePath, content) {
1474
1670
  }
1475
1671
  try {
1476
1672
  await mkdir2(dirname2(absolutePath), { recursive: true });
1477
- await writeFile5(absolutePath, content, "utf-8");
1673
+ await writeFile6(absolutePath, content, "utf-8");
1478
1674
  return { path: relativePath, bytesWritten: content.length };
1479
1675
  } catch (err) {
1480
1676
  const message = err instanceof Error ? err.message : String(err);
@@ -1501,7 +1697,7 @@ var init_write_file = __esm({
1501
1697
  });
1502
1698
 
1503
1699
  // src/tools/ask-user.ts
1504
- import * as p2 from "@clack/prompts";
1700
+ import * as p3 from "@clack/prompts";
1505
1701
  import { tool as tool8 } from "ai";
1506
1702
  import { z as z11 } from "zod";
1507
1703
  function buildAskUserTool() {
@@ -1518,8 +1714,8 @@ function buildAskUserTool() {
1518
1714
  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
1715
  };
1520
1716
  }
1521
- const answer = await p2.text({ message: input.question });
1522
- if (p2.isCancel(answer)) return { answer: "User skipped this question" };
1717
+ const answer = await p3.text({ message: input.question });
1718
+ if (p3.isCancel(answer)) return { answer: "User skipped this question" };
1523
1719
  return { answer };
1524
1720
  }
1525
1721
  });
@@ -1706,13 +1902,13 @@ var init_pages_finder = __esm({
1706
1902
 
1707
1903
  // src/core/review.ts
1708
1904
  import { access } from "fs/promises";
1709
- import { join as join13, isAbsolute } from "path";
1710
- import * as p3 from "@clack/prompts";
1905
+ import { join as join14, isAbsolute } from "path";
1906
+ import * as p4 from "@clack/prompts";
1711
1907
  import spawn from "cross-spawn";
1712
1908
  import which from "which";
1713
1909
  function resolvePath(artifact, outputDir) {
1714
1910
  if (isAbsolute(artifact)) return artifact;
1715
- return join13(outputDir, artifact);
1911
+ return join14(outputDir, artifact);
1716
1912
  }
1717
1913
  async function detectEditors() {
1718
1914
  if (cachedEditors) return cachedEditors;
@@ -1736,7 +1932,7 @@ async function launchEditor(editor, files) {
1736
1932
  };
1737
1933
  const proc = spawn(editor.command, args, { stdio: "inherit" });
1738
1934
  proc.on("error", (err) => {
1739
- p3.log.warn(`Couldn't open ${editor.label} (${err.message}). Review the files manually:`);
1935
+ p4.log.warn(`Couldn't open ${editor.label} (${err.message}). Review the files manually:`);
1740
1936
  for (const f of files) console.log(` ${CYAN}${f}${RESET3}`);
1741
1937
  settle();
1742
1938
  });
@@ -1750,17 +1946,17 @@ async function launchEditor(editor, files) {
1750
1946
  async function openInEditor(files) {
1751
1947
  const editors = await detectEditors();
1752
1948
  if (editors.length === 0) {
1753
- p3.log.warn("No editors found. Review the files manually:");
1949
+ p4.log.warn("No editors found. Review the files manually:");
1754
1950
  for (const f of files) console.log(` ${CYAN}${f}${RESET3}`);
1755
1951
  return;
1756
1952
  }
1757
1953
  if (preferredEditor) {
1758
1954
  const editor2 = editors.find((e) => e.command === preferredEditor);
1759
1955
  if (editor2) {
1760
- const open = await p3.confirm({
1956
+ const open = await p4.confirm({
1761
1957
  message: `Open in ${editor2.label}?`
1762
1958
  });
1763
- if (!p3.isCancel(open) && open) {
1959
+ if (!p4.isCancel(open) && open) {
1764
1960
  await launchEditor(editor2, files);
1765
1961
  }
1766
1962
  return;
@@ -1770,20 +1966,20 @@ async function openInEditor(files) {
1770
1966
  value: e.command,
1771
1967
  label: e.label
1772
1968
  }));
1773
- const selected = await p3.select({
1969
+ const selected = await p4.select({
1774
1970
  message: "Open output files for review?",
1775
1971
  options: [...options, { value: "skip", label: "No, skip - I'll review later" }]
1776
1972
  });
1777
- if (p3.isCancel(selected) || selected === "skip") return;
1973
+ if (p4.isCancel(selected) || selected === "skip") return;
1778
1974
  const editor = editors.find((e) => e.command === selected);
1779
- const remember = await p3.select({
1975
+ const remember = await p4.select({
1780
1976
  message: `Use ${editor.label} for all future reviews?`,
1781
1977
  options: [
1782
1978
  { value: "always", label: `Yes, always use ${editor.label}` },
1783
1979
  { value: "ask", label: "No, ask me each time" }
1784
1980
  ]
1785
1981
  });
1786
- if (!p3.isCancel(remember) && remember === "always") {
1982
+ if (!p4.isCancel(remember) && remember === "always") {
1787
1983
  preferredEditor = editor.command;
1788
1984
  }
1789
1985
  await launchEditor(editor, files);
@@ -1794,7 +1990,7 @@ async function showResults(result, options) {
1794
1990
  if (result.artifacts.length === 0) {
1795
1991
  const knownFiles = ["AUTONOMA.md", "entity-audit.md", "scenarios.md"];
1796
1992
  for (const f of knownFiles) {
1797
- const fullPath = join13(options.outputDir, f);
1993
+ const fullPath = join14(options.outputDir, f);
1798
1994
  try {
1799
1995
  await access(fullPath);
1800
1996
  result.artifacts.push(f);
@@ -1824,7 +2020,7 @@ async function showResults(result, options) {
1824
2020
  }
1825
2021
  }
1826
2022
  if (options.reviewGuidance) {
1827
- p3.note(options.reviewGuidance, "What to check");
2023
+ p4.note(options.reviewGuidance, "What to check");
1828
2024
  }
1829
2025
  const showPreview = options.showPreview !== false;
1830
2026
  if (showPreview && resolvedPaths.length > 0 && !options.nonInteractive) {
@@ -1837,21 +2033,21 @@ async function reviewLoop(result, options) {
1837
2033
  await showResults(result, options);
1838
2034
  if (options.nonInteractive) return result;
1839
2035
  while (true) {
1840
- const input = await p3.text({
2036
+ const input = await p4.text({
1841
2037
  message: "Review the output. Press Enter to approve, or type feedback for the agent.",
1842
2038
  placeholder: "Looks good (Enter to approve)",
1843
2039
  defaultValue: ""
1844
2040
  });
1845
- if (p3.isCancel(input)) {
1846
- p3.log.warn("Cancelled.");
2041
+ if (p4.isCancel(input)) {
2042
+ p4.log.warn("Cancelled.");
1847
2043
  return result;
1848
2044
  }
1849
2045
  const feedback = input.trim();
1850
2046
  if (feedback === "") {
1851
- p3.log.success("Approved - moving on.");
2047
+ p4.log.success("Approved - moving on.");
1852
2048
  return result;
1853
2049
  }
1854
- p3.log.info(`Sending feedback to ${options.agentId}...`);
2050
+ p4.log.info(`Sending feedback to ${options.agentId}...`);
1855
2051
  console.log("");
1856
2052
  const revised = await options.onFeedback(feedback);
1857
2053
  if (revised) {
@@ -1883,13 +2079,13 @@ var init_review = __esm({
1883
2079
  });
1884
2080
 
1885
2081
  // src/agents/01-kb-generator/flows.ts
1886
- import { readFile as readFile8 } from "fs/promises";
1887
- import { join as join14 } from "path";
2082
+ import { readFile as readFile9 } from "fs/promises";
2083
+ import { join as join15 } from "path";
1888
2084
  import matter from "gray-matter";
1889
2085
  async function parseCoreFlows(outputDir) {
1890
2086
  let raw;
1891
2087
  try {
1892
- raw = await readFile8(join14(outputDir, "AUTONOMA.md"), "utf-8");
2088
+ raw = await readFile9(join15(outputDir, "AUTONOMA.md"), "utf-8");
1893
2089
  } catch {
1894
2090
  return [];
1895
2091
  }
@@ -2102,8 +2298,8 @@ var kb_generator_exports = {};
2102
2298
  __export(kb_generator_exports, {
2103
2299
  runKBGenerator: () => runKBGenerator
2104
2300
  });
2105
- import { readFile as readFile9 } from "fs/promises";
2106
- import { join as join15, resolve as resolve5 } from "path";
2301
+ import { readFile as readFile10 } from "fs/promises";
2302
+ import { join as join16, resolve as resolve5 } from "path";
2107
2303
  import { tool as tool11 } from "ai";
2108
2304
  import { z as z13 } from "zod";
2109
2305
  function buildRegisterPagesTool(tracker) {
@@ -2231,8 +2427,8 @@ Output files:
2231
2427
  const agentConfig = buildKbAgentConfig(tracker, model, input, onStepFinish, setResult);
2232
2428
  await runAgent(agentConfig, prompt, () => result);
2233
2429
  logger.summary();
2234
- const autonomaPath = join15(input.outputDir, "AUTONOMA.md");
2235
- const autonomaExists = await readFile9(autonomaPath, "utf-8").then(() => true).catch((err) => {
2430
+ const autonomaPath = join16(input.outputDir, "AUTONOMA.md");
2431
+ const autonomaExists = await readFile10(autonomaPath, "utf-8").then(() => true).catch((err) => {
2236
2432
  debugLog("AUTONOMA.md not found while checking step completion", { err });
2237
2433
  return false;
2238
2434
  });
@@ -2351,12 +2547,12 @@ var init_kb_generator = __esm({
2351
2547
  });
2352
2548
 
2353
2549
  // src/agents/04-recipe-builder/entity-order.ts
2354
- import { readFile as readFile10 } from "fs/promises";
2355
- import { join as join16 } from "path";
2550
+ import { readFile as readFile11 } from "fs/promises";
2551
+ import { join as join17 } from "path";
2356
2552
  import matter2 from "gray-matter";
2357
2553
  import { z as z14 } from "zod";
2358
2554
  async function parseEntityAudit(outputDir) {
2359
- const raw = await readFile10(join16(outputDir, "entity-audit.md"), "utf-8");
2555
+ const raw = await readFile11(join17(outputDir, "entity-audit.md"), "utf-8");
2360
2556
  try {
2361
2557
  const parsed = frontmatterSchema.safeParse(matter2(raw).data);
2362
2558
  if (parsed.success && parsed.data.models.length > 0) {
@@ -2742,8 +2938,8 @@ var entity_audit_exports = {};
2742
2938
  __export(entity_audit_exports, {
2743
2939
  runEntityAudit: () => runEntityAudit
2744
2940
  });
2745
- import { readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
2746
- import { join as join17 } from "path";
2941
+ import { readFile as readFile12, writeFile as writeFile7 } from "fs/promises";
2942
+ import { join as join18 } from "path";
2747
2943
  import { tool as tool12 } from "ai";
2748
2944
  import { glob as glob4 } from "glob";
2749
2945
  import { z as z15 } from "zod";
@@ -2874,7 +3070,7 @@ async function findPrismaSchema(projectRoot) {
2874
3070
  return candidates[0] ?? void 0;
2875
3071
  }
2876
3072
  async function extractPrismaModels(schemaPath) {
2877
- const content = await readFile11(schemaPath, "utf-8");
3073
+ const content = await readFile12(schemaPath, "utf-8");
2878
3074
  return content.split("\n").filter((line) => line.startsWith("model ")).map((line) => line.split(/\s+/)[1]).filter((name) => name != null);
2879
3075
  }
2880
3076
  async function detectFrameworkAndModels(projectRoot) {
@@ -2961,8 +3157,8 @@ ${formatException(err)}`);
2961
3157
  logger.summary();
2962
3158
  const writeCanonicalAudit = async () => {
2963
3159
  if (tracker.auditedModels.size === 0) return void 0;
2964
- const auditPath = join17(input.outputDir, "entity-audit.md");
2965
- await writeFile6(auditPath, tracker.generateAuditMarkdown(), "utf-8");
3160
+ const auditPath = join18(input.outputDir, "entity-audit.md");
3161
+ await writeFile7(auditPath, tracker.generateAuditMarkdown(), "utf-8");
2966
3162
  return auditPath;
2967
3163
  };
2968
3164
  const canonicalPath = await writeCanonicalAudit();
@@ -2999,9 +3195,9 @@ When done with changes, call finish again.`;
2999
3195
  }
3000
3196
  });
3001
3197
  if (!reviewed) {
3002
- const auditPath = join17(input.outputDir, "entity-audit.md");
3198
+ const auditPath = join18(input.outputDir, "entity-audit.md");
3003
3199
  try {
3004
- await readFile11(auditPath, "utf-8");
3200
+ await readFile12(auditPath, "utf-8");
3005
3201
  return {
3006
3202
  success: true,
3007
3203
  artifacts: ["entity-audit.md"],
@@ -3143,11 +3339,11 @@ ${duals.length > 0 ? duals.map((m) => `- **${m.name}** - standalone: ${m.creatio
3143
3339
  });
3144
3340
 
3145
3341
  // src/core/parse-entity-audit.ts
3146
- import { readFile as readFile12 } from "fs/promises";
3147
- import { join as join18 } from "path";
3342
+ import { readFile as readFile13 } from "fs/promises";
3343
+ import { join as join19 } from "path";
3148
3344
  async function parseEntityNames(outputDir) {
3149
3345
  try {
3150
- const content = await readFile12(join18(outputDir, "entity-audit.md"), "utf-8");
3346
+ const content = await readFile13(join19(outputDir, "entity-audit.md"), "utf-8");
3151
3347
  const names = [];
3152
3348
  for (const line of content.split("\n")) {
3153
3349
  const match = line.match(/^\s+-\s+name:\s+(.+)$/);
@@ -3220,13 +3416,13 @@ values; the recipe builder generates the exact records from what you write.`;
3220
3416
  });
3221
3417
 
3222
3418
  // src/agents/03-scenario-recipe/scenario-table.ts
3223
- import { readFile as readFile13 } from "fs/promises";
3224
- import { join as join19 } from "path";
3419
+ import { readFile as readFile14 } from "fs/promises";
3420
+ import { join as join20 } from "path";
3225
3421
  import matter3 from "gray-matter";
3226
3422
  async function parseScenario(outputDir) {
3227
3423
  let raw;
3228
3424
  try {
3229
- raw = await readFile13(join19(outputDir, "scenarios.md"), "utf-8");
3425
+ raw = await readFile14(join20(outputDir, "scenarios.md"), "utf-8");
3230
3426
  } catch {
3231
3427
  return { scenarioNames: [], entityTypes: [] };
3232
3428
  }
@@ -3307,8 +3503,8 @@ __export(scenario_recipe_exports, {
3307
3503
  feedbackToScenario: () => feedbackToScenario,
3308
3504
  runScenarioRecipe: () => runScenarioRecipe
3309
3505
  });
3310
- import { readFile as readFile14 } from "fs/promises";
3311
- import { join as join20 } from "path";
3506
+ import { readFile as readFile15 } from "fs/promises";
3507
+ import { join as join21 } from "path";
3312
3508
  import { tool as tool13 } from "ai";
3313
3509
  import { z as z16 } from "zod";
3314
3510
  function buildFinishTool3(requiredEntities, outputDir, onFinish) {
@@ -3322,7 +3518,7 @@ function buildFinishTool3(requiredEntities, outputDir, onFinish) {
3322
3518
  execute: async (input) => {
3323
3519
  let content;
3324
3520
  try {
3325
- content = await readFile14(join20(outputDir, "scenarios.md"), "utf-8");
3521
+ content = await readFile15(join21(outputDir, "scenarios.md"), "utf-8");
3326
3522
  } catch {
3327
3523
  return { error: "Cannot finish: scenarios.md not found. Write it first." };
3328
3524
  }
@@ -3422,9 +3618,9 @@ When done with changes, call finish again.`;
3422
3618
  }
3423
3619
  });
3424
3620
  if (!reviewed) {
3425
- const scenariosPath = join20(input.outputDir, "scenarios.md");
3621
+ const scenariosPath = join21(input.outputDir, "scenarios.md");
3426
3622
  try {
3427
- await readFile14(scenariosPath, "utf-8");
3623
+ await readFile15(scenariosPath, "utf-8");
3428
3624
  return {
3429
3625
  success: true,
3430
3626
  artifacts: ["scenarios.md"],
@@ -3581,11 +3777,11 @@ var init_entity_relevance = __esm({
3581
3777
 
3582
3778
  // src/core/detect-pkg-manager.ts
3583
3779
  import { existsSync as existsSync2 } from "fs";
3584
- import { join as join21 } from "path";
3780
+ import { join as join22 } from "path";
3585
3781
  function detectPackageManager(projectRoot) {
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";
3782
+ if (existsSync2(join22(projectRoot, "bun.lock")) || existsSync2(join22(projectRoot, "bun.lockb"))) return "bun";
3783
+ if (existsSync2(join22(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
3784
+ if (existsSync2(join22(projectRoot, "yarn.lock"))) return "yarn";
3589
3785
  return "npm";
3590
3786
  }
3591
3787
  function installCommand(pm, ...packages) {
@@ -3660,22 +3856,6 @@ var init_highlight = __esm({
3660
3856
  }
3661
3857
  });
3662
3858
 
3663
- // src/core/to-record.ts
3664
- function toRecord(value) {
3665
- if (typeof value !== "object" || value === null) return {};
3666
- const record = {};
3667
- for (const [key, entry] of Object.entries(value)) {
3668
- record[key] = entry;
3669
- }
3670
- return record;
3671
- }
3672
- var init_to_record = __esm({
3673
- "src/core/to-record.ts"() {
3674
- "use strict";
3675
- init_esm_shims();
3676
- }
3677
- });
3678
-
3679
3859
  // src/agents/04-recipe-builder/http-client.ts
3680
3860
  import { createHmac } from "crypto";
3681
3861
  function sign(body, secret) {
@@ -3811,7 +3991,7 @@ function validateRecipeAgainstSchema(recipe, schema) {
3811
3991
  }
3812
3992
  }
3813
3993
  const refs = /* @__PURE__ */ new Set();
3814
- collectRefs(record, refs);
3994
+ collectRefs2(record, refs);
3815
3995
  for (const alias of refs) {
3816
3996
  if (!declaredAliases.has(alias)) {
3817
3997
  problems.push({
@@ -3825,16 +4005,16 @@ function validateRecipeAgainstSchema(recipe, schema) {
3825
4005
  }
3826
4006
  return problems;
3827
4007
  }
3828
- function collectRefs(value, out) {
4008
+ function collectRefs2(value, out) {
3829
4009
  if (Array.isArray(value)) {
3830
- for (const v of value) collectRefs(v, out);
4010
+ for (const v of value) collectRefs2(v, out);
3831
4011
  } else if (value !== null && typeof value === "object") {
3832
4012
  const obj = toRecord(value);
3833
4013
  if (typeof obj._ref === "string") {
3834
4014
  out.add(obj._ref);
3835
4015
  return;
3836
4016
  }
3837
- for (const v of Object.values(obj)) collectRefs(v, out);
4017
+ for (const v of Object.values(obj)) collectRefs2(v, out);
3838
4018
  }
3839
4019
  }
3840
4020
  function formatValidationProblems(problems) {
@@ -3855,93 +4035,6 @@ var init_discover_schema = __esm({
3855
4035
  }
3856
4036
  });
3857
4037
 
3858
- // src/agents/04-recipe-builder/recipe.ts
3859
- import { readFile as readFile15, writeFile as writeFile7 } from "fs/promises";
3860
- import { join as join22 } from "path";
3861
- function collectRefs2(value, out) {
3862
- if (Array.isArray(value)) {
3863
- for (const v of value) collectRefs2(v, out);
3864
- } else if (value !== null && typeof value === "object") {
3865
- const obj = toRecord(value);
3866
- if (typeof obj._ref === "string") out.add(obj._ref);
3867
- for (const v of Object.values(obj)) collectRefs2(v, out);
3868
- }
3869
- }
3870
- function buildSingleEntityRecipe(entityName, models, entityOrder, allEntities) {
3871
- const modelMap = new Map(models.map((m) => [m.name, m]));
3872
- const aliasOwner = /* @__PURE__ */ new Map();
3873
- for (const [name, entity] of Object.entries(allEntities)) {
3874
- for (const rec of entity?.recipeData ?? []) {
3875
- if (typeof rec._alias === "string") aliasOwner.set(rec._alias, name);
3876
- }
3877
- }
3878
- const recipe = {};
3879
- const done = /* @__PURE__ */ new Set();
3880
- const onStack = /* @__PURE__ */ new Set();
3881
- function include(name) {
3882
- if (done.has(name) || onStack.has(name)) return;
3883
- onStack.add(name);
3884
- const records = allEntities[name]?.recipeData ?? [];
3885
- for (const dep of modelMap.get(name)?.created_by ?? []) {
3886
- if (entityOrder.includes(dep.owner)) include(dep.owner);
3887
- }
3888
- const refs = /* @__PURE__ */ new Set();
3889
- collectRefs2(records, refs);
3890
- for (const alias of refs) {
3891
- const owner = aliasOwner.get(alias);
3892
- if (owner && owner !== name) include(owner);
3893
- }
3894
- onStack.delete(name);
3895
- done.add(name);
3896
- if (records.length > 0) recipe[name] = records;
3897
- }
3898
- include(entityName);
3899
- return recipe;
3900
- }
3901
- function buildFullRecipe(entityOrder, allEntities) {
3902
- const recipe = {};
3903
- for (const name of entityOrder) {
3904
- const entity = allEntities[name];
3905
- if (entity?.recipeData && entity.recipeData.length > 0) {
3906
- recipe[name] = entity.recipeData;
3907
- }
3908
- }
3909
- return recipe;
3910
- }
3911
- function buildSubmittableRecipe(create, description) {
3912
- return {
3913
- version: 1,
3914
- source: {
3915
- discoverPath: "discover.json",
3916
- scenariosPath: "scenarios.md"
3917
- },
3918
- validationMode: "endpoint-lifecycle",
3919
- recipes: [
3920
- {
3921
- name: "standard",
3922
- description,
3923
- create,
3924
- validation: {
3925
- status: "validated",
3926
- method: "endpoint-up-down"
3927
- }
3928
- }
3929
- ]
3930
- };
3931
- }
3932
- async function saveRecipe(outputDir, recipe) {
3933
- await writeFile7(join22(outputDir, RECIPE_FILE), JSON.stringify(recipe, null, 2), "utf-8");
3934
- }
3935
- var RECIPE_FILE;
3936
- var init_recipe = __esm({
3937
- "src/agents/04-recipe-builder/recipe.ts"() {
3938
- "use strict";
3939
- init_esm_shims();
3940
- init_to_record();
3941
- RECIPE_FILE = "recipe.json";
3942
- }
3943
- });
3944
-
3945
4038
  // src/agents/04-recipe-builder/state.ts
3946
4039
  import { readFile as readFile16, writeFile as writeFile8 } from "fs/promises";
3947
4040
  import { join as join23 } from "path";
@@ -4128,7 +4221,7 @@ So a failure has exactly two possible origins, and your only job is to tell them
4128
4221
  import { writeFile as writeFile9, readFile as readFile17 } from "fs/promises";
4129
4222
  import { tmpdir } from "os";
4130
4223
  import { join as join24 } from "path";
4131
- import * as p4 from "@clack/prompts";
4224
+ import * as p5 from "@clack/prompts";
4132
4225
  import { tool as tool14 } from "ai";
4133
4226
  import spawn2 from "cross-spawn";
4134
4227
  import { z as z19 } from "zod";
@@ -4244,10 +4337,10 @@ Read scenarios.md and entity-audit.md to understand the correct aliases and sche
4244
4337
  );
4245
4338
  logger.summary();
4246
4339
  if (revised) {
4247
- p4.note(JSON.stringify(revised, null, 2), `Fixed data for ${entityName}`, { format: codeNoteFormat });
4340
+ p5.note(JSON.stringify(revised, null, 2), `Fixed data for ${entityName}`, { format: codeNoteFormat });
4248
4341
  return revised;
4249
4342
  }
4250
- p4.log.warn("Could not auto-fix. Returning original data.");
4343
+ p5.log.warn("Could not auto-fix. Returning original data.");
4251
4344
  return current;
4252
4345
  }
4253
4346
  async function generateInstructions(entityName, entityIndex, totalEntities, isFirst, techStack, auditModel, recipeData, model, projectRoot, outputDir) {
@@ -4314,20 +4407,20 @@ Read the creation file from the project to understand the existing service/funct
4314
4407
  return result ?? "No instructions generated. Check the entity audit for creation_file and creation_function.";
4315
4408
  }
4316
4409
  async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed, model, outputDir, completedEntities, schemaSpec) {
4317
- p4.log.info(
4410
+ p5.log.info(
4318
4411
  `Legend for recipe fields:
4319
4412
  _alias - Internal ID used to reference this record from other entities (e.g., { "_ref": "org_1" })
4320
4413
  _ref - Reference to a record created by a parent entity's _alias
4321
4414
  All other fields are the actual data that will be inserted into your database.`
4322
4415
  );
4323
- p4.note(JSON.stringify(proposed, null, 2), `Proposed data for ${entityName} (${proposed.length} records)`, {
4416
+ p5.note(JSON.stringify(proposed, null, 2), `Proposed data for ${entityName} (${proposed.length} records)`, {
4324
4417
  format: codeNoteFormat
4325
4418
  });
4326
- p4.log.info(
4419
+ p5.log.info(
4327
4420
  "Review checklist:\n - Do field values match your real data patterns?\n - Are _ref references pointing to correct parent aliases?\n - Are enum fields varied across records (not all the same value)?\n - Are there enough records for your test scenarios?"
4328
4421
  );
4329
4422
  while (true) {
4330
- const action = await p4.select({
4423
+ const action = await p5.select({
4331
4424
  message: `[${entityIndex + 1}/${totalEntities}] ${entityName} - does this data look right?`,
4332
4425
  options: [
4333
4426
  { value: "keep", label: "Yes, keep" },
@@ -4335,19 +4428,19 @@ async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed
4335
4428
  { value: "edit", label: "No, edit manually" }
4336
4429
  ]
4337
4430
  });
4338
- if (p4.isCancel(action)) throw new Error("Recipe review cancelled");
4431
+ if (p5.isCancel(action)) throw new Error("Recipe review cancelled");
4339
4432
  if (action === "keep") return proposed;
4340
4433
  if (action === "edit") {
4341
4434
  const tmpPath = join24(tmpdir(), `autonoma-recipe-${entityName}.json`);
4342
4435
  await writeFile9(tmpPath, JSON.stringify(proposed, null, 2), "utf-8");
4343
4436
  const env = readEnv();
4344
4437
  const editor = env.EDITOR ?? env.VISUAL ?? "vi";
4345
- p4.log.info(`Opening ${editor}... Save and close when done.`);
4438
+ p5.log.info(`Opening ${editor}... Save and close when done.`);
4346
4439
  const launched = await new Promise((resolve6) => {
4347
4440
  const proc = spawn2(editor, [tmpPath], { stdio: "inherit" });
4348
4441
  proc.on("close", () => resolve6(true));
4349
4442
  proc.on("error", (err) => {
4350
- p4.log.error(
4443
+ p5.log.error(
4351
4444
  `Couldn't open ${editor} (${err.message}). Edit this file manually, then choose "edit" again: ${tmpPath}`
4352
4445
  );
4353
4446
  resolve6(false);
@@ -4357,18 +4450,18 @@ async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed
4357
4450
  const edited = await readFile17(tmpPath, "utf-8");
4358
4451
  try {
4359
4452
  proposed = JSON.parse(edited);
4360
- p4.note(JSON.stringify(proposed, null, 2), `Updated data for ${entityName}`, { format: codeNoteFormat });
4453
+ p5.note(JSON.stringify(proposed, null, 2), `Updated data for ${entityName}`, { format: codeNoteFormat });
4361
4454
  } catch (err) {
4362
- p4.log.error(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}. Try again.`);
4455
+ p5.log.error(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}. Try again.`);
4363
4456
  }
4364
4457
  continue;
4365
4458
  }
4366
4459
  if (action === "chat") {
4367
- const feedback = await p4.text({
4460
+ const feedback = await p5.text({
4368
4461
  message: "What should be changed?",
4369
4462
  placeholder: "e.g., add more records, change field values, fix references..."
4370
4463
  });
4371
- if (p4.isCancel(feedback) || !feedback.trim()) continue;
4464
+ if (p5.isCancel(feedback) || !feedback.trim()) continue;
4372
4465
  proposed = await reviseRecipeData(
4373
4466
  entityName,
4374
4467
  entityIndex,
@@ -4408,14 +4501,14 @@ async function promptOnFailure(entityName, errorBody, ctx, phase, httpStatus) {
4408
4501
  });
4409
4502
  if (ctx.budget.attempts < MAX_AUTOFIX_ATTEMPTS) {
4410
4503
  ctx.budget.attempts++;
4411
- p4.log.info(`Triage: ${reason}`);
4412
- p4.log.info(
4504
+ p5.log.info(`Triage: ${reason}`);
4505
+ p5.log.info(
4413
4506
  `Handing the failure to the agent to fix from the error (attempt ${ctx.budget.attempts}/${MAX_AUTOFIX_ATTEMPTS})...`
4414
4507
  );
4415
4508
  return seedFeedbackFromError(errorContext, reason);
4416
4509
  }
4417
- p4.log.warn(`The agent tried ${MAX_AUTOFIX_ATTEMPTS}\xD7 without resolving it. Latest triage: ${reason}`);
4418
- const action = await p4.select({
4510
+ p5.log.warn(`The agent tried ${MAX_AUTOFIX_ATTEMPTS}\xD7 without resolving it. Latest triage: ${reason}`);
4511
+ const action = await p5.select({
4419
4512
  message: "What would you like to do?",
4420
4513
  options: [
4421
4514
  { value: "retry", label: "Yes, retry - I fixed my handler code", hint: "Send the same request again" },
@@ -4432,22 +4525,22 @@ async function promptOnFailure(entityName, errorBody, ctx, phase, httpStatus) {
4432
4525
  { value: "skip", label: "No, skip this entity", hint: "Move on to the next entity" }
4433
4526
  ]
4434
4527
  });
4435
- if (p4.isCancel(action)) throw new Error("Entity loop cancelled");
4528
+ if (p5.isCancel(action)) throw new Error("Entity loop cancelled");
4436
4529
  if (action === "skip") return "skip";
4437
4530
  if (action === "retry") return "retry";
4438
4531
  if (action === "autofix") {
4439
4532
  ctx.budget.attempts++;
4440
4533
  return seedFeedbackFromError(errorContext, reason);
4441
4534
  }
4442
- const fb = await p4.text({
4535
+ const fb = await p5.text({
4443
4536
  message: "What's wrong with the recipe data?",
4444
4537
  placeholder: "e.g. Transaction references acc_1 but Account uses account_1 as its alias"
4445
4538
  });
4446
- if (p4.isCancel(fb)) throw new Error("Entity loop cancelled");
4539
+ if (p5.isCancel(fb)) throw new Error("Entity loop cancelled");
4447
4540
  return { feedback: `${fb.trim()}${errorContext}` };
4448
4541
  }
4449
4542
  async function testUpDown(entityName, entityIndex, totalEntities, sdkConfig, recipe, grounding, discoverSchema) {
4450
- p4.log.info(
4543
+ p5.log.info(
4451
4544
  `Let's verify this factory works. We'll send a test request to create ${entityName}, then check the database.`
4452
4545
  );
4453
4546
  const failureCtx = { ...grounding, recipe };
@@ -4457,7 +4550,7 @@ async function testUpDown(entityName, entityIndex, totalEntities, sdkConfig, rec
4457
4550
  if (problems.length > 0) {
4458
4551
  const errorBody = `Recipe failed local schema validation against /discover (not sent to the server):
4459
4552
  ${formatValidationProblems(problems)}`;
4460
- p4.log.error(errorBody);
4553
+ p5.log.error(errorBody);
4461
4554
  const action = await promptOnFailure(entityName, errorBody, failureCtx, "create");
4462
4555
  if (action === "skip") return "skip";
4463
4556
  if (action === "retry") continue;
@@ -4465,12 +4558,12 @@ ${formatValidationProblems(problems)}`;
4465
4558
  }
4466
4559
  }
4467
4560
  const testRunId = `test-${Date.now()}`;
4468
- p4.log.step(`[${entityIndex + 1}/${totalEntities}] Sending UP request...`);
4561
+ p5.log.step(`[${entityIndex + 1}/${totalEntities}] Sending UP request...`);
4469
4562
  let upResult;
4470
4563
  try {
4471
4564
  upResult = await up(sdkConfig, recipe, testRunId);
4472
4565
  } catch (err) {
4473
- p4.log.error(`UP request failed:
4566
+ p5.log.error(`UP request failed:
4474
4567
  ${formatException(err)}`);
4475
4568
  const action = await promptOnFailure(entityName, formatException(err), failureCtx, "create");
4476
4569
  if (action === "skip") return "skip";
@@ -4478,29 +4571,29 @@ ${formatException(err)}`);
4478
4571
  return action;
4479
4572
  }
4480
4573
  if (!upResult.ok) {
4481
- p4.log.error(`UP failed (HTTP ${upResult.status}):`);
4574
+ p5.log.error(`UP failed (HTTP ${upResult.status}):`);
4482
4575
  console.log(JSON.stringify(upResult.body, null, 2));
4483
4576
  const action = await promptOnFailure(entityName, upResult.body, failureCtx, "create", upResult.status);
4484
4577
  if (action === "skip") return "skip";
4485
4578
  if (action === "retry") continue;
4486
4579
  return action;
4487
4580
  }
4488
- p4.log.success(`UP succeeded!`);
4581
+ p5.log.success(`UP succeeded!`);
4489
4582
  console.log(JSON.stringify(upResult.body, null, 2));
4490
4583
  const refsTokenValue = toRecord(upResult.body).refsToken;
4491
4584
  const refsToken = typeof refsTokenValue === "string" ? refsTokenValue : void 0;
4492
4585
  if (!refsToken) {
4493
- p4.log.error("No refsToken in UP response - cannot test DOWN.");
4586
+ p5.log.error("No refsToken in UP response - cannot test DOWN.");
4494
4587
  return "skip";
4495
4588
  }
4496
- p4.log.info("Now let's verify teardown works - leftover test data would pollute your database.");
4589
+ p5.log.info("Now let's verify teardown works - leftover test data would pollute your database.");
4497
4590
  while (true) {
4498
- p4.log.step(`[${entityIndex + 1}/${totalEntities}] Sending DOWN request...`);
4591
+ p5.log.step(`[${entityIndex + 1}/${totalEntities}] Sending DOWN request...`);
4499
4592
  let downResult;
4500
4593
  try {
4501
4594
  downResult = await down(sdkConfig, refsToken);
4502
4595
  } catch (err) {
4503
- p4.log.error(`DOWN request failed:
4596
+ p5.log.error(`DOWN request failed:
4504
4597
  ${formatException(err)}`);
4505
4598
  const action = await promptOnFailure(entityName, formatException(err), failureCtx, "teardown");
4506
4599
  if (action === "skip") return "skip";
@@ -4508,7 +4601,7 @@ ${formatException(err)}`);
4508
4601
  return action;
4509
4602
  }
4510
4603
  if (!downResult.ok) {
4511
- p4.log.error(`DOWN failed (HTTP ${downResult.status}):`);
4604
+ p5.log.error(`DOWN failed (HTTP ${downResult.status}):`);
4512
4605
  console.log(JSON.stringify(downResult.body, null, 2));
4513
4606
  const action = await promptOnFailure(
4514
4607
  entityName,
@@ -4521,7 +4614,7 @@ ${formatException(err)}`);
4521
4614
  if (action === "retry") continue;
4522
4615
  return action;
4523
4616
  }
4524
- p4.log.success("DOWN succeeded!");
4617
+ p5.log.success("DOWN succeeded!");
4525
4618
  return "success";
4526
4619
  }
4527
4620
  }
@@ -4538,7 +4631,7 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
4538
4631
  if (!schema) return {};
4539
4632
  return { schema, spec: renderModelSchema(schema, name) ?? void 0 };
4540
4633
  }
4541
- p4.log.info(
4634
+ p5.log.info(
4542
4635
  `We're going to set up your test data factories one entity at a time. Each factory teaches the Autonoma SDK how to create and tear down a specific type of record in YOUR database, using YOUR existing service functions.
4543
4636
 
4544
4637
  We'll test each one live before moving on - this way if something breaks, you'll know exactly which entity caused it. Let's start with the root entities (no dependencies), then work through the dependents.`
@@ -4547,7 +4640,7 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
4547
4640
  const entityName = state.entityOrder[i];
4548
4641
  const auditModel = modelMap.get(entityName);
4549
4642
  if (!auditModel) {
4550
- p4.log.warn(`[${i + 1}/${total}] ${entityName} - not found in entity audit, skipping`);
4643
+ p5.log.warn(`[${i + 1}/${total}] ${entityName} - not found in entity audit, skipping`);
4551
4644
  state.entities[entityName] = {
4552
4645
  entityName,
4553
4646
  status: "skipped",
@@ -4559,13 +4652,13 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
4559
4652
  }
4560
4653
  const existing = state.entities[entityName];
4561
4654
  if (existing?.status === "tested-down") {
4562
- p4.log.info(`[${i + 1}/${total}] ${entityName} - already done, skipping`);
4655
+ p5.log.info(`[${i + 1}/${total}] ${entityName} - already done, skipping`);
4563
4656
  continue;
4564
4657
  }
4565
4658
  const isRoot = auditModel.created_by.length === 0;
4566
4659
  const depInfo = isRoot ? "This is a root entity - no dependencies." : `This depends on: ${auditModel.created_by.map((d) => d.owner).join(", ")}`;
4567
- p4.log.step(`[${i + 1}/${total}] ${entityName}`);
4568
- p4.log.info(depInfo);
4660
+ p5.log.step(`[${i + 1}/${total}] ${entityName}`);
4661
+ p5.log.info(depInfo);
4569
4662
  const { spec: recipeSchemaSpec } = await loadLiveSchema(entityName);
4570
4663
  let recipeData = existing?.recipeData;
4571
4664
  if (!recipeData || existing?.status === "pending") {
@@ -4614,28 +4707,28 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
4614
4707
  outputDir
4615
4708
  );
4616
4709
  const DOCS_BASE2 = "https://docs.autonoma.app";
4617
- p4.log.info(
4710
+ p5.log.info(
4618
4711
  `Next: implement the ${entityName} factory. The block below is a copy-paste guide -
4619
4712
  paste it into Claude Code (or your AI assistant) and it will write the factory in your codebase.
4620
4713
  A factory teaches the Autonoma SDK how to create and tear down ${entityName} records using your app's own code.
4621
4714
  Keep it local for now: implement it, run your app on localhost, and we'll test it live here. You deploy later.`
4622
4715
  );
4623
- p4.note(instructions, `Implementation guide for ${entityName} (paste into your AI assistant)`, {
4716
+ p5.note(instructions, `Implementation guide for ${entityName} (paste into your AI assistant)`, {
4624
4717
  format: codeNoteFormat
4625
4718
  });
4626
- p4.log.info(`Autonoma SDK docs: ${DOCS_BASE2}/sdk/environment-factory`);
4719
+ p5.log.info(`Autonoma SDK docs: ${DOCS_BASE2}/sdk/environment-factory`);
4627
4720
  if (i === 0) {
4628
- p4.log.info(
4721
+ p5.log.info(
4629
4722
  "This is your first factory - the guide includes one-time SDK setup. Later entities only need the factory function."
4630
4723
  );
4631
4724
  }
4632
4725
  notify("Autonoma", `${entityName} - implementation ready, waiting for you`);
4633
- const ready = await p4.confirm({
4726
+ const ready = await p5.confirm({
4634
4727
  message: `[${i + 1}/${total}] Is your app running locally with the ${entityName} factory wired up?`
4635
4728
  });
4636
- if (p4.isCancel(ready)) throw new Error("Entity loop cancelled");
4729
+ if (p5.isCancel(ready)) throw new Error("Entity loop cancelled");
4637
4730
  if (!ready) {
4638
- p4.log.info("Take your time implementing. Run again with --resume to continue from here.");
4731
+ p5.log.info("Take your time implementing. Run again with --resume to continue from here.");
4639
4732
  return;
4640
4733
  }
4641
4734
  }
@@ -4651,7 +4744,7 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
4651
4744
  JSON.stringify({ sharedSecret: secret, endpointUrl: state.sdkEndpointUrl }, null, 2),
4652
4745
  "utf-8"
4653
4746
  );
4654
- p4.note(
4747
+ p5.note(
4655
4748
  `AUTONOMA_SHARED_SECRET=${secret}
4656
4749
 
4657
4750
  Add this to your server's .env file and restart it.
@@ -4661,22 +4754,22 @@ The same value must be set in both your server and the Autonoma dashboard.
4661
4754
  Saved to: ${join24(outputDir, "autonoma-config.json")}`,
4662
4755
  "Shared secret generated"
4663
4756
  );
4664
- const secretReady = await p4.confirm({
4757
+ const secretReady = await p5.confirm({
4665
4758
  message: "Did you add the secret to your .env and restart the server?"
4666
4759
  });
4667
- if (p4.isCancel(secretReady)) throw new Error("Entity loop cancelled");
4760
+ if (p5.isCancel(secretReady)) throw new Error("Entity loop cancelled");
4668
4761
  if (!secretReady) {
4669
- p4.log.info("Add the secret and run again with --resume to continue.");
4762
+ p5.log.info("Add the secret and run again with --resume to continue.");
4670
4763
  return;
4671
4764
  }
4672
4765
  }
4673
4766
  if (!state.sdkEndpointUrl) {
4674
- const url = await p4.text({
4767
+ const url = await p5.text({
4675
4768
  message: "What's your SDK endpoint URL?",
4676
4769
  placeholder: "http://localhost:3000/api/autonoma",
4677
4770
  defaultValue: "http://localhost:3000/api/autonoma"
4678
4771
  });
4679
- if (p4.isCancel(url)) throw new Error("Entity loop cancelled");
4772
+ if (p5.isCancel(url)) throw new Error("Entity loop cancelled");
4680
4773
  state.sdkEndpointUrl = url.trim() || "http://localhost:3000/api/autonoma";
4681
4774
  await saveRecipeState(outputDir, state);
4682
4775
  await writeFile9(
@@ -4712,15 +4805,15 @@ Saved to: ${join24(outputDir, "autonoma-config.json")}`,
4712
4805
  );
4713
4806
  if (testResult === "success") {
4714
4807
  state.entities[entityName].status = "tested-down";
4715
- p4.log.success(`[${i + 1}/${total}] ${entityName} - factory verified`);
4808
+ p5.log.success(`[${i + 1}/${total}] ${entityName} - factory verified`);
4716
4809
  testDone = true;
4717
4810
  } else if (testResult === "skip") {
4718
4811
  state.entities[entityName].status = "skipped";
4719
4812
  state.entities[entityName].errorLog.push("UP/DOWN test skipped by user");
4720
- p4.log.warn(`[${i + 1}/${total}] ${entityName} - skipped, continuing to next entity`);
4813
+ p5.log.warn(`[${i + 1}/${total}] ${entityName} - skipped, continuing to next entity`);
4721
4814
  testDone = true;
4722
4815
  } else {
4723
- p4.log.info(`Re-generating recipe data for ${entityName} based on your feedback...`);
4816
+ p5.log.info(`Re-generating recipe data for ${entityName} based on your feedback...`);
4724
4817
  const revised = await reviseRecipeData(
4725
4818
  entityName,
4726
4819
  i,
@@ -4795,7 +4888,7 @@ When done, call finish with the instructions text.`;
4795
4888
  });
4796
4889
 
4797
4890
  // src/agents/04-recipe-builder/phases/full-validation.ts
4798
- import * as p5 from "@clack/prompts";
4891
+ import * as p6 from "@clack/prompts";
4799
4892
  import { tool as tool15 } from "ai";
4800
4893
  import { z as z20 } from "zod";
4801
4894
  async function reviseFullRecipe(current, feedback, model, outputDir, entityOrder, schemaSpec) {
@@ -4854,35 +4947,35 @@ Revise the recipe to address the feedback, then call finish with the complete up
4854
4947
  }
4855
4948
  async function teardown(sdkConfig, refsToken, successMessage) {
4856
4949
  if (!refsToken) return true;
4857
- p5.log.step("[Full validation] Tearing down all entities...");
4950
+ p6.log.step("[Full validation] Tearing down all entities...");
4858
4951
  let downResult;
4859
4952
  try {
4860
4953
  downResult = await down(sdkConfig, refsToken);
4861
4954
  } catch (err) {
4862
- p5.log.error(`Full DOWN request failed:
4955
+ p6.log.error(`Full DOWN request failed:
4863
4956
  ${formatException(err)}`);
4864
4957
  return false;
4865
4958
  }
4866
4959
  if (!downResult.ok) {
4867
- p5.log.error(`Full DOWN failed (HTTP ${downResult.status}):`);
4960
+ p6.log.error(`Full DOWN failed (HTTP ${downResult.status}):`);
4868
4961
  console.log(JSON.stringify(downResult.body, null, 2));
4869
4962
  return false;
4870
4963
  }
4871
- p5.log.success(successMessage);
4964
+ p6.log.success(successMessage);
4872
4965
  return true;
4873
4966
  }
4874
4967
  async function runFullValidation(state, _models, outputDir, model) {
4875
4968
  const total = state.entityOrder.length;
4876
- p5.log.info(
4969
+ p6.log.info(
4877
4970
  `All individual factories work. Now let's create EVERYTHING together and verify the app looks right with a full dataset. This is the recipe that will run before every test execution.`
4878
4971
  );
4879
4972
  if (!state.sdkEndpointUrl) {
4880
- const url = await p5.text({
4973
+ const url = await p6.text({
4881
4974
  message: "What's your SDK endpoint URL?",
4882
4975
  placeholder: "http://localhost:3000/api/autonoma",
4883
4976
  defaultValue: "http://localhost:3000/api/autonoma"
4884
4977
  });
4885
- if (p5.isCancel(url)) throw new Error("Cancelled");
4978
+ if (p6.isCancel(url)) throw new Error("Cancelled");
4886
4979
  state.sdkEndpointUrl = url.trim() || "http://localhost:3000/api/autonoma";
4887
4980
  await saveRecipeState(outputDir, state);
4888
4981
  }
@@ -4897,69 +4990,69 @@ async function runFullValidation(state, _models, outputDir, model) {
4897
4990
  if (discoverSchema) {
4898
4991
  const problems = validateRecipeAgainstSchema(fullRecipe, discoverSchema);
4899
4992
  if (problems.length > 0) {
4900
- p5.log.warn(
4993
+ p6.log.warn(
4901
4994
  `Heads up - the recipe has likely schema problems (from /discover); the full UP may fail:
4902
4995
  ${formatValidationProblems(problems)}`
4903
4996
  );
4904
4997
  }
4905
4998
  }
4906
4999
  const testRunId = `full-${Date.now()}`;
4907
- p5.log.step(`[Full validation] Creating all ${total} entities...`);
5000
+ p6.log.step(`[Full validation] Creating all ${total} entities...`);
4908
5001
  let upResult;
4909
5002
  try {
4910
5003
  upResult = await up(sdkConfig, fullRecipe, testRunId);
4911
5004
  } catch (err) {
4912
- p5.log.error(`Full UP request failed:
5005
+ p6.log.error(`Full UP request failed:
4913
5006
  ${formatException(err)}`);
4914
5007
  notify("Autonoma", "Full validation UP failed, action needed");
4915
- const action = await p5.select({
5008
+ const action = await p6.select({
4916
5009
  message: "What would you like to do?",
4917
5010
  options: [
4918
5011
  { value: "retry", label: "Yes, retry - I fixed it", hint: "Send the request again" },
4919
5012
  { value: "skip", label: "No, skip full validation", hint: "Continue to test generation" }
4920
5013
  ]
4921
5014
  });
4922
- if (p5.isCancel(action)) throw new Error("Cancelled");
5015
+ if (p6.isCancel(action)) throw new Error("Cancelled");
4923
5016
  if (action === "skip") return false;
4924
5017
  continue;
4925
5018
  }
4926
5019
  if (!upResult.ok) {
4927
- p5.log.error(`Full UP failed (HTTP ${upResult.status}):`);
5020
+ p6.log.error(`Full UP failed (HTTP ${upResult.status}):`);
4928
5021
  console.log(JSON.stringify(upResult.body, null, 2));
4929
5022
  notify("Autonoma", "Full validation UP failed, action needed");
4930
- const action = await p5.select({
5023
+ const action = await p6.select({
4931
5024
  message: "What would you like to do?",
4932
5025
  options: [
4933
5026
  { value: "retry", label: "Yes, retry - I fixed it", hint: "Send the request again" },
4934
5027
  { value: "skip", label: "No, skip full validation", hint: "Continue to test generation" }
4935
5028
  ]
4936
5029
  });
4937
- if (p5.isCancel(action)) throw new Error("Cancelled");
5030
+ if (p6.isCancel(action)) throw new Error("Cancelled");
4938
5031
  if (action === "skip") return false;
4939
5032
  continue;
4940
5033
  }
4941
- p5.log.success("Full UP succeeded!");
5034
+ p6.log.success("Full UP succeeded!");
4942
5035
  const body = toRecord(upResult.body);
4943
5036
  const refsToken = typeof body.refsToken === "string" ? body.refsToken : void 0;
4944
5037
  const auth = body.auth != null && typeof body.auth === "object" ? toRecord(body.auth) : void 0;
4945
5038
  if (auth && Object.keys(auth).length > 0) {
4946
5039
  const authJson = JSON.stringify(auth, null, 2);
4947
5040
  const looksPlaceholder = authJson.includes("test-token") || authJson.includes("placeholder") || authJson.includes("todo");
4948
- p5.note(
5041
+ p6.note(
4949
5042
  authJson + "\n\nThese are the credentials your auth callback returns.\nThe test runner will use them to authenticate as the test user when executing tests." + (looksPlaceholder ? "\n\n\u26A0 This looks like a placeholder. Update your auth callback to return real credentials\n(a valid JWT, session cookie, or email/password) so the test runner can actually log in." : ""),
4950
5043
  "Auth credentials"
4951
5044
  );
4952
5045
  } else {
4953
- p5.log.warn(
5046
+ p6.log.warn(
4954
5047
  "No auth credentials returned. Your createHandler's auth callback must return credentials the test runner can use to log in (cookies, headers, or email/password). Without it, tests can't authenticate."
4955
5048
  );
4956
5049
  }
4957
- p5.log.info("Browse the app and check if the test data looks right.");
5050
+ p6.log.info("Browse the app and check if the test data looks right.");
4958
5051
  notify("Autonoma", "Full validation succeeded - review the app");
4959
- const looksGood = await p5.confirm({
5052
+ const looksGood = await p6.confirm({
4960
5053
  message: "Does the app look right with the test data?"
4961
5054
  });
4962
- if (p5.isCancel(looksGood)) throw new Error("Cancelled");
5055
+ if (p6.isCancel(looksGood)) throw new Error("Cancelled");
4963
5056
  const torndown = await teardown(
4964
5057
  sdkConfig,
4965
5058
  refsToken,
@@ -4967,15 +5060,15 @@ ${formatException(err)}`);
4967
5060
  );
4968
5061
  if (!torndown) return false;
4969
5062
  if (looksGood) return true;
4970
- const feedback = await p5.text({
5063
+ const feedback = await p6.text({
4971
5064
  message: "What's wrong with the test data? Describe what to change.",
4972
5065
  placeholder: "e.g. accounts need realistic balances, transactions should reference the right account..."
4973
5066
  });
4974
- if (p5.isCancel(feedback) || !feedback.trim()) {
4975
- p5.log.info("No feedback given. You can edit recipe.json manually and re-run with --resume.");
5067
+ if (p6.isCancel(feedback) || !feedback.trim()) {
5068
+ p6.log.info("No feedback given. You can edit recipe.json manually and re-run with --resume.");
4976
5069
  return false;
4977
5070
  }
4978
- p5.log.info("Revising the full recipe based on your feedback...");
5071
+ p6.log.info("Revising the full recipe based on your feedback...");
4979
5072
  const revised = await reviseFullRecipe(
4980
5073
  fullRecipe,
4981
5074
  feedback.trim(),
@@ -4985,7 +5078,7 @@ ${formatException(err)}`);
4985
5078
  fullSchemaSpec
4986
5079
  );
4987
5080
  if (!revised) {
4988
- p5.log.warn("Couldn't revise automatically. Edit recipe.json manually and re-run with --resume.");
5081
+ p6.log.warn("Couldn't revise automatically. Edit recipe.json manually and re-run with --resume.");
4989
5082
  return false;
4990
5083
  }
4991
5084
  for (const [name, records] of Object.entries(revised)) {
@@ -4995,7 +5088,7 @@ ${formatException(err)}`);
4995
5088
  }
4996
5089
  await saveRecipeState(outputDir, state);
4997
5090
  fullRecipe = buildFullRecipe(state.entityOrder, state.entities);
4998
- p5.note(JSON.stringify(fullRecipe, null, 2), "Revised recipe - re-running full validation", {
5091
+ p6.note(JSON.stringify(fullRecipe, null, 2), "Revised recipe - re-running full validation", {
4999
5092
  format: codeNoteFormat
5000
5093
  });
5001
5094
  }
@@ -5017,45 +5110,6 @@ var init_full_validation = __esm({
5017
5110
  }
5018
5111
  });
5019
5112
 
5020
- // src/agents/04-recipe-builder/phases/submit.ts
5021
- import * as p6 from "@clack/prompts";
5022
- async function runSubmit(state, outputDir, autonomaApiUrl, autonomaApiToken, autonomaGenerationId) {
5023
- const fullCreate = buildFullRecipe(state.entityOrder, state.entities);
5024
- const recipe = buildSubmittableRecipe(fullCreate, "Standard test scenario with realistic data");
5025
- await saveRecipe(outputDir, recipe);
5026
- p6.log.success("Recipe saved to recipe.json");
5027
- if (!autonomaApiUrl || !autonomaApiToken || !autonomaGenerationId) {
5028
- p6.log.info(
5029
- "Autonoma API credentials not configured - recipe saved locally. Submit manually or configure AUTONOMA_API_URL, AUTONOMA_API_TOKEN, AUTONOMA_GENERATION_ID."
5030
- );
5031
- return "recipe.json";
5032
- }
5033
- const url = `${autonomaApiUrl}/v1/setup/setups/${autonomaGenerationId}/scenario-recipe-versions`;
5034
- p6.log.step("Submitting recipe to Autonoma...");
5035
- const res = await fetch(url, {
5036
- method: "POST",
5037
- headers: {
5038
- "Content-Type": "application/json",
5039
- Authorization: `Bearer ${autonomaApiToken}`
5040
- },
5041
- body: JSON.stringify(recipe)
5042
- });
5043
- if (res.ok) {
5044
- p6.log.success(`Recipe submitted successfully (HTTP ${res.status})`);
5045
- } else {
5046
- const text6 = await res.text();
5047
- p6.log.error(`Recipe submission failed (HTTP ${res.status}): ${text6}`);
5048
- }
5049
- return "recipe.json";
5050
- }
5051
- var init_submit = __esm({
5052
- "src/agents/04-recipe-builder/phases/submit.ts"() {
5053
- "use strict";
5054
- init_esm_shims();
5055
- init_recipe();
5056
- }
5057
- });
5058
-
5059
5113
  // src/agents/04-recipe-builder/phases/tech-detect.ts
5060
5114
  import * as p7 from "@clack/prompts";
5061
5115
  import { tool as tool16 } from "ai";
@@ -5235,13 +5289,21 @@ async function runRecipeBuilder(input) {
5235
5289
  }
5236
5290
  if (state.phase === "submit") {
5237
5291
  const env = readEnv();
5238
- const recipePath = await runSubmit(
5292
+ const { recipePath, uploaded } = await runSubmit(
5239
5293
  state,
5240
5294
  input.outputDir,
5241
5295
  env.AUTONOMA_API_URL,
5242
5296
  env.AUTONOMA_API_TOKEN,
5243
5297
  env.AUTONOMA_GENERATION_ID
5244
5298
  );
5299
+ const uploadCredentialsPresent = env.AUTONOMA_API_URL != null && env.AUTONOMA_API_TOKEN != null && env.AUTONOMA_GENERATION_ID != null;
5300
+ if (uploadCredentialsPresent && !uploaded) {
5301
+ return {
5302
+ success: false,
5303
+ artifacts: [recipePath],
5304
+ summary: `Recipe was generated but not accepted by Autonoma. The recipe JSON was printed above - re-upload with \`npx @autonoma-ai/planner@latest upload\` (or run again with --resume).`
5305
+ };
5306
+ }
5245
5307
  state.phase = "done";
5246
5308
  await saveRecipeState(input.outputDir, state);
5247
5309
  return {
@@ -7179,6 +7241,7 @@ function ensureSupportedNode() {
7179
7241
  ensureSupportedNode();
7180
7242
 
7181
7243
  // src/index.ts
7244
+ init_submit();
7182
7245
  import { readFile as readFile23, writeFile as writeFile14 } from "fs/promises";
7183
7246
  import { join as join31 } from "path";
7184
7247
  import * as p9 from "@clack/prompts";
@@ -7186,16 +7249,16 @@ import * as p9 from "@clack/prompts";
7186
7249
  // src/config.ts
7187
7250
  init_esm_shims();
7188
7251
  import { readFileSync as readFileSync2 } from "fs";
7189
- import { resolve, join as join2 } from "path";
7252
+ import { resolve, join as join3 } from "path";
7190
7253
 
7191
7254
  // src/core/global-env.ts
7192
7255
  init_esm_shims();
7193
7256
  init_env();
7194
7257
  import { readFileSync } from "fs";
7195
7258
  import { homedir } from "os";
7196
- import { join } from "path";
7197
- var AUTONOMA_HOME = join(homedir(), ".autonoma");
7198
- var GLOBAL_ENV_PATH = join(AUTONOMA_HOME, ".env");
7259
+ import { join as join2 } from "path";
7260
+ var AUTONOMA_HOME = join2(homedir(), ".autonoma");
7261
+ var GLOBAL_ENV_PATH = join2(AUTONOMA_HOME, ".env");
7199
7262
  function parseEnvContent(content) {
7200
7263
  const out = {};
7201
7264
  for (const line of content.split("\n")) {
@@ -7231,7 +7294,7 @@ init_env();
7231
7294
  function loadProjectEnv(projectRoot) {
7232
7295
  let content;
7233
7296
  try {
7234
- content = readFileSync2(join2(projectRoot, ".env"), "utf-8");
7297
+ content = readFileSync2(join3(projectRoot, ".env"), "utf-8");
7235
7298
  } catch {
7236
7299
  return;
7237
7300
  }
@@ -7396,10 +7459,10 @@ init_model();
7396
7459
  init_esm_shims();
7397
7460
  import { mkdir } from "fs/promises";
7398
7461
  import { homedir as homedir3 } from "os";
7399
- import { join as join6 } from "path";
7400
- var AUTONOMA_HOME3 = join6(homedir3(), ".autonoma");
7462
+ import { join as join7 } from "path";
7463
+ var AUTONOMA_HOME3 = join7(homedir3(), ".autonoma");
7401
7464
  function getOutputDir(projectSlug) {
7402
- return join6(AUTONOMA_HOME3, projectSlug);
7465
+ return join7(AUTONOMA_HOME3, projectSlug);
7403
7466
  }
7404
7467
  async function ensureOutputDir(projectSlug) {
7405
7468
  const dir = getOutputDir(projectSlug);
@@ -7413,8 +7476,8 @@ init_env();
7413
7476
  // src/core/git.ts
7414
7477
  init_esm_shims();
7415
7478
  import { execFile } from "child_process";
7416
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
7417
- import { join as join7 } from "path";
7479
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
7480
+ import { join as join8 } from "path";
7418
7481
  import { promisify } from "util";
7419
7482
  var execFileAsync = promisify(execFile);
7420
7483
  var GIT_INFO_FILE = ".git-info.json";
@@ -7438,11 +7501,11 @@ async function readGitInfo(projectRoot) {
7438
7501
  };
7439
7502
  }
7440
7503
  async function saveGitInfo(outputDir, info) {
7441
- await writeFile2(join7(outputDir, GIT_INFO_FILE), JSON.stringify(info, null, 2), "utf-8");
7504
+ await writeFile3(join8(outputDir, GIT_INFO_FILE), JSON.stringify(info, null, 2), "utf-8");
7442
7505
  }
7443
7506
  async function loadGitInfo(outputDir) {
7444
7507
  try {
7445
- const raw = await readFile2(join7(outputDir, GIT_INFO_FILE), "utf-8");
7508
+ const raw = await readFile3(join8(outputDir, GIT_INFO_FILE), "utf-8");
7446
7509
  const parsed = JSON.parse(raw);
7447
7510
  if (typeof parsed === "object" && parsed != null && "sha" in parsed && typeof parsed.sha === "string") {
7448
7511
  const branch = "branch" in parsed && typeof parsed.branch === "string" ? parsed.branch : void 0;
@@ -7462,8 +7525,8 @@ init_project_map();
7462
7525
  // src/core/state.ts
7463
7526
  init_esm_shims();
7464
7527
  init_debug();
7465
- import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
7466
- import { join as join9 } from "path";
7528
+ import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
7529
+ import { join as join10 } from "path";
7467
7530
  import { z as z3 } from "zod";
7468
7531
  var StepStatusSchema = z3.enum(["pending", "running", "done", "failed", "paused"]);
7469
7532
  var PipelineStateSchema = z3.object({
@@ -7492,9 +7555,9 @@ function initialState() {
7492
7555
  };
7493
7556
  }
7494
7557
  async function loadState(outputDir) {
7495
- const path3 = join9(outputDir, STATE_FILE);
7558
+ const path3 = join10(outputDir, STATE_FILE);
7496
7559
  try {
7497
- const raw = await readFile4(path3, "utf-8");
7560
+ const raw = await readFile5(path3, "utf-8");
7498
7561
  return PipelineStateSchema.parse(JSON.parse(raw));
7499
7562
  } catch (err) {
7500
7563
  const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
@@ -7503,8 +7566,8 @@ async function loadState(outputDir) {
7503
7566
  }
7504
7567
  }
7505
7568
  async function saveState(outputDir, state) {
7506
- const path3 = join9(outputDir, STATE_FILE);
7507
- await writeFile4(path3, JSON.stringify(state, null, 2), "utf-8");
7569
+ const path3 = join10(outputDir, STATE_FILE);
7570
+ await writeFile5(path3, JSON.stringify(state, null, 2), "utf-8");
7508
7571
  }
7509
7572
  async function markStep(outputDir, state, step, status) {
7510
7573
  const updated = {
@@ -7530,16 +7593,16 @@ function nextPendingStep(state) {
7530
7593
  // src/core/upload.ts
7531
7594
  init_esm_shims();
7532
7595
  init_debug();
7533
- import { readFile as readFile5 } from "fs/promises";
7534
- import { basename, join as join10, relative } from "path";
7535
- import * as p from "@clack/prompts";
7596
+ import { readFile as readFile6 } from "fs/promises";
7597
+ import { basename, join as join11, relative } from "path";
7598
+ import * as p2 from "@clack/prompts";
7536
7599
  import { glob } from "glob";
7537
7600
  var ARTIFACT_FILES = ["AUTONOMA.md", "scenarios.md", "entity-audit.md"];
7538
7601
  async function readArtifacts(outputDir) {
7539
7602
  const files = [];
7540
7603
  for (const name of ARTIFACT_FILES) {
7541
7604
  try {
7542
- const content = await readFile5(join10(outputDir, name), "utf-8");
7605
+ const content = await readFile6(join11(outputDir, name), "utf-8");
7543
7606
  files.push({ name, content });
7544
7607
  } catch (err) {
7545
7608
  debugLog(`Artifact ${name} not on disk; skipping upload`, { err });
@@ -7548,13 +7611,13 @@ async function readArtifacts(outputDir) {
7548
7611
  return files;
7549
7612
  }
7550
7613
  async function readTestCases(outputDir) {
7551
- const testsDir = join10(outputDir, "qa-tests");
7614
+ const testsDir = join11(outputDir, "qa-tests");
7552
7615
  const matches = await glob("**/*.md", { cwd: testsDir, nodir: true });
7553
7616
  const files = [];
7554
7617
  for (const match of matches) {
7555
7618
  const name = basename(match);
7556
7619
  if (name === "INDEX.md") continue;
7557
- const content = await readFile5(join10(testsDir, match), "utf-8");
7620
+ const content = await readFile6(join11(testsDir, match), "utf-8");
7558
7621
  const folderPath = relative(".", match).split("/").slice(0, -1).join("/");
7559
7622
  files.push({ name, content, folder: folderPath.length > 0 ? folderPath : void 0 });
7560
7623
  }
@@ -7591,14 +7654,14 @@ async function patchJson(url, token, body) {
7591
7654
  async function uploadArtifacts(config, outputDir) {
7592
7655
  const { autonomaApiUrl, autonomaApiToken, autonomaGenerationId } = config;
7593
7656
  if (autonomaApiUrl == null || autonomaApiToken == null || autonomaGenerationId == null) {
7594
- p.log.info(
7657
+ p2.log.info(
7595
7658
  `Autonoma upload credentials not configured - artifacts saved locally only. They live in ${outputDir}.`
7596
7659
  );
7597
7660
  return;
7598
7661
  }
7599
7662
  const baseUrl = autonomaApiUrl.replace(/\/+$/, "");
7600
7663
  const setupUrl = `${baseUrl}/v1/setup/setups/${autonomaGenerationId}`;
7601
- p.log.step("Uploading artifacts to Autonoma...");
7664
+ p2.log.step("Uploading artifacts to Autonoma...");
7602
7665
  const [testCases, artifacts, gitInfo] = await Promise.all([
7603
7666
  readTestCases(outputDir),
7604
7667
  readArtifacts(outputDir),
@@ -7606,7 +7669,7 @@ async function uploadArtifacts(config, outputDir) {
7606
7669
  ]);
7607
7670
  await postJson(`${setupUrl}/artifacts`, autonomaApiToken, { testCases, artifacts, commitSha: gitInfo?.sha });
7608
7671
  await patchJson(setupUrl, autonomaApiToken, { status: "completed" });
7609
- p.log.success(
7672
+ p2.log.success(
7610
7673
  `Uploaded ${testCases.length} test case${testCases.length === 1 ? "" : "s"} and ${artifacts.length} artifact${artifacts.length === 1 ? "" : "s"}. Return to your browser to continue onboarding.`
7611
7674
  );
7612
7675
  }
@@ -7974,12 +8037,28 @@ async function main() {
7974
8037
  await showStatus(outputDir2);
7975
8038
  return;
7976
8039
  }
8040
+ if (command === "upload") {
8041
+ const config2 = loadConfig({
8042
+ project: strArg(args, "project"),
8043
+ slug: strArg(args, "slug")
8044
+ });
8045
+ const outputDir2 = await ensureOutputDir(config2.projectSlug);
8046
+ const recipeUploaded = await uploadRecipeFromDisk(outputDir2, {
8047
+ apiUrl: config2.autonomaApiUrl,
8048
+ apiToken: config2.autonomaApiToken,
8049
+ generationId: config2.autonomaGenerationId
8050
+ });
8051
+ await uploadArtifacts(config2, outputDir2);
8052
+ await flushAnalytics();
8053
+ process.exit(recipeUploaded ? 0 : 1);
8054
+ }
7977
8055
  if (command === "help" || args.help) {
7978
8056
  console.log("Usage:");
7979
8057
  console.log(
7980
8058
  " test-planner [run] [--project <path>] [--frontend <path>] [--backends <path,path>] [--model <id>] [--step <name>] [--resume] [--non-interactive]"
7981
8059
  );
7982
8060
  console.log(" test-planner status [--project <path>]");
8061
+ console.log(" test-planner upload [--project <path>] # re-upload already-generated recipe + artifacts");
7983
8062
  console.log("");
7984
8063
  console.log("`run` is the default command; it may be omitted.");
7985
8064
  return;