@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/README.md +8 -0
- package/dist/index.js +415 -336
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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(
|
|
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
|
|
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 =
|
|
213
|
-
DEVICE_ID_PATH =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
497
|
-
import { join as
|
|
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
|
|
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 =
|
|
699
|
+
const path3 = join9(outputDir, PROJECT_MAP_FILE);
|
|
504
700
|
try {
|
|
505
|
-
const raw = await
|
|
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
|
|
915
|
-
import { join as
|
|
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 =
|
|
1131
|
+
const fullPath = join12(projectRoot, match);
|
|
936
1132
|
try {
|
|
937
|
-
const content = await
|
|
938
|
-
const prefix = relative2(projectRoot,
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 :
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1522
|
-
if (
|
|
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
|
|
1710
|
-
import * as
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1956
|
+
const open = await p4.confirm({
|
|
1761
1957
|
message: `Open in ${editor2.label}?`
|
|
1762
1958
|
});
|
|
1763
|
-
if (!
|
|
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
|
|
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 (
|
|
1973
|
+
if (p4.isCancel(selected) || selected === "skip") return;
|
|
1778
1974
|
const editor = editors.find((e) => e.command === selected);
|
|
1779
|
-
const remember = await
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1846
|
-
|
|
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
|
-
|
|
2047
|
+
p4.log.success("Approved - moving on.");
|
|
1852
2048
|
return result;
|
|
1853
2049
|
}
|
|
1854
|
-
|
|
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
|
|
1887
|
-
import { join as
|
|
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
|
|
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
|
|
2106
|
-
import { join as
|
|
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 =
|
|
2235
|
-
const autonomaExists = await
|
|
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
|
|
2355
|
-
import { join as
|
|
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
|
|
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
|
|
2746
|
-
import { join as
|
|
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
|
|
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 =
|
|
2965
|
-
await
|
|
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 =
|
|
3198
|
+
const auditPath = join18(input.outputDir, "entity-audit.md");
|
|
3003
3199
|
try {
|
|
3004
|
-
await
|
|
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
|
|
3147
|
-
import { join as
|
|
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
|
|
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
|
|
3224
|
-
import { join as
|
|
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
|
|
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
|
|
3311
|
-
import { join as
|
|
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
|
|
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 =
|
|
3621
|
+
const scenariosPath = join21(input.outputDir, "scenarios.md");
|
|
3426
3622
|
try {
|
|
3427
|
-
await
|
|
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
|
|
3780
|
+
import { join as join22 } from "path";
|
|
3585
3781
|
function detectPackageManager(projectRoot) {
|
|
3586
|
-
if (existsSync2(
|
|
3587
|
-
if (existsSync2(
|
|
3588
|
-
if (existsSync2(
|
|
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
|
-
|
|
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
|
|
4008
|
+
function collectRefs2(value, out) {
|
|
3829
4009
|
if (Array.isArray(value)) {
|
|
3830
|
-
for (const v of value)
|
|
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))
|
|
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
|
|
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
|
-
|
|
4340
|
+
p5.note(JSON.stringify(revised, null, 2), `Fixed data for ${entityName}`, { format: codeNoteFormat });
|
|
4248
4341
|
return revised;
|
|
4249
4342
|
}
|
|
4250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4416
|
+
p5.note(JSON.stringify(proposed, null, 2), `Proposed data for ${entityName} (${proposed.length} records)`, {
|
|
4324
4417
|
format: codeNoteFormat
|
|
4325
4418
|
});
|
|
4326
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4453
|
+
p5.note(JSON.stringify(proposed, null, 2), `Updated data for ${entityName}`, { format: codeNoteFormat });
|
|
4361
4454
|
} catch (err) {
|
|
4362
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
4412
|
-
|
|
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
|
-
|
|
4418
|
-
const action = await
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4586
|
+
p5.log.error("No refsToken in UP response - cannot test DOWN.");
|
|
4494
4587
|
return "skip";
|
|
4495
4588
|
}
|
|
4496
|
-
|
|
4589
|
+
p5.log.info("Now let's verify teardown works - leftover test data would pollute your database.");
|
|
4497
4590
|
while (true) {
|
|
4498
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4568
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4716
|
+
p5.note(instructions, `Implementation guide for ${entityName} (paste into your AI assistant)`, {
|
|
4624
4717
|
format: codeNoteFormat
|
|
4625
4718
|
});
|
|
4626
|
-
|
|
4719
|
+
p5.log.info(`Autonoma SDK docs: ${DOCS_BASE2}/sdk/environment-factory`);
|
|
4627
4720
|
if (i === 0) {
|
|
4628
|
-
|
|
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
|
|
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 (
|
|
4729
|
+
if (p5.isCancel(ready)) throw new Error("Entity loop cancelled");
|
|
4637
4730
|
if (!ready) {
|
|
4638
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
4760
|
+
if (p5.isCancel(secretReady)) throw new Error("Entity loop cancelled");
|
|
4668
4761
|
if (!secretReady) {
|
|
4669
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
4813
|
+
p5.log.warn(`[${i + 1}/${total}] ${entityName} - skipped, continuing to next entity`);
|
|
4721
4814
|
testDone = true;
|
|
4722
4815
|
} else {
|
|
4723
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4955
|
+
p6.log.error(`Full DOWN request failed:
|
|
4863
4956
|
${formatException(err)}`);
|
|
4864
4957
|
return false;
|
|
4865
4958
|
}
|
|
4866
4959
|
if (!downResult.ok) {
|
|
4867
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 (
|
|
5030
|
+
if (p6.isCancel(action)) throw new Error("Cancelled");
|
|
4938
5031
|
if (action === "skip") return false;
|
|
4939
5032
|
continue;
|
|
4940
5033
|
}
|
|
4941
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5052
|
+
const looksGood = await p6.confirm({
|
|
4960
5053
|
message: "Does the app look right with the test data?"
|
|
4961
5054
|
});
|
|
4962
|
-
if (
|
|
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
|
|
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 (
|
|
4975
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
7198
|
-
var GLOBAL_ENV_PATH =
|
|
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(
|
|
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
|
|
7400
|
-
var AUTONOMA_HOME3 =
|
|
7462
|
+
import { join as join7 } from "path";
|
|
7463
|
+
var AUTONOMA_HOME3 = join7(homedir3(), ".autonoma");
|
|
7401
7464
|
function getOutputDir(projectSlug) {
|
|
7402
|
-
return
|
|
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
|
|
7417
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
7466
|
-
import { join as
|
|
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 =
|
|
7558
|
+
const path3 = join10(outputDir, STATE_FILE);
|
|
7496
7559
|
try {
|
|
7497
|
-
const raw = await
|
|
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 =
|
|
7507
|
-
await
|
|
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
|
|
7534
|
-
import { basename, join as
|
|
7535
|
-
import * as
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|