@autonoma-ai/planner 0.1.8-canary.bed7cd6 → 0.1.8
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 +429 -205
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -84,9 +84,39 @@ var init_model = __esm({
|
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
+
// src/core/version.ts
|
|
88
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
89
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
90
|
+
import { dirname, join as join5 } from "path";
|
|
91
|
+
function resolveVersion() {
|
|
92
|
+
try {
|
|
93
|
+
const here = dirname(fileURLToPath2(import.meta.url));
|
|
94
|
+
for (const rel of ["../package.json", "../../package.json", "../../../package.json"]) {
|
|
95
|
+
try {
|
|
96
|
+
const pkg = JSON.parse(readFileSync3(join5(here, rel), "utf-8"));
|
|
97
|
+
if (pkg?.name === PACKAGE_NAME && typeof pkg.version === "string") {
|
|
98
|
+
return pkg.version;
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
return "unknown";
|
|
106
|
+
}
|
|
107
|
+
var PACKAGE_NAME, CLI_VERSION;
|
|
108
|
+
var init_version = __esm({
|
|
109
|
+
"src/core/version.ts"() {
|
|
110
|
+
"use strict";
|
|
111
|
+
init_esm_shims();
|
|
112
|
+
PACKAGE_NAME = "@autonoma-ai/planner";
|
|
113
|
+
CLI_VERSION = resolveVersion();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
87
117
|
// src/core/analytics.ts
|
|
88
|
-
import { readFileSync as
|
|
89
|
-
import { join as
|
|
118
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
119
|
+
import { join as join6 } from "path";
|
|
90
120
|
import { homedir as homedir3 } from "os";
|
|
91
121
|
import { randomUUID } from "crypto";
|
|
92
122
|
function resolveKey() {
|
|
@@ -109,7 +139,7 @@ function getRunId() {
|
|
|
109
139
|
function getDeviceId() {
|
|
110
140
|
if (cachedDeviceId) return cachedDeviceId;
|
|
111
141
|
try {
|
|
112
|
-
cachedDeviceId =
|
|
142
|
+
cachedDeviceId = readFileSync4(DEVICE_ID_PATH, "utf-8").trim();
|
|
113
143
|
if (cachedDeviceId) return cachedDeviceId;
|
|
114
144
|
} catch {
|
|
115
145
|
}
|
|
@@ -140,7 +170,7 @@ function track(event, properties = {}) {
|
|
|
140
170
|
// Only build a person profile when we have a real identity from the app,
|
|
141
171
|
// so the CLI joins the existing funnel person instead of creating a new one.
|
|
142
172
|
$process_person_profile: identity != null,
|
|
143
|
-
cli_version:
|
|
173
|
+
cli_version: CLI_VERSION
|
|
144
174
|
}
|
|
145
175
|
});
|
|
146
176
|
const promise = fetch(`${resolveHost()}/capture/`, {
|
|
@@ -177,8 +207,9 @@ var init_analytics = __esm({
|
|
|
177
207
|
"src/core/analytics.ts"() {
|
|
178
208
|
"use strict";
|
|
179
209
|
init_esm_shims();
|
|
180
|
-
|
|
181
|
-
|
|
210
|
+
init_version();
|
|
211
|
+
AUTONOMA_HOME3 = join6(homedir3(), ".autonoma");
|
|
212
|
+
DEVICE_ID_PATH = join6(AUTONOMA_HOME3, ".device-id");
|
|
182
213
|
POSTHOG_PUBLIC_KEY = "phc_mUOwUj62r8vyiisFPvXLC3G5RftETIBMnKNSHqTBdka";
|
|
183
214
|
DEFAULT_HOST = "https://us.i.posthog.com";
|
|
184
215
|
RUN_ID = randomUUID();
|
|
@@ -193,6 +224,9 @@ import { APICallError, RetryError, LoadAPIKeyError, InvalidPromptError, NoSuchMo
|
|
|
193
224
|
function sleep(ms) {
|
|
194
225
|
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
195
226
|
}
|
|
227
|
+
function isUserCancellation(err) {
|
|
228
|
+
return err instanceof Error && /\bcancell?ed\b/i.test(err.message);
|
|
229
|
+
}
|
|
196
230
|
function chainMessages(err) {
|
|
197
231
|
const parts = [];
|
|
198
232
|
let cur = err;
|
|
@@ -217,10 +251,16 @@ function describeKnownError(err) {
|
|
|
217
251
|
hint: "Set a valid OPENROUTER_API_KEY (https://openrouter.ai/keys). If it's already set, the key may be revoked, empty, or have a stray space."
|
|
218
252
|
};
|
|
219
253
|
}
|
|
254
|
+
if (msg.includes("fewer max_tokens") || msg.includes("can only afford")) {
|
|
255
|
+
return {
|
|
256
|
+
title: "Your OpenRouter account doesn't have enough credit for this run.",
|
|
257
|
+
hint: "Add credit (even a few dollars goes a long way) at https://openrouter.ai/settings/credits, then re-run. A free balance can't cover a full request."
|
|
258
|
+
};
|
|
259
|
+
}
|
|
220
260
|
if (msg.includes("insufficient") || msg.includes("credits") || msg.includes("quota") || status === 402) {
|
|
221
261
|
return {
|
|
222
262
|
title: "OpenRouter ran out of credits for this account.",
|
|
223
|
-
hint: "
|
|
263
|
+
hint: "Add credit at https://openrouter.ai/settings/credits, then re-run."
|
|
224
264
|
};
|
|
225
265
|
}
|
|
226
266
|
if (NoSuchModelError.isInstance(err) || msg.includes("not a valid model") || msg.includes("no endpoints found") || msg.includes("model not found")) {
|
|
@@ -240,7 +280,7 @@ function describeKnownError(err) {
|
|
|
240
280
|
function supportReference(extra = {}) {
|
|
241
281
|
const fields = {
|
|
242
282
|
ref: getRunId(),
|
|
243
|
-
version:
|
|
283
|
+
version: CLI_VERSION,
|
|
244
284
|
node: process.version,
|
|
245
285
|
platform: `${process.platform}-${process.arch}`,
|
|
246
286
|
...extra
|
|
@@ -286,6 +326,7 @@ var init_errors = __esm({
|
|
|
286
326
|
"use strict";
|
|
287
327
|
init_esm_shims();
|
|
288
328
|
init_analytics();
|
|
329
|
+
init_version();
|
|
289
330
|
AgentError = class extends Error {
|
|
290
331
|
constructor(message, phase, cause) {
|
|
291
332
|
super(message);
|
|
@@ -650,7 +691,7 @@ var init_agent = __esm({
|
|
|
650
691
|
|
|
651
692
|
// src/core/gitignore.ts
|
|
652
693
|
import { readFile as readFile5 } from "fs/promises";
|
|
653
|
-
import { join as
|
|
694
|
+
import { join as join10, relative as relative2 } from "path";
|
|
654
695
|
import { glob as glob2 } from "glob";
|
|
655
696
|
async function loadGitignorePatterns(projectRoot) {
|
|
656
697
|
const patterns = [
|
|
@@ -670,10 +711,10 @@ async function loadGitignorePatterns(projectRoot) {
|
|
|
670
711
|
];
|
|
671
712
|
const matches = await glob2("**/.gitignore", { cwd: projectRoot, dot: true });
|
|
672
713
|
for (const match of matches) {
|
|
673
|
-
const fullPath =
|
|
714
|
+
const fullPath = join10(projectRoot, match);
|
|
674
715
|
try {
|
|
675
716
|
const content = await readFile5(fullPath, "utf-8");
|
|
676
|
-
const prefix = relative2(projectRoot,
|
|
717
|
+
const prefix = relative2(projectRoot, join10(projectRoot, match, ".."));
|
|
677
718
|
const parsed = parseGitignore(content, prefix);
|
|
678
719
|
patterns.push(...parsed);
|
|
679
720
|
} catch (err) {
|
|
@@ -897,7 +938,7 @@ var init_grep = __esm({
|
|
|
897
938
|
// src/tools/list-directory.ts
|
|
898
939
|
import { readdir } from "fs/promises";
|
|
899
940
|
import { stat } from "fs/promises";
|
|
900
|
-
import { join as
|
|
941
|
+
import { join as join11, relative as relative3 } from "path";
|
|
901
942
|
import { tool as tool4 } from "ai";
|
|
902
943
|
import { z as z4 } from "zod";
|
|
903
944
|
import { minimatch } from "minimatch";
|
|
@@ -922,7 +963,7 @@ async function buildTree(dirPath, maxDepth, currentDepth, isIgnored, relativeBas
|
|
|
922
963
|
const withTypes = [];
|
|
923
964
|
for (const name of rawEntries) {
|
|
924
965
|
try {
|
|
925
|
-
const s = await stat(
|
|
966
|
+
const s = await stat(join11(dirPath, name));
|
|
926
967
|
withTypes.push({ name, isDir: s.isDirectory() });
|
|
927
968
|
} catch {
|
|
928
969
|
withTypes.push({ name, isDir: false });
|
|
@@ -942,7 +983,7 @@ async function buildTree(dirPath, maxDepth, currentDepth, isIgnored, relativeBas
|
|
|
942
983
|
}
|
|
943
984
|
if (entry.isDir) {
|
|
944
985
|
const children = await buildTree(
|
|
945
|
-
|
|
986
|
+
join11(dirPath, entry.name),
|
|
946
987
|
maxDepth,
|
|
947
988
|
currentDepth + 1,
|
|
948
989
|
isIgnored,
|
|
@@ -993,7 +1034,7 @@ async function buildListDirectoryTool(workingDirectory) {
|
|
|
993
1034
|
};
|
|
994
1035
|
}
|
|
995
1036
|
seen.add(cacheKey);
|
|
996
|
-
const targetDir = input.path === "." ? workingDirectory :
|
|
1037
|
+
const targetDir = input.path === "." ? workingDirectory : join11(workingDirectory, input.path);
|
|
997
1038
|
try {
|
|
998
1039
|
const s = await stat(targetDir);
|
|
999
1040
|
if (!s.isDirectory()) {
|
|
@@ -1176,7 +1217,7 @@ Be thorough but focused - only investigate what's relevant to your instruction.`
|
|
|
1176
1217
|
|
|
1177
1218
|
// src/tools/write-file.ts
|
|
1178
1219
|
import { writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
|
|
1179
|
-
import { dirname, relative as relative5, resolve as resolve3 } from "path";
|
|
1220
|
+
import { dirname as dirname2, relative as relative5, resolve as resolve3 } from "path";
|
|
1180
1221
|
import { tool as tool7 } from "ai";
|
|
1181
1222
|
import { z as z7 } from "zod";
|
|
1182
1223
|
async function executeWriteFile(outputDirectory, filePath, content) {
|
|
@@ -1187,7 +1228,7 @@ async function executeWriteFile(outputDirectory, filePath, content) {
|
|
|
1187
1228
|
return { error: "Cannot write files outside the output directory" };
|
|
1188
1229
|
}
|
|
1189
1230
|
try {
|
|
1190
|
-
await mkdir2(
|
|
1231
|
+
await mkdir2(dirname2(absolutePath), { recursive: true });
|
|
1191
1232
|
await writeFile4(absolutePath, content, "utf-8");
|
|
1192
1233
|
return { path: relativePath, bytesWritten: content.length };
|
|
1193
1234
|
} catch (err) {
|
|
@@ -1365,12 +1406,12 @@ var init_pages_finder = __esm({
|
|
|
1365
1406
|
// src/core/review.ts
|
|
1366
1407
|
import * as p3 from "@clack/prompts";
|
|
1367
1408
|
import { access } from "fs/promises";
|
|
1368
|
-
import { join as
|
|
1409
|
+
import { join as join12, isAbsolute } from "path";
|
|
1369
1410
|
import { spawn } from "child_process";
|
|
1370
1411
|
import which from "which";
|
|
1371
1412
|
function resolvePath(artifact, outputDir) {
|
|
1372
1413
|
if (isAbsolute(artifact)) return artifact;
|
|
1373
|
-
return
|
|
1414
|
+
return join12(outputDir, artifact);
|
|
1374
1415
|
}
|
|
1375
1416
|
async function detectEditors() {
|
|
1376
1417
|
if (cachedEditors) return cachedEditors;
|
|
@@ -1442,7 +1483,7 @@ async function showResults(result, options) {
|
|
|
1442
1483
|
if (result.artifacts.length === 0) {
|
|
1443
1484
|
const knownFiles = ["AUTONOMA.md", "entity-audit.md", "scenarios.md"];
|
|
1444
1485
|
for (const f of knownFiles) {
|
|
1445
|
-
const fullPath =
|
|
1486
|
+
const fullPath = join12(options.outputDir, f);
|
|
1446
1487
|
try {
|
|
1447
1488
|
await access(fullPath);
|
|
1448
1489
|
result.artifacts.push(f);
|
|
@@ -1675,12 +1716,12 @@ After the frontmatter, include:
|
|
|
1675
1716
|
|
|
1676
1717
|
// src/agents/01-kb-generator/flows.ts
|
|
1677
1718
|
import { readFile as readFile7 } from "fs/promises";
|
|
1678
|
-
import { join as
|
|
1719
|
+
import { join as join13 } from "path";
|
|
1679
1720
|
import matter from "gray-matter";
|
|
1680
1721
|
async function parseCoreFlows(outputDir) {
|
|
1681
1722
|
let raw;
|
|
1682
1723
|
try {
|
|
1683
|
-
raw = await readFile7(
|
|
1724
|
+
raw = await readFile7(join13(outputDir, "AUTONOMA.md"), "utf-8");
|
|
1684
1725
|
} catch {
|
|
1685
1726
|
return [];
|
|
1686
1727
|
}
|
|
@@ -1752,7 +1793,7 @@ __export(kb_generator_exports, {
|
|
|
1752
1793
|
import { tool as tool10 } from "ai";
|
|
1753
1794
|
import { z as z10 } from "zod";
|
|
1754
1795
|
import { readFile as readFile8 } from "fs/promises";
|
|
1755
|
-
import { join as
|
|
1796
|
+
import { join as join14 } from "path";
|
|
1756
1797
|
function buildRegisterPagesTool(tracker) {
|
|
1757
1798
|
return tool10({
|
|
1758
1799
|
description: "Register ALL page/route files discovered via glob. Call this ONCE after globbing for page files. The system will track which ones you've read and block finish until all are covered.",
|
|
@@ -1866,7 +1907,7 @@ Output files:
|
|
|
1866
1907
|
};
|
|
1867
1908
|
await runAgent(agentConfig, prompt, () => result);
|
|
1868
1909
|
logger.summary();
|
|
1869
|
-
const autonomaPath =
|
|
1910
|
+
const autonomaPath = join14(input.outputDir, "AUTONOMA.md");
|
|
1870
1911
|
const autonomaExists = await readFile8(autonomaPath, "utf-8").then(() => true).catch(() => false);
|
|
1871
1912
|
if (!result?.success && autonomaExists) {
|
|
1872
1913
|
result = {
|
|
@@ -1961,12 +2002,30 @@ var init_kb_generator = __esm({
|
|
|
1961
2002
|
|
|
1962
2003
|
// src/agents/04-recipe-builder/entity-order.ts
|
|
1963
2004
|
import { readFile as readFile9 } from "fs/promises";
|
|
1964
|
-
import { join as
|
|
2005
|
+
import { join as join15 } from "path";
|
|
2006
|
+
import matter2 from "gray-matter";
|
|
2007
|
+
import { z as z11 } from "zod";
|
|
1965
2008
|
async function parseEntityAudit(outputDir) {
|
|
1966
|
-
const raw = await readFile9(
|
|
2009
|
+
const raw = await readFile9(join15(outputDir, "entity-audit.md"), "utf-8");
|
|
2010
|
+
try {
|
|
2011
|
+
const parsed = frontmatterSchema.safeParse(matter2(raw).data);
|
|
2012
|
+
if (parsed.success && parsed.data.models.length > 0) {
|
|
2013
|
+
return parsed.data.models.map((m) => ({
|
|
2014
|
+
name: m.name,
|
|
2015
|
+
independently_created: m.independently_created,
|
|
2016
|
+
creation_file: m.creation_file,
|
|
2017
|
+
creation_function: m.creation_function,
|
|
2018
|
+
side_effects: m.side_effects,
|
|
2019
|
+
created_by: m.created_by
|
|
2020
|
+
}));
|
|
2021
|
+
}
|
|
2022
|
+
} catch {
|
|
2023
|
+
}
|
|
2024
|
+
return parseAuditByLineScan(raw);
|
|
2025
|
+
}
|
|
2026
|
+
function parseAuditByLineScan(raw) {
|
|
1967
2027
|
const fmMatch = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
1968
|
-
|
|
1969
|
-
const yaml = fmMatch[1];
|
|
2028
|
+
const yaml = fmMatch ? fmMatch[1] : raw;
|
|
1970
2029
|
const models = [];
|
|
1971
2030
|
let current = null;
|
|
1972
2031
|
let inCreatedBy = false;
|
|
@@ -2120,10 +2179,28 @@ function getEntityDependencyChain(entityName, models, entityOrder) {
|
|
|
2120
2179
|
walk(entityName);
|
|
2121
2180
|
return chain;
|
|
2122
2181
|
}
|
|
2182
|
+
var createdBySchema, auditedModelSchema, frontmatterSchema;
|
|
2123
2183
|
var init_entity_order = __esm({
|
|
2124
2184
|
"src/agents/04-recipe-builder/entity-order.ts"() {
|
|
2125
2185
|
"use strict";
|
|
2126
2186
|
init_esm_shims();
|
|
2187
|
+
createdBySchema = z11.object({
|
|
2188
|
+
owner: z11.string(),
|
|
2189
|
+
via: z11.string().optional(),
|
|
2190
|
+
why: z11.string().optional()
|
|
2191
|
+
});
|
|
2192
|
+
auditedModelSchema = z11.object({
|
|
2193
|
+
name: z11.string(),
|
|
2194
|
+
independently_created: z11.coerce.boolean().default(false),
|
|
2195
|
+
creation_file: z11.string().optional(),
|
|
2196
|
+
creation_function: z11.string().optional(),
|
|
2197
|
+
side_effects: z11.array(z11.string()).optional(),
|
|
2198
|
+
// Tolerate a stray `created_by:` with no entries (parsed as null by YAML).
|
|
2199
|
+
created_by: z11.array(createdBySchema).nullish().transform((v) => v ?? [])
|
|
2200
|
+
});
|
|
2201
|
+
frontmatterSchema = z11.object({
|
|
2202
|
+
models: z11.array(auditedModelSchema).nullish().transform((v) => v ?? [])
|
|
2203
|
+
});
|
|
2127
2204
|
}
|
|
2128
2205
|
});
|
|
2129
2206
|
|
|
@@ -2333,16 +2410,16 @@ __export(entity_audit_exports, {
|
|
|
2333
2410
|
runEntityAudit: () => runEntityAudit
|
|
2334
2411
|
});
|
|
2335
2412
|
import { readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
|
|
2336
|
-
import { join as
|
|
2413
|
+
import { join as join16 } from "path";
|
|
2337
2414
|
import { tool as tool11 } from "ai";
|
|
2338
|
-
import { z as
|
|
2415
|
+
import { z as z12 } from "zod";
|
|
2339
2416
|
import { glob as glob4 } from "glob";
|
|
2340
2417
|
function buildRegisterModelsTool(tracker) {
|
|
2341
2418
|
return tool11({
|
|
2342
2419
|
description: "Register ALL database models discovered via grep. Call this ONCE after grepping for model definitions. After registering, use next_model to process them one at a time.",
|
|
2343
|
-
inputSchema:
|
|
2344
|
-
models:
|
|
2345
|
-
framework:
|
|
2420
|
+
inputSchema: z12.object({
|
|
2421
|
+
models: z12.array(z12.string()).describe("All model/table names found by grep"),
|
|
2422
|
+
framework: z12.string().describe("Database framework detected (e.g. 'sqlalchemy', 'prisma', 'drizzle')")
|
|
2346
2423
|
}),
|
|
2347
2424
|
execute: async (input) => {
|
|
2348
2425
|
tracker.register(input.models);
|
|
@@ -2358,7 +2435,7 @@ function buildRegisterModelsTool(tracker) {
|
|
|
2358
2435
|
function buildNextModelTool(tracker) {
|
|
2359
2436
|
return tool11({
|
|
2360
2437
|
description: "Get the next model to audit from the queue. If you called next_model before without calling mark_model_audited, the previous model is auto-skipped (marked as no creation path found). Returns done:true when all models are processed.",
|
|
2361
|
-
inputSchema:
|
|
2438
|
+
inputSchema: z12.object({}),
|
|
2362
2439
|
execute: async () => {
|
|
2363
2440
|
const next = tracker.nextModel();
|
|
2364
2441
|
if (!next) {
|
|
@@ -2378,16 +2455,16 @@ function buildNextModelTool(tracker) {
|
|
|
2378
2455
|
function buildMarkModelAuditedTool(tracker) {
|
|
2379
2456
|
return tool11({
|
|
2380
2457
|
description: "Mark a model as audited after you have determined its creation paths. Call this for EACH model after reading its creation code and determining independently_created + created_by. Include creation_function (e.g. 'UserService.create'), side_effects (list of things the creation does beyond the model itself), and for each created_by entry include owner, via (function name), and why (one sentence explaining the relationship).",
|
|
2381
|
-
inputSchema:
|
|
2382
|
-
model:
|
|
2383
|
-
independently_created:
|
|
2384
|
-
creation_file:
|
|
2385
|
-
creation_function:
|
|
2386
|
-
side_effects:
|
|
2387
|
-
created_by:
|
|
2388
|
-
owner:
|
|
2389
|
-
via:
|
|
2390
|
-
why:
|
|
2458
|
+
inputSchema: z12.object({
|
|
2459
|
+
model: z12.string().describe("Model name"),
|
|
2460
|
+
independently_created: z12.boolean(),
|
|
2461
|
+
creation_file: z12.string().optional().describe("File containing the creation function"),
|
|
2462
|
+
creation_function: z12.string().optional().describe("Function/method name (e.g. 'UserService.create' or 'create_user')"),
|
|
2463
|
+
side_effects: z12.array(z12.string()).optional().describe("Side effects of creation (e.g. 'creates default Settings row', 'hashes password')"),
|
|
2464
|
+
created_by: z12.array(z12.object({
|
|
2465
|
+
owner: z12.string().describe("Owner model name"),
|
|
2466
|
+
via: z12.string().optional().describe("Function that creates this model (e.g. 'OrganizationService.create')"),
|
|
2467
|
+
why: z12.string().optional().describe("One sentence explaining why this model is created as a side effect")
|
|
2391
2468
|
})).describe("List of owner models that create this as a side effect, empty array if none")
|
|
2392
2469
|
}),
|
|
2393
2470
|
execute: async (input) => {
|
|
@@ -2425,16 +2502,16 @@ function buildMarkModelAuditedTool(tracker) {
|
|
|
2425
2502
|
function buildModelCoverageTool(tracker) {
|
|
2426
2503
|
return tool11({
|
|
2427
2504
|
description: "Check how many registered models you've audited vs how many remain.",
|
|
2428
|
-
inputSchema:
|
|
2505
|
+
inputSchema: z12.object({}),
|
|
2429
2506
|
execute: async () => tracker.coverage()
|
|
2430
2507
|
});
|
|
2431
2508
|
}
|
|
2432
2509
|
function buildFinishTool2(tracker, onFinish) {
|
|
2433
2510
|
return tool11({
|
|
2434
2511
|
description: "Call when entity audit is complete. BLOCKED if there are unaudited models \u2014 call model_coverage first to check.",
|
|
2435
|
-
inputSchema:
|
|
2436
|
-
summary:
|
|
2437
|
-
artifacts:
|
|
2512
|
+
inputSchema: z12.object({
|
|
2513
|
+
summary: z12.string().describe("Summary of the audit"),
|
|
2514
|
+
artifacts: z12.array(z12.string()).describe("Files written")
|
|
2438
2515
|
}),
|
|
2439
2516
|
execute: async (input) => {
|
|
2440
2517
|
const cov = tracker.coverage();
|
|
@@ -2544,14 +2621,18 @@ write_file already targets the output directory \u2014 use just the filename.`;
|
|
|
2544
2621
|
${formatException(err)}`);
|
|
2545
2622
|
}
|
|
2546
2623
|
logger.summary();
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
const auditPath =
|
|
2550
|
-
await writeFile5(auditPath,
|
|
2624
|
+
const writeCanonicalAudit = async () => {
|
|
2625
|
+
if (tracker.auditedModels.size === 0) return null;
|
|
2626
|
+
const auditPath = join16(input.outputDir, "entity-audit.md");
|
|
2627
|
+
await writeFile5(auditPath, tracker.generateAuditMarkdown(), "utf-8");
|
|
2628
|
+
return auditPath;
|
|
2629
|
+
};
|
|
2630
|
+
const canonicalPath = await writeCanonicalAudit();
|
|
2631
|
+
if (!result && canonicalPath) {
|
|
2551
2632
|
const cov = tracker.coverage();
|
|
2552
2633
|
result = {
|
|
2553
2634
|
success: true,
|
|
2554
|
-
artifacts: [
|
|
2635
|
+
artifacts: [canonicalPath],
|
|
2555
2636
|
summary: `Safety net: agent ran out of steps but audited ${cov.audited}/${cov.totalModels} models. File written from tracker data.`
|
|
2556
2637
|
};
|
|
2557
2638
|
}
|
|
@@ -2575,11 +2656,12 @@ Call model_coverage to see current state.
|
|
|
2575
2656
|
Adjust based on the feedback. You can grep/read source files again if needed.
|
|
2576
2657
|
When done with changes, call finish again.`;
|
|
2577
2658
|
await runAgent(agentConfig, feedbackPrompt, () => result);
|
|
2659
|
+
await writeCanonicalAudit();
|
|
2578
2660
|
return result;
|
|
2579
2661
|
}
|
|
2580
2662
|
});
|
|
2581
2663
|
if (!reviewed) {
|
|
2582
|
-
const auditPath =
|
|
2664
|
+
const auditPath = join16(input.outputDir, "entity-audit.md");
|
|
2583
2665
|
try {
|
|
2584
2666
|
await readFile10(auditPath, "utf-8");
|
|
2585
2667
|
return {
|
|
@@ -2721,10 +2803,10 @@ ${duals.length > 0 ? duals.map((m) => `- **${m.name}** \u2014 standalone: ${m.cr
|
|
|
2721
2803
|
|
|
2722
2804
|
// src/core/parse-entity-audit.ts
|
|
2723
2805
|
import { readFile as readFile11 } from "fs/promises";
|
|
2724
|
-
import { join as
|
|
2806
|
+
import { join as join17 } from "path";
|
|
2725
2807
|
async function parseEntityNames(outputDir) {
|
|
2726
2808
|
try {
|
|
2727
|
-
const content = await readFile11(
|
|
2809
|
+
const content = await readFile11(join17(outputDir, "entity-audit.md"), "utf-8");
|
|
2728
2810
|
const names = [];
|
|
2729
2811
|
for (const line of content.split("\n")) {
|
|
2730
2812
|
const match = line.match(/^\s+-\s+name:\s+(.+)$/);
|
|
@@ -2744,17 +2826,17 @@ var init_parse_entity_audit = __esm({
|
|
|
2744
2826
|
|
|
2745
2827
|
// src/agents/03-scenario-recipe/scenario-table.ts
|
|
2746
2828
|
import { readFile as readFile12 } from "fs/promises";
|
|
2747
|
-
import { join as
|
|
2748
|
-
import
|
|
2829
|
+
import { join as join18 } from "path";
|
|
2830
|
+
import matter3 from "gray-matter";
|
|
2749
2831
|
async function parseScenario(outputDir) {
|
|
2750
2832
|
let raw;
|
|
2751
2833
|
try {
|
|
2752
|
-
raw = await readFile12(
|
|
2834
|
+
raw = await readFile12(join18(outputDir, "scenarios.md"), "utf-8");
|
|
2753
2835
|
} catch {
|
|
2754
2836
|
return { scenarioNames: [], entityTypes: [] };
|
|
2755
2837
|
}
|
|
2756
2838
|
try {
|
|
2757
|
-
const data =
|
|
2839
|
+
const data = matter3(raw).data;
|
|
2758
2840
|
const varsByEntity = /* @__PURE__ */ new Map();
|
|
2759
2841
|
for (const vf of data.variable_fields ?? []) {
|
|
2760
2842
|
const entity = vf?.entity != null ? String(vf.entity) : "";
|
|
@@ -2889,21 +2971,21 @@ __export(scenario_recipe_exports, {
|
|
|
2889
2971
|
runScenarioRecipe: () => runScenarioRecipe
|
|
2890
2972
|
});
|
|
2891
2973
|
import { readFile as readFile13 } from "fs/promises";
|
|
2892
|
-
import { join as
|
|
2974
|
+
import { join as join19 } from "path";
|
|
2893
2975
|
import { tool as tool12 } from "ai";
|
|
2894
|
-
import { z as
|
|
2976
|
+
import { z as z13 } from "zod";
|
|
2895
2977
|
function buildFinishTool3(requiredEntities, outputDir, onFinish) {
|
|
2896
2978
|
return tool12({
|
|
2897
2979
|
description: "Call when scenario design is complete and scenarios.md is written. BLOCKED if any required entities are missing from the scenario.",
|
|
2898
|
-
inputSchema:
|
|
2899
|
-
summary:
|
|
2900
|
-
entityCount:
|
|
2901
|
-
artifacts:
|
|
2980
|
+
inputSchema: z13.object({
|
|
2981
|
+
summary: z13.string().describe("Summary of the scenario"),
|
|
2982
|
+
entityCount: z13.number().describe("Number of entity types in the scenario"),
|
|
2983
|
+
artifacts: z13.array(z13.string()).describe("Files written")
|
|
2902
2984
|
}),
|
|
2903
2985
|
execute: async (input) => {
|
|
2904
2986
|
if (requiredEntities.length > 0) {
|
|
2905
2987
|
try {
|
|
2906
|
-
const content = await readFile13(
|
|
2988
|
+
const content = await readFile13(join19(outputDir, "scenarios.md"), "utf-8");
|
|
2907
2989
|
const missing = requiredEntities.filter(
|
|
2908
2990
|
(e) => !content.includes(e)
|
|
2909
2991
|
);
|
|
@@ -2990,7 +3072,7 @@ When done with changes, call finish again.`;
|
|
|
2990
3072
|
}
|
|
2991
3073
|
});
|
|
2992
3074
|
if (!reviewed) {
|
|
2993
|
-
const scenariosPath =
|
|
3075
|
+
const scenariosPath = join19(input.outputDir, "scenarios.md");
|
|
2994
3076
|
try {
|
|
2995
3077
|
await readFile13(scenariosPath, "utf-8");
|
|
2996
3078
|
return {
|
|
@@ -3047,7 +3129,7 @@ var init_scenario_recipe = __esm({
|
|
|
3047
3129
|
|
|
3048
3130
|
// src/agents/04-recipe-builder/state.ts
|
|
3049
3131
|
import { readFile as readFile14, writeFile as writeFile6 } from "fs/promises";
|
|
3050
|
-
import { join as
|
|
3132
|
+
import { join as join20 } from "path";
|
|
3051
3133
|
function adapterKey(a) {
|
|
3052
3134
|
return `${a.language}:${a.framework}`;
|
|
3053
3135
|
}
|
|
@@ -3074,14 +3156,14 @@ function initialRecipeState() {
|
|
|
3074
3156
|
}
|
|
3075
3157
|
async function loadRecipeState(outputDir) {
|
|
3076
3158
|
try {
|
|
3077
|
-
const raw = await readFile14(
|
|
3159
|
+
const raw = await readFile14(join20(outputDir, STATE_FILE2), "utf-8");
|
|
3078
3160
|
return JSON.parse(raw);
|
|
3079
3161
|
} catch {
|
|
3080
3162
|
return null;
|
|
3081
3163
|
}
|
|
3082
3164
|
}
|
|
3083
3165
|
async function saveRecipeState(outputDir, state) {
|
|
3084
|
-
await writeFile6(
|
|
3166
|
+
await writeFile6(join20(outputDir, STATE_FILE2), JSON.stringify(state, null, 2), "utf-8");
|
|
3085
3167
|
}
|
|
3086
3168
|
var ALL_ADAPTERS, ADAPTER_HINTS, STATE_FILE2;
|
|
3087
3169
|
var init_state = __esm({
|
|
@@ -3128,7 +3210,7 @@ var init_state = __esm({
|
|
|
3128
3210
|
|
|
3129
3211
|
// src/agents/04-recipe-builder/entity-relevance.ts
|
|
3130
3212
|
import { Output, generateText } from "ai";
|
|
3131
|
-
import { z as
|
|
3213
|
+
import { z as z14 } from "zod";
|
|
3132
3214
|
async function callRanker(model, prompt) {
|
|
3133
3215
|
const result = await generateText({
|
|
3134
3216
|
model,
|
|
@@ -3210,8 +3292,8 @@ var init_entity_relevance = __esm({
|
|
|
3210
3292
|
"use strict";
|
|
3211
3293
|
init_esm_shims();
|
|
3212
3294
|
init_errors();
|
|
3213
|
-
rankedSchema =
|
|
3214
|
-
ranked:
|
|
3295
|
+
rankedSchema = z14.object({
|
|
3296
|
+
ranked: z14.array(z14.string()).describe("Every entity name, ordered most-important first.")
|
|
3215
3297
|
});
|
|
3216
3298
|
}
|
|
3217
3299
|
});
|
|
@@ -3219,7 +3301,7 @@ var init_entity_relevance = __esm({
|
|
|
3219
3301
|
// src/agents/04-recipe-builder/phases/tech-detect.ts
|
|
3220
3302
|
import * as p4 from "@clack/prompts";
|
|
3221
3303
|
import { tool as tool13 } from "ai";
|
|
3222
|
-
import { z as
|
|
3304
|
+
import { z as z15 } from "zod";
|
|
3223
3305
|
async function detectTechStack(projectRoot, modelId, nonInteractive) {
|
|
3224
3306
|
const model = getModel(modelId);
|
|
3225
3307
|
const ignorePatterns = await loadGitignorePatterns(projectRoot);
|
|
@@ -3227,9 +3309,9 @@ async function detectTechStack(projectRoot, modelId, nonInteractive) {
|
|
|
3227
3309
|
const { logger, onStepFinish } = buildDefaultStepLogger("tech-detect", 10);
|
|
3228
3310
|
const finishTool = tool13({
|
|
3229
3311
|
description: "Report the detected backend technology stack.",
|
|
3230
|
-
inputSchema:
|
|
3231
|
-
language:
|
|
3232
|
-
framework:
|
|
3312
|
+
inputSchema: z15.object({
|
|
3313
|
+
language: z15.string().describe("Programming language: typescript, python, go, ruby, java, php, rust, elixir"),
|
|
3314
|
+
framework: z15.string().describe("Web framework: express, node, hono, web, flask, fastapi, django, gin, rails, rack, spring, laravel, axum, actix, plug")
|
|
3233
3315
|
}),
|
|
3234
3316
|
execute: async (input) => {
|
|
3235
3317
|
detected = input;
|
|
@@ -3311,11 +3393,11 @@ When done, call finish with your findings.`;
|
|
|
3311
3393
|
|
|
3312
3394
|
// src/core/detect-pkg-manager.ts
|
|
3313
3395
|
import { existsSync as existsSync2 } from "fs";
|
|
3314
|
-
import { join as
|
|
3396
|
+
import { join as join21 } from "path";
|
|
3315
3397
|
function detectPackageManager(projectRoot) {
|
|
3316
|
-
if (existsSync2(
|
|
3317
|
-
if (existsSync2(
|
|
3318
|
-
if (existsSync2(
|
|
3398
|
+
if (existsSync2(join21(projectRoot, "bun.lock")) || existsSync2(join21(projectRoot, "bun.lockb"))) return "bun";
|
|
3399
|
+
if (existsSync2(join21(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3400
|
+
if (existsSync2(join21(projectRoot, "yarn.lock"))) return "yarn";
|
|
3319
3401
|
return "npm";
|
|
3320
3402
|
}
|
|
3321
3403
|
function installCommand(pm, ...packages) {
|
|
@@ -3392,7 +3474,7 @@ var init_highlight = __esm({
|
|
|
3392
3474
|
|
|
3393
3475
|
// src/agents/04-recipe-builder/recipe.ts
|
|
3394
3476
|
import { readFile as readFile15, writeFile as writeFile7 } from "fs/promises";
|
|
3395
|
-
import { join as
|
|
3477
|
+
import { join as join22 } from "path";
|
|
3396
3478
|
function buildSingleEntityRecipe(entityName, models, entityOrder, allEntities) {
|
|
3397
3479
|
const chain = getEntityDependencyChain(entityName, models, entityOrder);
|
|
3398
3480
|
const recipe = {};
|
|
@@ -3436,7 +3518,7 @@ function buildSubmittableRecipe(create, description) {
|
|
|
3436
3518
|
};
|
|
3437
3519
|
}
|
|
3438
3520
|
async function saveRecipe(outputDir, recipe) {
|
|
3439
|
-
await writeFile7(
|
|
3521
|
+
await writeFile7(join22(outputDir, RECIPE_FILE), JSON.stringify(recipe, null, 2), "utf-8");
|
|
3440
3522
|
}
|
|
3441
3523
|
var RECIPE_FILE;
|
|
3442
3524
|
var init_recipe = __esm({
|
|
@@ -3494,24 +3576,108 @@ var init_http_client = __esm({
|
|
|
3494
3576
|
}
|
|
3495
3577
|
});
|
|
3496
3578
|
|
|
3579
|
+
// src/agents/04-recipe-builder/phases/failure-classifier.ts
|
|
3580
|
+
import { Output as Output2, generateText as generateText2 } from "ai";
|
|
3581
|
+
import { z as z16 } from "zod";
|
|
3582
|
+
function buildClassifierPrompt(args) {
|
|
3583
|
+
const errorText = typeof args.error === "string" ? args.error : JSON.stringify(args.error, null, 2);
|
|
3584
|
+
const phaseLine = args.phase === "teardown" ? `This was a DOWN (teardown) request. Teardown runs the developer's delete logic against data the create() step already accepted, so teardown failures are usually implementation-side (wrong delete order, foreign-key cleanup bugs) rather than caused by the recipe.` : `This was an UP (create) request \u2014 the factory tried to insert the recipe records.`;
|
|
3585
|
+
const statusLine = args.httpStatus != null ? `HTTP status: ${args.httpStatus}. (A 5xx usually means the handler threw; a 4xx is more often a rejected payload, but use the error text \u2014 status alone is not decisive.)` : `No HTTP status (the request threw before a response \u2014 often a network/server-process problem, lean implementation or unclear).`;
|
|
3586
|
+
return `${PRIMER}
|
|
3587
|
+
|
|
3588
|
+
- RECIPE DATA is wrong \u2014 the JSON the tool sent doesn't fit the developer's (correct) schema. Examples of the *kind* of problem (not an exhaustive list): a _ref points at an alias that does not exist among the valid targets listed below, a field holds a value the backend rejects, a required field is missing or an unknown field was sent, or a value has the wrong type. These are fixable by regenerating the data \u2014 the developer's code is fine.
|
|
3589
|
+
- IMPLEMENTATION is wrong \u2014 the developer's own handler/factory code is broken. Examples of the *kind* of problem: the factory for this entity is not registered, the handler references a column or table that does not exist (even though the recipe never mentioned it), the insert/delete logic has a bug, or the server threw an unhandled exception. No change to the test data can fix this; the developer must edit code.
|
|
3590
|
+
|
|
3591
|
+
## How to decide
|
|
3592
|
+
|
|
3593
|
+
Ask: "Would sending DIFFERENT, corrected test data \u2014 still matching the intended schema \u2014 plausibly make this request succeed?"
|
|
3594
|
+
- If yes \u2192 **recipe**.
|
|
3595
|
+
- If the data shown below looks valid and the error points at the server's own logic, a missing factory, or a column/table the recipe never referenced \u2192 **implementation**.
|
|
3596
|
+
- If you genuinely cannot tell from the evidence \u2192 **unclear**. Prefer "unclear" over a confident guess; a wrong confident answer is worse than admitting uncertainty.
|
|
3597
|
+
|
|
3598
|
+
Cross-check the error against the actual data below before deciding \u2014 e.g. only call a _ref invalid if its target really is absent from the valid targets list.
|
|
3599
|
+
|
|
3600
|
+
## Evidence
|
|
3601
|
+
|
|
3602
|
+
${phaseLine}
|
|
3603
|
+
${statusLine}
|
|
3604
|
+
|
|
3605
|
+
Entity: "${args.entityName}"
|
|
3606
|
+
|
|
3607
|
+
Valid _ref targets (aliases declared by already-created parent entities):
|
|
3608
|
+
${args.validRefAliases?.trim() || "(none \u2014 this is a root entity with no parents to reference)"}
|
|
3609
|
+
|
|
3610
|
+
What the entity audit recorded about how "${args.entityName}" is created:
|
|
3611
|
+
${args.entityAudit?.trim() || "(not available)"}
|
|
3612
|
+
|
|
3613
|
+
Test data sent:
|
|
3614
|
+
${JSON.stringify(args.recipe, null, 2)}
|
|
3615
|
+
|
|
3616
|
+
Error:
|
|
3617
|
+
${errorText}`;
|
|
3618
|
+
}
|
|
3619
|
+
async function classifyFailure(model, args) {
|
|
3620
|
+
try {
|
|
3621
|
+
const result = await generateText2({
|
|
3622
|
+
model,
|
|
3623
|
+
prompt: buildClassifierPrompt(args),
|
|
3624
|
+
output: Output2.object({ schema: classificationSchema })
|
|
3625
|
+
});
|
|
3626
|
+
return result.output;
|
|
3627
|
+
} catch (err) {
|
|
3628
|
+
return { side: "unclear", reason: `Could not auto-triage this error (${formatException(err)}).` };
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
var classificationSchema, PRIMER;
|
|
3632
|
+
var init_failure_classifier = __esm({
|
|
3633
|
+
"src/agents/04-recipe-builder/phases/failure-classifier.ts"() {
|
|
3634
|
+
"use strict";
|
|
3635
|
+
init_esm_shims();
|
|
3636
|
+
init_errors();
|
|
3637
|
+
classificationSchema = z16.object({
|
|
3638
|
+
side: z16.enum(["recipe", "implementation", "unclear"]).describe(
|
|
3639
|
+
"recipe = the test data we sent is wrong and regenerating it could fix the failure; implementation = the developer's handler/factory code is wrong and only a code change fixes it; unclear = cannot confidently attribute the failure to either side."
|
|
3640
|
+
),
|
|
3641
|
+
reason: z16.string().describe("One short, plain-language sentence explaining the verdict for the user. No code, no jargon dumps.")
|
|
3642
|
+
});
|
|
3643
|
+
PRIMER = `## Background \u2014 what you are looking at
|
|
3644
|
+
|
|
3645
|
+
The Autonoma SDK lets a developer seed and tear down test data through a single HTTP endpoint on their own backend (the "Environment Factory"). For each database entity the developer registers a *factory* with two functions: a create() that inserts records, and a teardown() that deletes them. Their handler code calls into their app's real service/ORM layer.
|
|
3646
|
+
|
|
3647
|
+
A separate tool (not the developer) generates the *recipe*: JSON test data, one array of records per entity. Each record may carry an "_alias" (a unique handle, e.g. "account_1") and "_ref" fields ({ "_ref": "account_1" }) that point at an alias declared by an already-created parent entity. The tool sends this recipe to the endpoint:
|
|
3648
|
+
- An **UP** request asks the factory to create the records (calls create()).
|
|
3649
|
+
- A **DOWN** request asks the factory to tear them down (calls teardown()).
|
|
3650
|
+
|
|
3651
|
+
So a failure has exactly two possible origins, and your only job is to tell them apart:`;
|
|
3652
|
+
}
|
|
3653
|
+
});
|
|
3654
|
+
|
|
3497
3655
|
// src/agents/04-recipe-builder/phases/entity-loop.ts
|
|
3498
3656
|
import * as p5 from "@clack/prompts";
|
|
3499
3657
|
import { writeFile as writeFile8, readFile as readFile16 } from "fs/promises";
|
|
3500
|
-
import { join as
|
|
3658
|
+
import { join as join23 } from "path";
|
|
3501
3659
|
import { tmpdir } from "os";
|
|
3502
3660
|
import { spawn as spawn2 } from "child_process";
|
|
3503
3661
|
import { tool as tool14 } from "ai";
|
|
3504
|
-
import { z as
|
|
3662
|
+
import { z as z17 } from "zod";
|
|
3505
3663
|
function summarizeCompletedAliases(completedEntities, excludeName) {
|
|
3506
3664
|
return Object.entries(completedEntities).filter(([name, e]) => name !== excludeName && e.recipeData && e.recipeData.length > 0).map(([name, e]) => `${name}: aliases ${e.recipeData.map((r) => r._alias ?? "?").join(", ")}`).join("\n");
|
|
3507
3665
|
}
|
|
3666
|
+
function summarizeEntityAudit(model) {
|
|
3667
|
+
if (!model) return void 0;
|
|
3668
|
+
const parts = [`independently_created: ${model.independently_created}`];
|
|
3669
|
+
if (model.creation_function) parts.push(`creation function: ${model.creation_function}`);
|
|
3670
|
+
if (model.created_by.length > 0) parts.push(`created by: ${model.created_by.map((c) => c.owner).join(", ")}`);
|
|
3671
|
+
if (model.side_effects?.length) parts.push(`side effects: ${model.side_effects.join(", ")}`);
|
|
3672
|
+
return parts.join("; ");
|
|
3673
|
+
}
|
|
3508
3674
|
async function proposeRecipeData(entityName, entityIndex, totalEntities, model, outputDir, _projectRoot, completedEntities) {
|
|
3509
3675
|
let result;
|
|
3510
3676
|
const { logger, onStepFinish } = buildDefaultStepLogger(`propose:${entityName}`, 20);
|
|
3511
3677
|
const finishTool = tool14({
|
|
3512
3678
|
description: "Submit the proposed recipe data as a JSON array of records.",
|
|
3513
|
-
inputSchema:
|
|
3514
|
-
records:
|
|
3679
|
+
inputSchema: z17.object({
|
|
3680
|
+
records: z17.array(z17.record(z17.string(), z17.unknown())).describe("Array of record objects for this entity")
|
|
3515
3681
|
}),
|
|
3516
3682
|
execute: async (input) => {
|
|
3517
3683
|
result = input.records;
|
|
@@ -3552,8 +3718,8 @@ async function reviseRecipeData(entityName, entityIndex, totalEntities, current,
|
|
|
3552
3718
|
let revised;
|
|
3553
3719
|
const finishTool = tool14({
|
|
3554
3720
|
description: "Submit the fixed recipe data.",
|
|
3555
|
-
inputSchema:
|
|
3556
|
-
records:
|
|
3721
|
+
inputSchema: z17.object({
|
|
3722
|
+
records: z17.array(z17.record(z17.string(), z17.unknown()))
|
|
3557
3723
|
}),
|
|
3558
3724
|
execute: async (input) => {
|
|
3559
3725
|
revised = input.records;
|
|
@@ -3609,8 +3775,8 @@ async function generateInstructions(entityName, entityIndex, totalEntities, isFi
|
|
|
3609
3775
|
const { logger, onStepFinish } = buildDefaultStepLogger(`instructions:${entityName}`, 15);
|
|
3610
3776
|
const finishTool = tool14({
|
|
3611
3777
|
description: "Submit the implementation instructions.",
|
|
3612
|
-
inputSchema:
|
|
3613
|
-
instructions:
|
|
3778
|
+
inputSchema: z17.object({
|
|
3779
|
+
instructions: z17.string().describe("Complete, copy-pasteable implementation instructions")
|
|
3614
3780
|
}),
|
|
3615
3781
|
execute: async (input) => {
|
|
3616
3782
|
result = input.instructions;
|
|
@@ -3690,7 +3856,7 @@ async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed
|
|
|
3690
3856
|
if (p5.isCancel(action)) throw new Error("Recipe review cancelled");
|
|
3691
3857
|
if (action === "keep") return proposed;
|
|
3692
3858
|
if (action === "edit") {
|
|
3693
|
-
const tmpPath =
|
|
3859
|
+
const tmpPath = join23(tmpdir(), `autonoma-recipe-${entityName}.json`);
|
|
3694
3860
|
await writeFile8(tmpPath, JSON.stringify(proposed, null, 2), "utf-8");
|
|
3695
3861
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
3696
3862
|
p5.log.info(`Opening ${editor}... Save and close when done.`);
|
|
@@ -3727,30 +3893,74 @@ async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed
|
|
|
3727
3893
|
}
|
|
3728
3894
|
}
|
|
3729
3895
|
}
|
|
3730
|
-
|
|
3896
|
+
function formatErrorContext(errorBody) {
|
|
3897
|
+
if (errorBody == null) return "";
|
|
3898
|
+
const rendered = typeof errorBody === "string" ? errorBody : JSON.stringify(errorBody);
|
|
3899
|
+
return `
|
|
3900
|
+
Server error: ${rendered}`;
|
|
3901
|
+
}
|
|
3902
|
+
function seedFeedbackFromError(errorContext, reason) {
|
|
3903
|
+
const triage = reason ? ` (Auto-triage: ${reason})` : "";
|
|
3904
|
+
return { feedback: `The request failed \u2014 read the error and fix the recipe data.${triage}${errorContext}` };
|
|
3905
|
+
}
|
|
3906
|
+
async function promptOnFailure(entityName, errorBody, ctx, phase, httpStatus) {
|
|
3731
3907
|
notify("Autonoma", `${entityName} \u2014 failed, action needed`);
|
|
3908
|
+
const errorContext = formatErrorContext(errorBody);
|
|
3909
|
+
const budgetLeft = ctx.budget.attempts < MAX_AUTOFIX_ATTEMPTS;
|
|
3910
|
+
const { side, reason } = await classifyFailure(ctx.model, {
|
|
3911
|
+
entityName,
|
|
3912
|
+
phase,
|
|
3913
|
+
httpStatus,
|
|
3914
|
+
error: errorBody,
|
|
3915
|
+
recipe: ctx.recipe,
|
|
3916
|
+
validRefAliases: ctx.validRefAliases,
|
|
3917
|
+
entityAudit: ctx.entityAudit
|
|
3918
|
+
});
|
|
3919
|
+
if (side === "recipe" && budgetLeft) {
|
|
3920
|
+
ctx.budget.attempts++;
|
|
3921
|
+
p5.log.info(
|
|
3922
|
+
`Triaged as a recipe-data issue \u2014 fixing automatically (attempt ${ctx.budget.attempts}/${MAX_AUTOFIX_ATTEMPTS}): ${reason}`
|
|
3923
|
+
);
|
|
3924
|
+
return seedFeedbackFromError(errorContext, reason);
|
|
3925
|
+
}
|
|
3926
|
+
const offerAutofix = side !== "implementation" && budgetLeft;
|
|
3927
|
+
if (side === "implementation") {
|
|
3928
|
+
p5.log.warn(`This looks like a handler/code issue, not the test data: ${reason}`);
|
|
3929
|
+
} else if (side === "recipe" && !budgetLeft) {
|
|
3930
|
+
p5.log.warn(`Autofix ran ${MAX_AUTOFIX_ATTEMPTS}\xD7 without resolving it \u2014 over to you. Latest read: ${reason}`);
|
|
3931
|
+
}
|
|
3732
3932
|
const action = await p5.select({
|
|
3733
3933
|
message: "What would you like to do?",
|
|
3734
3934
|
options: [
|
|
3735
3935
|
{ value: "retry", label: "Yes, retry \u2014 I fixed my handler code", hint: "Send the same request again" },
|
|
3736
|
-
|
|
3936
|
+
...offerAutofix ? [
|
|
3937
|
+
{
|
|
3938
|
+
value: "autofix",
|
|
3939
|
+
label: "Yes, let the agent fix it from the error",
|
|
3940
|
+
hint: "The error explains itself \u2014 hand it to the agent, no typing"
|
|
3941
|
+
}
|
|
3942
|
+
] : [],
|
|
3943
|
+
{ value: "feedback", label: "Yes, fix the recipe data \u2014 I'll explain what's wrong", hint: "The request data is wrong and I'll describe the change" },
|
|
3737
3944
|
{ value: "skip", label: "No, skip this entity", hint: "Move on to the next entity" }
|
|
3738
3945
|
]
|
|
3739
3946
|
});
|
|
3740
3947
|
if (p5.isCancel(action)) throw new Error("Entity loop cancelled");
|
|
3741
3948
|
if (action === "skip") return "skip";
|
|
3742
3949
|
if (action === "retry") return "retry";
|
|
3950
|
+
if (action === "autofix") {
|
|
3951
|
+
ctx.budget.attempts++;
|
|
3952
|
+
return seedFeedbackFromError(errorContext);
|
|
3953
|
+
}
|
|
3743
3954
|
const fb = await p5.text({
|
|
3744
3955
|
message: "What's wrong with the recipe data?",
|
|
3745
3956
|
placeholder: "e.g. Transaction references acc_1 but Account uses account_1 as its alias"
|
|
3746
3957
|
});
|
|
3747
3958
|
if (p5.isCancel(fb)) throw new Error("Entity loop cancelled");
|
|
3748
|
-
const errorContext = errorBody ? `
|
|
3749
|
-
Server error: ${JSON.stringify(errorBody)}` : "";
|
|
3750
3959
|
return { feedback: `${fb.trim()}${errorContext}` };
|
|
3751
3960
|
}
|
|
3752
|
-
async function testUpDown(entityName, entityIndex, totalEntities, sdkConfig, recipe) {
|
|
3961
|
+
async function testUpDown(entityName, entityIndex, totalEntities, sdkConfig, recipe, grounding) {
|
|
3753
3962
|
p5.log.info(`Let's verify this factory works. We'll send a test request to create ${entityName}, then check the database.`);
|
|
3963
|
+
const failureCtx = { ...grounding, recipe };
|
|
3754
3964
|
while (true) {
|
|
3755
3965
|
const testRunId = `test-${Date.now()}`;
|
|
3756
3966
|
p5.log.step(`[${entityIndex + 1}/${totalEntities}] Sending UP request...`);
|
|
@@ -3760,7 +3970,7 @@ async function testUpDown(entityName, entityIndex, totalEntities, sdkConfig, rec
|
|
|
3760
3970
|
} catch (err) {
|
|
3761
3971
|
p5.log.error(`UP request failed:
|
|
3762
3972
|
${formatException(err)}`);
|
|
3763
|
-
const action = await promptOnFailure(entityName,
|
|
3973
|
+
const action = await promptOnFailure(entityName, formatException(err), failureCtx, "create");
|
|
3764
3974
|
if (action === "skip") return "skip";
|
|
3765
3975
|
if (action === "retry") continue;
|
|
3766
3976
|
return action;
|
|
@@ -3768,7 +3978,7 @@ ${formatException(err)}`);
|
|
|
3768
3978
|
if (!upResult.ok) {
|
|
3769
3979
|
p5.log.error(`UP failed (HTTP ${upResult.status}):`);
|
|
3770
3980
|
console.log(JSON.stringify(upResult.body, null, 2));
|
|
3771
|
-
const action = await promptOnFailure(entityName, upResult.body);
|
|
3981
|
+
const action = await promptOnFailure(entityName, upResult.body, failureCtx, "create", upResult.status);
|
|
3772
3982
|
if (action === "skip") return "skip";
|
|
3773
3983
|
if (action === "retry") continue;
|
|
3774
3984
|
return action;
|
|
@@ -3789,7 +3999,7 @@ ${formatException(err)}`);
|
|
|
3789
3999
|
} catch (err) {
|
|
3790
4000
|
p5.log.error(`DOWN request failed:
|
|
3791
4001
|
${formatException(err)}`);
|
|
3792
|
-
const action = await promptOnFailure(entityName,
|
|
4002
|
+
const action = await promptOnFailure(entityName, formatException(err), failureCtx, "teardown");
|
|
3793
4003
|
if (action === "skip") return "skip";
|
|
3794
4004
|
if (action === "retry") continue;
|
|
3795
4005
|
return action;
|
|
@@ -3797,7 +4007,7 @@ ${formatException(err)}`);
|
|
|
3797
4007
|
if (!downResult.ok) {
|
|
3798
4008
|
p5.log.error(`DOWN failed (HTTP ${downResult.status}):`);
|
|
3799
4009
|
console.log(JSON.stringify(downResult.body, null, 2));
|
|
3800
|
-
const action = await promptOnFailure(entityName, downResult.body);
|
|
4010
|
+
const action = await promptOnFailure(entityName, downResult.body, failureCtx, "teardown", downResult.status);
|
|
3801
4011
|
if (action === "skip") return "skip";
|
|
3802
4012
|
if (action === "retry") continue;
|
|
3803
4013
|
return action;
|
|
@@ -3905,7 +4115,7 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
|
|
|
3905
4115
|
state.sharedSecret = secret;
|
|
3906
4116
|
await saveRecipeState(outputDir, state);
|
|
3907
4117
|
await writeFile8(
|
|
3908
|
-
|
|
4118
|
+
join23(outputDir, "autonoma-config.json"),
|
|
3909
4119
|
JSON.stringify({ sharedSecret: secret, endpointUrl: state.sdkEndpointUrl }, null, 2),
|
|
3910
4120
|
"utf-8"
|
|
3911
4121
|
);
|
|
@@ -3916,7 +4126,7 @@ Add this to your server's .env file and restart it.
|
|
|
3916
4126
|
This is a 64-character hex key used for HMAC-SHA256 request signing.
|
|
3917
4127
|
The same value must be set in both your server and the Autonoma dashboard.
|
|
3918
4128
|
|
|
3919
|
-
Saved to: ${
|
|
4129
|
+
Saved to: ${join23(outputDir, "autonoma-config.json")}`,
|
|
3920
4130
|
"Shared secret generated"
|
|
3921
4131
|
);
|
|
3922
4132
|
const secretReady = await p5.confirm({
|
|
@@ -3938,7 +4148,7 @@ Saved to: ${join22(outputDir, "autonoma-config.json")}`,
|
|
|
3938
4148
|
state.sdkEndpointUrl = url.trim() || "http://localhost:3000/api/autonoma";
|
|
3939
4149
|
await saveRecipeState(outputDir, state);
|
|
3940
4150
|
await writeFile8(
|
|
3941
|
-
|
|
4151
|
+
join23(outputDir, "autonoma-config.json"),
|
|
3942
4152
|
JSON.stringify({ sharedSecret: state.sharedSecret, endpointUrl: state.sdkEndpointUrl }, null, 2),
|
|
3943
4153
|
"utf-8"
|
|
3944
4154
|
);
|
|
@@ -3947,10 +4157,17 @@ Saved to: ${join22(outputDir, "autonoma-config.json")}`,
|
|
|
3947
4157
|
endpointUrl: state.sdkEndpointUrl,
|
|
3948
4158
|
sharedSecret: state.sharedSecret
|
|
3949
4159
|
};
|
|
4160
|
+
const autofixBudget = { attempts: 0 };
|
|
4161
|
+
const grounding = {
|
|
4162
|
+
model,
|
|
4163
|
+
budget: autofixBudget,
|
|
4164
|
+
validRefAliases: summarizeCompletedAliases(state.entities, entityName),
|
|
4165
|
+
entityAudit: summarizeEntityAudit(models.find((m) => m.name === entityName))
|
|
4166
|
+
};
|
|
3950
4167
|
let testDone = false;
|
|
3951
4168
|
while (!testDone) {
|
|
3952
4169
|
const singleRecipe = buildSingleEntityRecipe(entityName, models, state.entityOrder, state.entities);
|
|
3953
|
-
const testResult = await testUpDown(entityName, i, total, sdkConfig, singleRecipe);
|
|
4170
|
+
const testResult = await testUpDown(entityName, i, total, sdkConfig, singleRecipe, grounding);
|
|
3954
4171
|
if (testResult === "success") {
|
|
3955
4172
|
state.entities[entityName].status = "tested-down";
|
|
3956
4173
|
p5.log.success(`[${i + 1}/${total}] ${entityName} \u2014 factory verified`);
|
|
@@ -3983,7 +4200,7 @@ Saved to: ${join22(outputDir, "autonoma-config.json")}`,
|
|
|
3983
4200
|
await saveRecipeState(outputDir, state);
|
|
3984
4201
|
}
|
|
3985
4202
|
}
|
|
3986
|
-
var PROPOSAL_PROMPT, INSTRUCTIONS_PROMPT;
|
|
4203
|
+
var PROPOSAL_PROMPT, INSTRUCTIONS_PROMPT, MAX_AUTOFIX_ATTEMPTS;
|
|
3987
4204
|
var init_entity_loop = __esm({
|
|
3988
4205
|
"src/agents/04-recipe-builder/phases/entity-loop.ts"() {
|
|
3989
4206
|
"use strict";
|
|
@@ -3999,6 +4216,7 @@ var init_entity_loop = __esm({
|
|
|
3999
4216
|
init_notify();
|
|
4000
4217
|
init_recipe();
|
|
4001
4218
|
init_http_client();
|
|
4219
|
+
init_failure_classifier();
|
|
4002
4220
|
PROPOSAL_PROMPT = `You are a recipe data designer. Given an entity from the entity audit and the scenario data, produce a JSON array of records for this entity.
|
|
4003
4221
|
|
|
4004
4222
|
Rules:
|
|
@@ -4026,19 +4244,20 @@ Include:
|
|
|
4026
4244
|
Be specific \u2014 reference actual file paths, function names, and types from the codebase.
|
|
4027
4245
|
|
|
4028
4246
|
When done, call finish with the instructions text.`;
|
|
4247
|
+
MAX_AUTOFIX_ATTEMPTS = 2;
|
|
4029
4248
|
}
|
|
4030
4249
|
});
|
|
4031
4250
|
|
|
4032
4251
|
// src/agents/04-recipe-builder/phases/full-validation.ts
|
|
4033
4252
|
import * as p6 from "@clack/prompts";
|
|
4034
4253
|
import { tool as tool15 } from "ai";
|
|
4035
|
-
import { z as
|
|
4254
|
+
import { z as z18 } from "zod";
|
|
4036
4255
|
async function reviseFullRecipe(current, feedback, model, outputDir, entityOrder) {
|
|
4037
4256
|
let revised;
|
|
4038
4257
|
const finishTool = tool15({
|
|
4039
4258
|
description: "Submit the revised full recipe: an object mapping each entity name to its array of records.",
|
|
4040
|
-
inputSchema:
|
|
4041
|
-
recipe:
|
|
4259
|
+
inputSchema: z18.object({
|
|
4260
|
+
recipe: z18.record(z18.string(), z18.array(z18.record(z18.string(), z18.unknown())))
|
|
4042
4261
|
}),
|
|
4043
4262
|
execute: async (input) => {
|
|
4044
4263
|
revised = input.recipe;
|
|
@@ -4271,7 +4490,7 @@ __export(recipe_builder_exports, {
|
|
|
4271
4490
|
runRecipeBuilder: () => runRecipeBuilder
|
|
4272
4491
|
});
|
|
4273
4492
|
import { readFile as readFile17 } from "fs/promises";
|
|
4274
|
-
import { join as
|
|
4493
|
+
import { join as join24 } from "path";
|
|
4275
4494
|
import * as p8 from "@clack/prompts";
|
|
4276
4495
|
async function runRecipeBuilder(input) {
|
|
4277
4496
|
const model = getModel(input.modelId);
|
|
@@ -4289,7 +4508,7 @@ async function runRecipeBuilder(input) {
|
|
|
4289
4508
|
);
|
|
4290
4509
|
let importanceRank;
|
|
4291
4510
|
try {
|
|
4292
|
-
const auditMarkdown = await readFile17(
|
|
4511
|
+
const auditMarkdown = await readFile17(join24(input.outputDir, "entity-audit.md"), "utf-8");
|
|
4293
4512
|
importanceRank = await rankEntitiesByImportance(models, auditMarkdown, model);
|
|
4294
4513
|
} catch {
|
|
4295
4514
|
importanceRank = void 0;
|
|
@@ -4391,22 +4610,22 @@ var init_recipe_builder = __esm({
|
|
|
4391
4610
|
});
|
|
4392
4611
|
|
|
4393
4612
|
// src/agents/05-test-generator/rubrics.ts
|
|
4394
|
-
import { z as
|
|
4613
|
+
import { z as z19 } from "zod";
|
|
4395
4614
|
var dimensionResultSchema, structuralIntentRubric, flowCompletenessRubric, uiTextRubric, dataAccuracyRubric, ALL_RUBRICS;
|
|
4396
4615
|
var init_rubrics = __esm({
|
|
4397
4616
|
"src/agents/05-test-generator/rubrics.ts"() {
|
|
4398
4617
|
"use strict";
|
|
4399
4618
|
init_esm_shims();
|
|
4400
|
-
dimensionResultSchema =
|
|
4401
|
-
pass:
|
|
4402
|
-
evidence:
|
|
4403
|
-
suggestion:
|
|
4619
|
+
dimensionResultSchema = z19.object({
|
|
4620
|
+
pass: z19.boolean(),
|
|
4621
|
+
evidence: z19.string().describe("What you checked and found \u2014 cite file paths, line content, or specific strings"),
|
|
4622
|
+
suggestion: z19.string().optional().describe("What the planner agent should fix, if failing")
|
|
4404
4623
|
});
|
|
4405
4624
|
structuralIntentRubric = {
|
|
4406
4625
|
name: "structural-intent",
|
|
4407
4626
|
maxSteps: 8,
|
|
4408
4627
|
dimensions: ["structuralValidity", "intentQuality", "missionAlignment"],
|
|
4409
|
-
resultSchema:
|
|
4628
|
+
resultSchema: z19.object({
|
|
4410
4629
|
structuralValidity: dimensionResultSchema.describe(
|
|
4411
4630
|
"Are all step verbs valid (click/type/scroll/assert/hover/drag/read/refresh)? Are asserts visual-only (no URLs, network, console)? No code selectors? No login steps?"
|
|
4412
4631
|
),
|
|
@@ -4447,7 +4666,7 @@ When done reviewing, call finish with your structured evaluation.`
|
|
|
4447
4666
|
name: "flow-completeness",
|
|
4448
4667
|
maxSteps: 12,
|
|
4449
4668
|
dimensions: ["actionCompletion", "mutationVerification"],
|
|
4450
|
-
resultSchema:
|
|
4669
|
+
resultSchema: z19.object({
|
|
4451
4670
|
actionCompletion: dimensionResultSchema.describe(
|
|
4452
4671
|
"Does the test complete a core action and reach an OUTCOME? Not just opening a modal or clicking a tab."
|
|
4453
4672
|
),
|
|
@@ -4483,7 +4702,7 @@ When done reviewing, call finish with your structured evaluation.`
|
|
|
4483
4702
|
name: "ui-text",
|
|
4484
4703
|
maxSteps: 20,
|
|
4485
4704
|
dimensions: ["uiTextAuthenticity"],
|
|
4486
|
-
resultSchema:
|
|
4705
|
+
resultSchema: z19.object({
|
|
4487
4706
|
uiTextAuthenticity: dimensionResultSchema.describe(
|
|
4488
4707
|
"Do all quoted strings in steps reference text a human would actually see on screen? Not translation keys, config paths, component names, enum identifiers, or CSS classes."
|
|
4489
4708
|
)
|
|
@@ -4522,7 +4741,7 @@ When done reviewing, call finish with your structured evaluation.`
|
|
|
4522
4741
|
name: "data-accuracy",
|
|
4523
4742
|
maxSteps: 20,
|
|
4524
4743
|
dimensions: ["dataAccuracy"],
|
|
4525
|
-
resultSchema:
|
|
4744
|
+
resultSchema: z19.object({
|
|
4526
4745
|
dataAccuracy: dimensionResultSchema.describe(
|
|
4527
4746
|
"Do the referenced UI elements (buttons, labels, fields, headings, toasts) actually exist in the source code for this page? Are default states correct? Does all test data (names, values, entities) come from the scenario data \u2014 NOT from other tests?"
|
|
4528
4747
|
)
|
|
@@ -4638,7 +4857,7 @@ var init_review_pass = __esm({
|
|
|
4638
4857
|
|
|
4639
4858
|
// src/agents/05-test-generator/review.ts
|
|
4640
4859
|
import { readFile as readFile18 } from "fs/promises";
|
|
4641
|
-
import { join as
|
|
4860
|
+
import { join as join25, relative as relative6, basename as basename3 } from "path";
|
|
4642
4861
|
import { glob as glob5 } from "glob";
|
|
4643
4862
|
import "ai";
|
|
4644
4863
|
async function reviewSingleTest(testContent, testPath, projectRoot, model, scenarioData) {
|
|
@@ -4665,14 +4884,14 @@ async function reviewSingleTest(testContent, testPath, projectRoot, model, scena
|
|
|
4665
4884
|
return merged;
|
|
4666
4885
|
}
|
|
4667
4886
|
async function runConsolidatedReview(outputDir, projectRoot, model) {
|
|
4668
|
-
const testsDir =
|
|
4887
|
+
const testsDir = join25(outputDir, "qa-tests");
|
|
4669
4888
|
const logger = createStepLogger("review", 5);
|
|
4670
4889
|
let scenarioData;
|
|
4671
4890
|
try {
|
|
4672
|
-
scenarioData = await readFile18(
|
|
4891
|
+
scenarioData = await readFile18(join25(outputDir, "scenarios.md"), "utf-8");
|
|
4673
4892
|
} catch {
|
|
4674
4893
|
}
|
|
4675
|
-
const testFiles = await glob5(
|
|
4894
|
+
const testFiles = await glob5(join25(testsDir, "**/*.md"));
|
|
4676
4895
|
const tests = [];
|
|
4677
4896
|
for (const testPath of testFiles) {
|
|
4678
4897
|
if (basename3(testPath) === "INDEX.md") continue;
|
|
@@ -4755,13 +4974,13 @@ var init_review2 = __esm({
|
|
|
4755
4974
|
|
|
4756
4975
|
// src/agents/05-test-generator/graph.ts
|
|
4757
4976
|
import { readFile as readFile19, writeFile as writeFile9 } from "fs/promises";
|
|
4758
|
-
import { join as
|
|
4977
|
+
import { join as join26 } from "path";
|
|
4759
4978
|
async function saveBfsState(outputDir, state) {
|
|
4760
|
-
const path3 =
|
|
4979
|
+
const path3 = join26(outputDir, STATE_FILE3);
|
|
4761
4980
|
await writeFile9(path3, JSON.stringify(state.serialize(), null, 2), "utf-8");
|
|
4762
4981
|
}
|
|
4763
4982
|
async function loadBfsState(outputDir) {
|
|
4764
|
-
const path3 =
|
|
4983
|
+
const path3 = join26(outputDir, STATE_FILE3);
|
|
4765
4984
|
try {
|
|
4766
4985
|
const raw = await readFile19(path3, "utf-8");
|
|
4767
4986
|
return CoverageState.deserialize(JSON.parse(raw));
|
|
@@ -4856,16 +5075,16 @@ var init_graph = __esm({
|
|
|
4856
5075
|
|
|
4857
5076
|
// src/agents/00b-feature-discovery/index.ts
|
|
4858
5077
|
import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
4859
|
-
import { join as
|
|
4860
|
-
import { z as
|
|
5078
|
+
import { join as join27 } from "path";
|
|
5079
|
+
import { z as z20 } from "zod";
|
|
4861
5080
|
import { tool as tool17 } from "ai";
|
|
4862
5081
|
async function saveFeatures(outputDir, features) {
|
|
4863
5082
|
const obj = Object.fromEntries(features);
|
|
4864
|
-
await writeFile10(
|
|
5083
|
+
await writeFile10(join27(outputDir, FEATURES_FILE), JSON.stringify(obj, null, 2), "utf-8");
|
|
4865
5084
|
}
|
|
4866
5085
|
async function loadFeatures(outputDir) {
|
|
4867
5086
|
try {
|
|
4868
|
-
const raw = await readFile20(
|
|
5087
|
+
const raw = await readFile20(join27(outputDir, FEATURES_FILE), "utf-8");
|
|
4869
5088
|
const obj = JSON.parse(raw);
|
|
4870
5089
|
return new Map(Object.entries(obj));
|
|
4871
5090
|
} catch {
|
|
@@ -4899,7 +5118,7 @@ Process every page. Call add_feature for each sub-feature you discover. When don
|
|
|
4899
5118
|
add_feature: tool17({
|
|
4900
5119
|
description: "Add a discovered sub-feature",
|
|
4901
5120
|
inputSchema: Feature.extend({
|
|
4902
|
-
id:
|
|
5121
|
+
id: z20.string().min(1).describe("Unique kebab-case ID (e.g. 'settings-notifications-tab')")
|
|
4903
5122
|
}),
|
|
4904
5123
|
execute: (featureInput) => {
|
|
4905
5124
|
const { id, ...rest } = featureInput;
|
|
@@ -4913,17 +5132,17 @@ Process every page. Call add_feature for each sub-feature you discover. When don
|
|
|
4913
5132
|
}),
|
|
4914
5133
|
view_features: tool17({
|
|
4915
5134
|
description: "View all discovered features so far",
|
|
4916
|
-
inputSchema:
|
|
5135
|
+
inputSchema: z20.object({}),
|
|
4917
5136
|
execute: () => collector.viewFeatures()
|
|
4918
5137
|
}),
|
|
4919
5138
|
view_pages: tool17({
|
|
4920
5139
|
description: "View the pages list to know what to analyze",
|
|
4921
|
-
inputSchema:
|
|
5140
|
+
inputSchema: z20.object({}),
|
|
4922
5141
|
execute: () => pagesDescription
|
|
4923
5142
|
}),
|
|
4924
5143
|
finish: tool17({
|
|
4925
5144
|
description: "Signal that feature discovery is complete",
|
|
4926
|
-
inputSchema:
|
|
5145
|
+
inputSchema: z20.object({ summary: z20.string() }),
|
|
4927
5146
|
execute: async (finishInput) => {
|
|
4928
5147
|
result = {
|
|
4929
5148
|
success: true,
|
|
@@ -4954,13 +5173,13 @@ var init_b_feature_discovery = __esm({
|
|
|
4954
5173
|
init_model();
|
|
4955
5174
|
init_tools();
|
|
4956
5175
|
FEATURES_FILE = "features.json";
|
|
4957
|
-
Feature =
|
|
4958
|
-
name:
|
|
4959
|
-
type:
|
|
4960
|
-
parentPagePath:
|
|
4961
|
-
sourceFiles:
|
|
4962
|
-
interactiveElements:
|
|
4963
|
-
description:
|
|
5176
|
+
Feature = z20.object({
|
|
5177
|
+
name: z20.string().min(1).describe("Human-readable name (e.g. 'Settings > Notifications Tab', 'Create Project Modal')"),
|
|
5178
|
+
type: z20.enum(["tab", "modal", "form", "table", "wizard", "nested-route", "complex-component"]),
|
|
5179
|
+
parentPagePath: z20.string().min(1).describe("The page path this feature belongs to (from the pages list)"),
|
|
5180
|
+
sourceFiles: z20.array(z20.string()).min(1).describe("Relative paths to the source files for this sub-feature"),
|
|
5181
|
+
interactiveElements: z20.number().int().min(0).describe("Count of interactive elements found (buttons, inputs, toggles, etc.)"),
|
|
5182
|
+
description: z20.string().min(10).describe("What this sub-feature does")
|
|
4964
5183
|
});
|
|
4965
5184
|
FeatureCollector = class {
|
|
4966
5185
|
features = /* @__PURE__ */ new Map();
|
|
@@ -5041,14 +5260,14 @@ Use kebab-case IDs that indicate the parent page and feature type:
|
|
|
5041
5260
|
});
|
|
5042
5261
|
|
|
5043
5262
|
// src/agents/05-test-generator/validation.ts
|
|
5044
|
-
import
|
|
5263
|
+
import matter4 from "gray-matter";
|
|
5045
5264
|
function validateTestContent(content) {
|
|
5046
5265
|
const errors = [];
|
|
5047
5266
|
if (!/^---\n[\s\S]*?\n---/.test(content)) {
|
|
5048
5267
|
errors.push("Missing frontmatter");
|
|
5049
5268
|
} else {
|
|
5050
5269
|
try {
|
|
5051
|
-
const { data } =
|
|
5270
|
+
const { data } = matter4(content);
|
|
5052
5271
|
if (!data.verification || typeof data.verification !== "string" || data.verification.length < 20) {
|
|
5053
5272
|
errors.push("Missing or insufficient 'verification' field in frontmatter \u2014 must describe WHERE to navigate and WHAT to assert at the source of truth");
|
|
5054
5273
|
}
|
|
@@ -5103,18 +5322,18 @@ var init_validation = __esm({
|
|
|
5103
5322
|
|
|
5104
5323
|
// src/agents/05-test-generator/tools.ts
|
|
5105
5324
|
import { mkdir as mkdir3, writeFile as writeFile11 } from "fs/promises";
|
|
5106
|
-
import { dirname as
|
|
5325
|
+
import { dirname as dirname3, join as join28 } from "path";
|
|
5107
5326
|
import { hasToolCall as hasToolCall3, stepCountIs as stepCountIs3, tool as tool18, ToolLoopAgent as ToolLoopAgent3 } from "ai";
|
|
5108
|
-
import
|
|
5109
|
-
import { z as
|
|
5327
|
+
import matter5 from "gray-matter";
|
|
5328
|
+
import { z as z21 } from "zod";
|
|
5110
5329
|
function buildWriteTestTool(state, outputDir) {
|
|
5111
5330
|
return tool18({
|
|
5112
5331
|
description: "Write a test file to qa-tests/{folder}/{filename}.md. Validates frontmatter before writing. Returns error if frontmatter is invalid.",
|
|
5113
|
-
inputSchema:
|
|
5114
|
-
folder:
|
|
5115
|
-
filename:
|
|
5116
|
-
content:
|
|
5117
|
-
nodeId:
|
|
5332
|
+
inputSchema: z21.object({
|
|
5333
|
+
folder: z21.string().describe("Subfolder name under qa-tests/"),
|
|
5334
|
+
filename: z21.string().describe("File name (e.g. login-valid-credentials.md)"),
|
|
5335
|
+
content: z21.string().describe("Full file content including YAML frontmatter"),
|
|
5336
|
+
nodeId: z21.string().describe("The FeatureNode ID this test belongs to")
|
|
5118
5337
|
}),
|
|
5119
5338
|
execute: async (input) => {
|
|
5120
5339
|
const frontmatter = extractFrontmatter(input.content);
|
|
@@ -5163,10 +5382,10 @@ function buildWriteTestTool(state, outputDir) {
|
|
|
5163
5382
|
};
|
|
5164
5383
|
}
|
|
5165
5384
|
}
|
|
5166
|
-
const relPath =
|
|
5167
|
-
const absPath =
|
|
5385
|
+
const relPath = join28("qa-tests", input.folder, input.filename);
|
|
5386
|
+
const absPath = join28(outputDir, relPath);
|
|
5168
5387
|
try {
|
|
5169
|
-
await mkdir3(
|
|
5388
|
+
await mkdir3(dirname3(absPath), { recursive: true });
|
|
5170
5389
|
await writeFile11(absPath, input.content, "utf-8");
|
|
5171
5390
|
state.markTested(input.nodeId, [relPath]);
|
|
5172
5391
|
await saveBfsState(outputDir, state);
|
|
@@ -5181,14 +5400,14 @@ function buildWriteTestTool(state, outputDir) {
|
|
|
5181
5400
|
function buildCreateFolderTool(outputDir) {
|
|
5182
5401
|
return tool18({
|
|
5183
5402
|
description: "Create a folder under qa-tests/ for organizing tests.",
|
|
5184
|
-
inputSchema:
|
|
5185
|
-
folder:
|
|
5403
|
+
inputSchema: z21.object({
|
|
5404
|
+
folder: z21.string().describe("Folder name (kebab-case)")
|
|
5186
5405
|
}),
|
|
5187
5406
|
execute: async (input) => {
|
|
5188
|
-
const absPath =
|
|
5407
|
+
const absPath = join28(outputDir, "qa-tests", input.folder);
|
|
5189
5408
|
try {
|
|
5190
5409
|
await mkdir3(absPath, { recursive: true });
|
|
5191
|
-
return { path:
|
|
5410
|
+
return { path: join28("qa-tests", input.folder) };
|
|
5192
5411
|
} catch (err) {
|
|
5193
5412
|
const message = err instanceof Error ? err.message : String(err);
|
|
5194
5413
|
return { error: `Failed to create folder: ${message}` };
|
|
@@ -5199,7 +5418,7 @@ function buildCreateFolderTool(outputDir) {
|
|
|
5199
5418
|
function buildNextNodeTool(state, outputDir) {
|
|
5200
5419
|
return tool18({
|
|
5201
5420
|
description: "Get the next node to write tests for. If you called next_node before without writing any tests (via write_test), the previous node is auto-skipped. Returns done:true when all nodes are processed.",
|
|
5202
|
-
inputSchema:
|
|
5421
|
+
inputSchema: z21.object({}),
|
|
5203
5422
|
execute: async () => {
|
|
5204
5423
|
const next = state.nextNode();
|
|
5205
5424
|
await saveBfsState(outputDir, state);
|
|
@@ -5228,7 +5447,7 @@ function buildNextNodeTool(state, outputDir) {
|
|
|
5228
5447
|
function buildGetProgressTool(state) {
|
|
5229
5448
|
return tool18({
|
|
5230
5449
|
description: "Check how many nodes have been tested vs how many remain.",
|
|
5231
|
-
inputSchema:
|
|
5450
|
+
inputSchema: z21.object({}),
|
|
5232
5451
|
execute: async () => {
|
|
5233
5452
|
const stats = state.summary();
|
|
5234
5453
|
const nodes = [...state.nodes.values()].map((n) => ({
|
|
@@ -5244,12 +5463,12 @@ function buildGetProgressTool(state) {
|
|
|
5244
5463
|
function buildSpawnResearcherTool(model, workingDirectory, onHeartbeat) {
|
|
5245
5464
|
return tool18({
|
|
5246
5465
|
description: "Spawn a research subagent to read and analyze source files without polluting your context. Use for complex sub-features where you don't want to read 20 files yourself.",
|
|
5247
|
-
inputSchema:
|
|
5248
|
-
instruction:
|
|
5466
|
+
inputSchema: z21.object({
|
|
5467
|
+
instruction: z21.string().describe("What to research \u2014 be specific about files and what to look for")
|
|
5249
5468
|
}),
|
|
5250
5469
|
execute: async (input) => {
|
|
5251
|
-
const resultSchema2 =
|
|
5252
|
-
findings:
|
|
5470
|
+
const resultSchema2 = z21.object({
|
|
5471
|
+
findings: z21.string().describe("Summary of what was found")
|
|
5253
5472
|
});
|
|
5254
5473
|
let result;
|
|
5255
5474
|
const subagent = new ToolLoopAgent3({
|
|
@@ -5287,7 +5506,7 @@ function buildSpawnResearcherTool(model, workingDirectory, onHeartbeat) {
|
|
|
5287
5506
|
}
|
|
5288
5507
|
function extractFrontmatter(content) {
|
|
5289
5508
|
try {
|
|
5290
|
-
const { data } =
|
|
5509
|
+
const { data } = matter5(content);
|
|
5291
5510
|
return data && Object.keys(data).length > 0 ? data : null;
|
|
5292
5511
|
} catch {
|
|
5293
5512
|
return null;
|
|
@@ -5301,14 +5520,14 @@ var init_tools2 = __esm({
|
|
|
5301
5520
|
init_tools();
|
|
5302
5521
|
init_graph();
|
|
5303
5522
|
init_validation();
|
|
5304
|
-
testFrontmatterSchema =
|
|
5305
|
-
title:
|
|
5306
|
-
description:
|
|
5307
|
-
intent:
|
|
5308
|
-
criticality:
|
|
5309
|
-
scenario:
|
|
5310
|
-
flow:
|
|
5311
|
-
verification:
|
|
5523
|
+
testFrontmatterSchema = z21.object({
|
|
5524
|
+
title: z21.string().min(1),
|
|
5525
|
+
description: z21.string().min(1),
|
|
5526
|
+
intent: z21.string().min(30, "Intent must be at least 30 characters \u2014 describe the BEHAVIOR being tested, not the steps"),
|
|
5527
|
+
criticality: z21.enum(["critical", "high", "mid", "low"]),
|
|
5528
|
+
scenario: z21.string().min(1),
|
|
5529
|
+
flow: z21.string().min(1),
|
|
5530
|
+
verification: z21.string().min(20, "Verification must describe WHERE to navigate and WHAT to assert at the source of truth \u2014 not UI acknowledgments like toasts")
|
|
5312
5531
|
});
|
|
5313
5532
|
}
|
|
5314
5533
|
});
|
|
@@ -5705,9 +5924,9 @@ __export(test_generator_exports, {
|
|
|
5705
5924
|
runTestGenerator: () => runTestGenerator
|
|
5706
5925
|
});
|
|
5707
5926
|
import { mkdir as mkdir4, readFile as readFile21, rmdir, unlink, writeFile as writeFile12 } from "fs/promises";
|
|
5708
|
-
import { basename as basename4, join as
|
|
5927
|
+
import { basename as basename4, join as join29 } from "path";
|
|
5709
5928
|
import { tool as tool19 } from "ai";
|
|
5710
|
-
import { z as
|
|
5929
|
+
import { z as z22 } from "zod";
|
|
5711
5930
|
import { glob as glob6 } from "glob";
|
|
5712
5931
|
async function preseedQueue(state, projectRoot, pages, features) {
|
|
5713
5932
|
let seeded = 0;
|
|
@@ -5757,8 +5976,8 @@ async function runTestGenerator(input) {
|
|
|
5757
5976
|
let result;
|
|
5758
5977
|
const finishTool = tool19({
|
|
5759
5978
|
description: "Call when the BFS queue is empty and all routes have been explored.",
|
|
5760
|
-
inputSchema:
|
|
5761
|
-
summary:
|
|
5979
|
+
inputSchema: z22.object({
|
|
5980
|
+
summary: z22.string().describe("Coverage summary")
|
|
5762
5981
|
}),
|
|
5763
5982
|
execute: async (finishInput) => {
|
|
5764
5983
|
const stats = state.summary();
|
|
@@ -5788,7 +6007,7 @@ async function runTestGenerator(input) {
|
|
|
5788
6007
|
let kbContext = "";
|
|
5789
6008
|
try {
|
|
5790
6009
|
const autonomaMd = await readFile21(
|
|
5791
|
-
|
|
6010
|
+
join29(input.outputDir, "AUTONOMA.md"),
|
|
5792
6011
|
"utf-8"
|
|
5793
6012
|
);
|
|
5794
6013
|
kbContext += `
|
|
@@ -5800,7 +6019,7 @@ ${autonomaMd}
|
|
|
5800
6019
|
}
|
|
5801
6020
|
try {
|
|
5802
6021
|
const scenariosMd = await readFile21(
|
|
5803
|
-
|
|
6022
|
+
join29(input.outputDir, "scenarios.md"),
|
|
5804
6023
|
"utf-8"
|
|
5805
6024
|
);
|
|
5806
6025
|
kbContext += `
|
|
@@ -5996,7 +6215,7 @@ IMPORTANT: Do NOT try to finish early. Process every node via next_node until it
|
|
|
5996
6215
|
console.log(` Fix pass complete`);
|
|
5997
6216
|
}
|
|
5998
6217
|
const allTestFiles = await glob6(
|
|
5999
|
-
|
|
6218
|
+
join29(input.outputDir, "qa-tests", "**/*.md")
|
|
6000
6219
|
);
|
|
6001
6220
|
let markedInvalid = 0;
|
|
6002
6221
|
for (const testPath of allTestFiles) {
|
|
@@ -6005,9 +6224,9 @@ IMPORTANT: Do NOT try to finish early. Process every node via next_node until it
|
|
|
6005
6224
|
const content = await readFile21(testPath, "utf-8");
|
|
6006
6225
|
const validation = validateTestContent(content);
|
|
6007
6226
|
if (!validation.valid) {
|
|
6008
|
-
const invalidDir =
|
|
6227
|
+
const invalidDir = join29(input.outputDir, "qa-tests", "_invalid");
|
|
6009
6228
|
await mkdir4(invalidDir, { recursive: true });
|
|
6010
|
-
const dest =
|
|
6229
|
+
const dest = join29(invalidDir, basename4(testPath));
|
|
6011
6230
|
const annotated = `<!-- VALIDATION ERRORS: ${validation.errors.join("; ")} -->
|
|
6012
6231
|
${content}`;
|
|
6013
6232
|
await writeFile12(dest, annotated, "utf-8");
|
|
@@ -6020,7 +6239,7 @@ ${content}`;
|
|
|
6020
6239
|
` ${markedInvalid} tests still invalid after review cycles \u2014 moved to _invalid/`
|
|
6021
6240
|
);
|
|
6022
6241
|
}
|
|
6023
|
-
const dirs = await glob6(
|
|
6242
|
+
const dirs = await glob6(join29(input.outputDir, "qa-tests", "**/"), {
|
|
6024
6243
|
dot: false
|
|
6025
6244
|
});
|
|
6026
6245
|
for (const dir of dirs.sort((a, b) => b.length - a.length)) {
|
|
@@ -6112,7 +6331,7 @@ async function generateIndex(outputDir, state) {
|
|
|
6112
6331
|
for (const paths of state.testsWritten.values()) {
|
|
6113
6332
|
for (const p10 of paths) {
|
|
6114
6333
|
try {
|
|
6115
|
-
const content2 = await readFile21(
|
|
6334
|
+
const content2 = await readFile21(join29(outputDir, p10), "utf-8");
|
|
6116
6335
|
const critMatch = content2.match(/criticality:\s*(\w+)/);
|
|
6117
6336
|
const critVal = critMatch?.[1] ?? "";
|
|
6118
6337
|
if (critCounts.has(critVal))
|
|
@@ -6163,22 +6382,22 @@ ${folders.map((f) => `| ${f.name} | ${f.test_count} |`).join("\n")}
|
|
|
6163
6382
|
|
|
6164
6383
|
${[...testsByFolder.entries()].flatMap(([_folder, tests]) => tests.map((t) => `- \`${t}\``)).join("\n")}
|
|
6165
6384
|
`;
|
|
6166
|
-
await writeFile12(
|
|
6385
|
+
await writeFile12(join29(outputDir, "qa-tests", "INDEX.md"), content, "utf-8");
|
|
6167
6386
|
}
|
|
6168
6387
|
async function generateJourneyTests(outputDir, model, projectRoot) {
|
|
6169
6388
|
const logger = createStepLogger("journeys", 50);
|
|
6170
6389
|
let autonomaMd = "";
|
|
6171
6390
|
let scenariosMd = "";
|
|
6172
6391
|
try {
|
|
6173
|
-
autonomaMd = await readFile21(
|
|
6392
|
+
autonomaMd = await readFile21(join29(outputDir, "AUTONOMA.md"), "utf-8");
|
|
6174
6393
|
} catch {
|
|
6175
6394
|
}
|
|
6176
6395
|
try {
|
|
6177
|
-
scenariosMd = await readFile21(
|
|
6396
|
+
scenariosMd = await readFile21(join29(outputDir, "scenarios.md"), "utf-8");
|
|
6178
6397
|
} catch {
|
|
6179
6398
|
}
|
|
6180
6399
|
if (!autonomaMd) return 0;
|
|
6181
|
-
const existingTests = await glob6(
|
|
6400
|
+
const existingTests = await glob6(join29(outputDir, "qa-tests", "**/*.md"));
|
|
6182
6401
|
const existingTitles = [];
|
|
6183
6402
|
for (const t of existingTests) {
|
|
6184
6403
|
if (basename4(t) === "INDEX.md") continue;
|
|
@@ -6227,7 +6446,7 @@ Write 5-8 journey tests using the write_test tool with folder "journeys". Then c
|
|
|
6227
6446
|
let journeyResult;
|
|
6228
6447
|
const journeyFinish = tool19({
|
|
6229
6448
|
description: "Signal journey generation is complete.",
|
|
6230
|
-
inputSchema:
|
|
6449
|
+
inputSchema: z22.object({ summary: z22.string() }),
|
|
6231
6450
|
execute: async (finishInput) => {
|
|
6232
6451
|
journeyResult = {
|
|
6233
6452
|
success: true,
|
|
@@ -6289,7 +6508,7 @@ var init_test_generator = __esm({
|
|
|
6289
6508
|
init_esm_shims();
|
|
6290
6509
|
import * as p9 from "@clack/prompts";
|
|
6291
6510
|
import { readFile as readFile22, writeFile as writeFile13 } from "fs/promises";
|
|
6292
|
-
import { join as
|
|
6511
|
+
import { join as join30 } from "path";
|
|
6293
6512
|
|
|
6294
6513
|
// src/config.ts
|
|
6295
6514
|
init_esm_shims();
|
|
@@ -6501,7 +6720,7 @@ init_notify();
|
|
|
6501
6720
|
// src/core/upload.ts
|
|
6502
6721
|
init_esm_shims();
|
|
6503
6722
|
import { readFile as readFile3 } from "fs/promises";
|
|
6504
|
-
import { basename, join as
|
|
6723
|
+
import { basename, join as join8, relative } from "path";
|
|
6505
6724
|
import * as p from "@clack/prompts";
|
|
6506
6725
|
import { glob } from "glob";
|
|
6507
6726
|
|
|
@@ -6509,7 +6728,7 @@ import { glob } from "glob";
|
|
|
6509
6728
|
init_esm_shims();
|
|
6510
6729
|
import { execFile as execFile2 } from "child_process";
|
|
6511
6730
|
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
6512
|
-
import { join as
|
|
6731
|
+
import { join as join7 } from "path";
|
|
6513
6732
|
import { promisify } from "util";
|
|
6514
6733
|
var execFileAsync = promisify(execFile2);
|
|
6515
6734
|
var GIT_INFO_FILE = ".git-info.json";
|
|
@@ -6533,11 +6752,11 @@ async function readGitInfo(projectRoot) {
|
|
|
6533
6752
|
};
|
|
6534
6753
|
}
|
|
6535
6754
|
async function saveGitInfo(outputDir, info) {
|
|
6536
|
-
await writeFile2(
|
|
6755
|
+
await writeFile2(join7(outputDir, GIT_INFO_FILE), JSON.stringify(info, null, 2), "utf-8");
|
|
6537
6756
|
}
|
|
6538
6757
|
async function loadGitInfo(outputDir) {
|
|
6539
6758
|
try {
|
|
6540
|
-
const raw = await readFile2(
|
|
6759
|
+
const raw = await readFile2(join7(outputDir, GIT_INFO_FILE), "utf-8");
|
|
6541
6760
|
const parsed = JSON.parse(raw);
|
|
6542
6761
|
if (typeof parsed === "object" && parsed != null && "sha" in parsed && typeof parsed.sha === "string") {
|
|
6543
6762
|
const branch = "branch" in parsed && typeof parsed.branch === "string" ? parsed.branch : void 0;
|
|
@@ -6556,7 +6775,7 @@ async function readArtifacts(outputDir) {
|
|
|
6556
6775
|
const files = [];
|
|
6557
6776
|
for (const name of ARTIFACT_FILES) {
|
|
6558
6777
|
try {
|
|
6559
|
-
const content = await readFile3(
|
|
6778
|
+
const content = await readFile3(join8(outputDir, name), "utf-8");
|
|
6560
6779
|
files.push({ name, content });
|
|
6561
6780
|
} catch {
|
|
6562
6781
|
}
|
|
@@ -6564,13 +6783,13 @@ async function readArtifacts(outputDir) {
|
|
|
6564
6783
|
return files;
|
|
6565
6784
|
}
|
|
6566
6785
|
async function readTestCases(outputDir) {
|
|
6567
|
-
const testsDir =
|
|
6786
|
+
const testsDir = join8(outputDir, "qa-tests");
|
|
6568
6787
|
const matches = await glob("**/*.md", { cwd: testsDir, nodir: true });
|
|
6569
6788
|
const files = [];
|
|
6570
6789
|
for (const match of matches) {
|
|
6571
6790
|
const name = basename(match);
|
|
6572
6791
|
if (name === "INDEX.md") continue;
|
|
6573
|
-
const content = await readFile3(
|
|
6792
|
+
const content = await readFile3(join8(testsDir, match), "utf-8");
|
|
6574
6793
|
const folderPath = relative(".", match).split("/").slice(0, -1).join("/");
|
|
6575
6794
|
files.push({ name, content, folder: folderPath.length > 0 ? folderPath : void 0 });
|
|
6576
6795
|
}
|
|
@@ -6630,7 +6849,7 @@ async function uploadArtifacts(config, outputDir) {
|
|
|
6630
6849
|
// src/core/state.ts
|
|
6631
6850
|
init_esm_shims();
|
|
6632
6851
|
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
6633
|
-
import { join as
|
|
6852
|
+
import { join as join9 } from "path";
|
|
6634
6853
|
var STATE_FILE = ".pipeline-state.json";
|
|
6635
6854
|
function initialState() {
|
|
6636
6855
|
return {
|
|
@@ -6645,7 +6864,7 @@ function initialState() {
|
|
|
6645
6864
|
};
|
|
6646
6865
|
}
|
|
6647
6866
|
async function loadState(outputDir) {
|
|
6648
|
-
const path3 =
|
|
6867
|
+
const path3 = join9(outputDir, STATE_FILE);
|
|
6649
6868
|
try {
|
|
6650
6869
|
const raw = await readFile4(path3, "utf-8");
|
|
6651
6870
|
return JSON.parse(raw);
|
|
@@ -6654,7 +6873,7 @@ async function loadState(outputDir) {
|
|
|
6654
6873
|
}
|
|
6655
6874
|
}
|
|
6656
6875
|
async function saveState(outputDir, state) {
|
|
6657
|
-
const path3 =
|
|
6876
|
+
const path3 = join9(outputDir, STATE_FILE);
|
|
6658
6877
|
await writeFile3(path3, JSON.stringify(state, null, 2), "utf-8");
|
|
6659
6878
|
}
|
|
6660
6879
|
async function markStep(outputDir, state, step, status) {
|
|
@@ -6675,11 +6894,11 @@ process.setSourceMapsEnabled(true);
|
|
|
6675
6894
|
var PAGES_FILE = "pages.json";
|
|
6676
6895
|
async function savePages(outputDir, pages) {
|
|
6677
6896
|
const obj = Object.fromEntries(pages);
|
|
6678
|
-
await writeFile13(
|
|
6897
|
+
await writeFile13(join30(outputDir, PAGES_FILE), JSON.stringify(obj, null, 2), "utf-8");
|
|
6679
6898
|
}
|
|
6680
6899
|
async function loadPages(outputDir) {
|
|
6681
6900
|
try {
|
|
6682
|
-
const raw = await readFile22(
|
|
6901
|
+
const raw = await readFile22(join30(outputDir, PAGES_FILE), "utf-8");
|
|
6683
6902
|
const obj = JSON.parse(raw);
|
|
6684
6903
|
return new Map(Object.entries(obj));
|
|
6685
6904
|
} catch {
|
|
@@ -6833,6 +7052,7 @@ async function runStep(step, outputDir, state, config, projectContext, nonIntera
|
|
|
6833
7052
|
p9.log.success(`Completed: ${label}`);
|
|
6834
7053
|
}
|
|
6835
7054
|
} catch (err) {
|
|
7055
|
+
if (isUserCancellation(err)) throw err;
|
|
6836
7056
|
state = await markStep(outputDir, state, step, "failed");
|
|
6837
7057
|
const known = describeKnownError(err);
|
|
6838
7058
|
if (known) {
|
|
@@ -7111,7 +7331,7 @@ Continue?`
|
|
|
7111
7331
|
}
|
|
7112
7332
|
}
|
|
7113
7333
|
} catch (err) {
|
|
7114
|
-
if (
|
|
7334
|
+
if (isUserCancellation(err)) {
|
|
7115
7335
|
p9.log.warn("Your progress is saved. Run again with --resume to continue from where you left off.");
|
|
7116
7336
|
return;
|
|
7117
7337
|
}
|
|
@@ -7137,6 +7357,10 @@ Continue?`
|
|
|
7137
7357
|
p9.outro("Done");
|
|
7138
7358
|
}
|
|
7139
7359
|
main().then(() => flushAnalytics()).catch(async (err) => {
|
|
7360
|
+
if (isUserCancellation(err)) {
|
|
7361
|
+
await flushAnalytics();
|
|
7362
|
+
process.exit(0);
|
|
7363
|
+
}
|
|
7140
7364
|
const known = describeKnownError(err);
|
|
7141
7365
|
if (known) {
|
|
7142
7366
|
console.error(`\x1B[31m${known.title}\x1B[0m`);
|