@autonoma-ai/planner 0.1.4-canary.b42e322 → 0.1.4
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 +217 -119
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -134,7 +134,7 @@ function toolCallSummary(name, input) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
function createStepLogger(agentId,
|
|
137
|
+
function createStepLogger(agentId, _maxSteps) {
|
|
138
138
|
const stats = {
|
|
139
139
|
filesRead: 0,
|
|
140
140
|
filesWritten: 0
|
|
@@ -158,26 +158,25 @@ function createStepLogger(agentId, maxSteps) {
|
|
|
158
158
|
console.log(message);
|
|
159
159
|
}
|
|
160
160
|
function log9(info) {
|
|
161
|
-
const stepPrefix = `[${agentId}] ${info.stepNumber + 1}/${maxSteps}`;
|
|
162
161
|
for (const tc of info.toolCalls) {
|
|
163
162
|
const summary2 = toolCallSummary(tc.name, tc.input);
|
|
164
163
|
switch (tc.name) {
|
|
165
164
|
case "read_file":
|
|
166
165
|
case "read_output":
|
|
167
166
|
stats.filesRead++;
|
|
168
|
-
writeSpinner(
|
|
167
|
+
writeSpinner(`reading ${summary2}`);
|
|
169
168
|
break;
|
|
170
169
|
case "glob":
|
|
171
|
-
writeSpinner(
|
|
170
|
+
writeSpinner(`glob ${summary2}`);
|
|
172
171
|
break;
|
|
173
172
|
case "grep":
|
|
174
|
-
writeSpinner(
|
|
173
|
+
writeSpinner(`grep ${summary2}`);
|
|
175
174
|
break;
|
|
176
175
|
case "list_directory":
|
|
177
|
-
writeSpinner(
|
|
176
|
+
writeSpinner(`listing ${summary2}`);
|
|
178
177
|
break;
|
|
179
178
|
case "bash":
|
|
180
|
-
writeSpinner(
|
|
179
|
+
writeSpinner(`bash: ${summary2}`);
|
|
181
180
|
break;
|
|
182
181
|
case "write_file": {
|
|
183
182
|
stats.filesWritten++;
|
|
@@ -197,7 +196,7 @@ function createStepLogger(agentId, maxSteps) {
|
|
|
197
196
|
writePermanent(` ${CYAN}\u2295 subagent: ${summary2}${RESET2}`);
|
|
198
197
|
break;
|
|
199
198
|
default:
|
|
200
|
-
writeSpinner(`${
|
|
199
|
+
writeSpinner(`${tc.name}${summary2 ? " " + summary2 : ""}`);
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
202
|
for (const te of info.toolErrors) {
|
|
@@ -800,7 +799,7 @@ import {
|
|
|
800
799
|
import { z as z6 } from "zod";
|
|
801
800
|
function buildSubagentTools(workingDirectory, onFileRead) {
|
|
802
801
|
const baseReadFile = buildReadFileTool(workingDirectory);
|
|
803
|
-
const
|
|
802
|
+
const readFile23 = onFileRead ? tool6({
|
|
804
803
|
description: baseReadFile.description,
|
|
805
804
|
inputSchema: baseReadFile.inputSchema,
|
|
806
805
|
execute: async (input, options) => {
|
|
@@ -813,7 +812,7 @@ function buildSubagentTools(workingDirectory, onFileRead) {
|
|
|
813
812
|
bash: buildBashTool(workingDirectory),
|
|
814
813
|
glob: buildGlobTool(workingDirectory),
|
|
815
814
|
grep: buildGrepTool(workingDirectory),
|
|
816
|
-
read_file:
|
|
815
|
+
read_file: readFile23
|
|
817
816
|
};
|
|
818
817
|
}
|
|
819
818
|
function buildSubagentTool(model, workingDirectory, onHeartbeat, onFileRead) {
|
|
@@ -984,7 +983,7 @@ async function runPageFinder(input) {
|
|
|
984
983
|
const model = getModel(input.modelId);
|
|
985
984
|
let result;
|
|
986
985
|
const pageCollector = new PageCollector();
|
|
987
|
-
const { logger, onStepFinish } = buildDefaultStepLogger("
|
|
986
|
+
const { logger, onStepFinish } = buildDefaultStepLogger("pages", 150);
|
|
988
987
|
let prompt = `You need to run the search on this directory ${input.projectRoot}.`;
|
|
989
988
|
if (input.extraMessage != null) {
|
|
990
989
|
prompt += `
|
|
@@ -1783,7 +1782,9 @@ async function parseEntityAudit(outputDir) {
|
|
|
1783
1782
|
}
|
|
1784
1783
|
return models;
|
|
1785
1784
|
}
|
|
1786
|
-
function resolveEntityOrder(models) {
|
|
1785
|
+
function resolveEntityOrder(models, importanceRank) {
|
|
1786
|
+
const rankOf = (name) => importanceRank?.get(name) ?? Number.MAX_SAFE_INTEGER;
|
|
1787
|
+
const compare = (a, b) => rankOf(a) - rankOf(b) || a.localeCompare(b);
|
|
1787
1788
|
const factoryModels = models.filter((m) => m.independently_created);
|
|
1788
1789
|
const nameSet = new Set(factoryModels.map((m) => m.name));
|
|
1789
1790
|
const inDegree = /* @__PURE__ */ new Map();
|
|
@@ -1804,7 +1805,7 @@ function resolveEntityOrder(models) {
|
|
|
1804
1805
|
for (const [name, deg] of inDegree) {
|
|
1805
1806
|
if (deg === 0) queue.push(name);
|
|
1806
1807
|
}
|
|
1807
|
-
queue.sort();
|
|
1808
|
+
queue.sort(compare);
|
|
1808
1809
|
const result = [];
|
|
1809
1810
|
while (queue.length > 0) {
|
|
1810
1811
|
const name = queue.shift();
|
|
@@ -1814,7 +1815,7 @@ function resolveEntityOrder(models) {
|
|
|
1814
1815
|
inDegree.set(dep, newDeg);
|
|
1815
1816
|
if (newDeg === 0) {
|
|
1816
1817
|
queue.push(dep);
|
|
1817
|
-
queue.sort();
|
|
1818
|
+
queue.sort(compare);
|
|
1818
1819
|
}
|
|
1819
1820
|
}
|
|
1820
1821
|
}
|
|
@@ -2847,10 +2848,98 @@ var init_state = __esm({
|
|
|
2847
2848
|
}
|
|
2848
2849
|
});
|
|
2849
2850
|
|
|
2851
|
+
// src/agents/04-recipe-builder/entity-relevance.ts
|
|
2852
|
+
import { Output, generateText } from "ai";
|
|
2853
|
+
import { z as z13 } from "zod";
|
|
2854
|
+
async function callRanker(model, prompt) {
|
|
2855
|
+
const result = await generateText({
|
|
2856
|
+
model,
|
|
2857
|
+
prompt,
|
|
2858
|
+
output: Output.object({ schema: rankedSchema })
|
|
2859
|
+
});
|
|
2860
|
+
return result.output.ranked;
|
|
2861
|
+
}
|
|
2862
|
+
function reconcileRanking(canonicalInOrder, aiRanked) {
|
|
2863
|
+
const canonical = new Set(canonicalInOrder);
|
|
2864
|
+
const order = [];
|
|
2865
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2866
|
+
const invented = [];
|
|
2867
|
+
const duplicates = [];
|
|
2868
|
+
for (const name of aiRanked) {
|
|
2869
|
+
if (!canonical.has(name)) {
|
|
2870
|
+
invented.push(name);
|
|
2871
|
+
continue;
|
|
2872
|
+
}
|
|
2873
|
+
if (seen.has(name)) {
|
|
2874
|
+
duplicates.push(name);
|
|
2875
|
+
continue;
|
|
2876
|
+
}
|
|
2877
|
+
seen.add(name);
|
|
2878
|
+
order.push(name);
|
|
2879
|
+
}
|
|
2880
|
+
const missing = canonicalInOrder.filter((name) => !seen.has(name));
|
|
2881
|
+
order.push(...missing);
|
|
2882
|
+
return { order, missing, invented, duplicates };
|
|
2883
|
+
}
|
|
2884
|
+
function buildPrompt(auditMarkdown, feedback) {
|
|
2885
|
+
return `You are ranking the database entities of an application by how foundational they are, so a user can configure them starting from the ones they understand best.
|
|
2886
|
+
|
|
2887
|
+
Rank from MOST important to LEAST important:
|
|
2888
|
+
- HIGH: entities that many others depend on, and entities representing the primary concepts of the domain \u2014 the accounts, users, workspaces/tenants, and core business objects a developer would name first when describing the product.
|
|
2889
|
+
- LOW: peripheral entities \u2014 feature-specific records, integration/audit/logging details, join tables, and narrow or client-specific tables a developer would not have top of mind.
|
|
2890
|
+
|
|
2891
|
+
Use the audit below as your only source of truth. The "created_by" relationships and how often an entity owns/spawns others are strong signals of importance.
|
|
2892
|
+
|
|
2893
|
+
Return EVERY entity name from the audit, each exactly once, ordered most-important first. Use the names exactly as written in the audit. Do not invent, rename, merge, or omit any entity.${feedback ? `
|
|
2894
|
+
|
|
2895
|
+
${feedback}` : ""}
|
|
2896
|
+
|
|
2897
|
+
--- ENTITY AUDIT ---
|
|
2898
|
+
${auditMarkdown}`;
|
|
2899
|
+
}
|
|
2900
|
+
async function rankEntitiesByImportance(models, auditMarkdown, model) {
|
|
2901
|
+
const canonical = models.map((m) => m.name);
|
|
2902
|
+
if (canonical.length === 0) return /* @__PURE__ */ new Map();
|
|
2903
|
+
const acceptableMissing = Math.floor(canonical.length / 2);
|
|
2904
|
+
try {
|
|
2905
|
+
let reconciled = reconcileRanking(canonical, await callRanker(model, buildPrompt(auditMarkdown)));
|
|
2906
|
+
if (reconciled.missing.length > acceptableMissing || reconciled.invented.length > 0 || reconciled.duplicates.length > 0) {
|
|
2907
|
+
const feedbackParts = ["Your previous response had problems. Fix them:"];
|
|
2908
|
+
if (reconciled.missing.length > 0)
|
|
2909
|
+
feedbackParts.push(`- You omitted these entities, include them: ${reconciled.missing.join(", ")}`);
|
|
2910
|
+
if (reconciled.invented.length > 0)
|
|
2911
|
+
feedbackParts.push(`- These names are not in the audit, do not use them: ${reconciled.invented.join(", ")}`);
|
|
2912
|
+
if (reconciled.duplicates.length > 0)
|
|
2913
|
+
feedbackParts.push(`- These were listed more than once, list each exactly once: ${reconciled.duplicates.join(", ")}`);
|
|
2914
|
+
const retry = reconcileRanking(
|
|
2915
|
+
canonical,
|
|
2916
|
+
await callRanker(model, buildPrompt(auditMarkdown, feedbackParts.join("\n")))
|
|
2917
|
+
);
|
|
2918
|
+
if (retry.missing.length <= reconciled.missing.length) reconciled = retry;
|
|
2919
|
+
}
|
|
2920
|
+
return new Map(reconciled.order.map((name, i) => [name, i]));
|
|
2921
|
+
} catch (err) {
|
|
2922
|
+
console.warn(
|
|
2923
|
+
`[recipe-builder] Importance ranking failed, falling back to alphabetical order: ${err instanceof Error ? err.message : String(err)}`
|
|
2924
|
+
);
|
|
2925
|
+
return /* @__PURE__ */ new Map();
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
var rankedSchema;
|
|
2929
|
+
var init_entity_relevance = __esm({
|
|
2930
|
+
"src/agents/04-recipe-builder/entity-relevance.ts"() {
|
|
2931
|
+
"use strict";
|
|
2932
|
+
init_esm_shims();
|
|
2933
|
+
rankedSchema = z13.object({
|
|
2934
|
+
ranked: z13.array(z13.string()).describe("Every entity name, ordered most-important first.")
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
});
|
|
2938
|
+
|
|
2850
2939
|
// src/agents/04-recipe-builder/phases/tech-detect.ts
|
|
2851
2940
|
import * as p4 from "@clack/prompts";
|
|
2852
2941
|
import { tool as tool13 } from "ai";
|
|
2853
|
-
import { z as
|
|
2942
|
+
import { z as z14 } from "zod";
|
|
2854
2943
|
async function detectTechStack(projectRoot, modelId, nonInteractive) {
|
|
2855
2944
|
const model = getModel(modelId);
|
|
2856
2945
|
const ignorePatterns = await loadGitignorePatterns(projectRoot);
|
|
@@ -2858,9 +2947,9 @@ async function detectTechStack(projectRoot, modelId, nonInteractive) {
|
|
|
2858
2947
|
const { logger, onStepFinish } = buildDefaultStepLogger("tech-detect", 10);
|
|
2859
2948
|
const finishTool = tool13({
|
|
2860
2949
|
description: "Report the detected backend technology stack.",
|
|
2861
|
-
inputSchema:
|
|
2862
|
-
language:
|
|
2863
|
-
framework:
|
|
2950
|
+
inputSchema: z14.object({
|
|
2951
|
+
language: z14.string().describe("Programming language: typescript, python, go, ruby, java, php, rust, elixir"),
|
|
2952
|
+
framework: z14.string().describe("Web framework: express, node, hono, web, flask, fastapi, django, gin, rails, rack, spring, laravel, axum, actix, plug")
|
|
2864
2953
|
}),
|
|
2865
2954
|
execute: async (input) => {
|
|
2866
2955
|
detected = input;
|
|
@@ -3132,7 +3221,7 @@ import { join as join22 } from "path";
|
|
|
3132
3221
|
import { tmpdir } from "os";
|
|
3133
3222
|
import { spawn as spawn2 } from "child_process";
|
|
3134
3223
|
import { tool as tool14 } from "ai";
|
|
3135
|
-
import { z as
|
|
3224
|
+
import { z as z15 } from "zod";
|
|
3136
3225
|
function summarizeCompletedAliases(completedEntities, excludeName) {
|
|
3137
3226
|
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");
|
|
3138
3227
|
}
|
|
@@ -3141,8 +3230,8 @@ async function proposeRecipeData(entityName, entityIndex, totalEntities, model,
|
|
|
3141
3230
|
const { logger, onStepFinish } = buildDefaultStepLogger(`propose:${entityName}`, 20);
|
|
3142
3231
|
const finishTool = tool14({
|
|
3143
3232
|
description: "Submit the proposed recipe data as a JSON array of records.",
|
|
3144
|
-
inputSchema:
|
|
3145
|
-
records:
|
|
3233
|
+
inputSchema: z15.object({
|
|
3234
|
+
records: z15.array(z15.record(z15.string(), z15.unknown())).describe("Array of record objects for this entity")
|
|
3146
3235
|
}),
|
|
3147
3236
|
execute: async (input) => {
|
|
3148
3237
|
result = input.records;
|
|
@@ -3183,8 +3272,8 @@ async function reviseRecipeData(entityName, entityIndex, totalEntities, current,
|
|
|
3183
3272
|
let revised;
|
|
3184
3273
|
const finishTool = tool14({
|
|
3185
3274
|
description: "Submit the fixed recipe data.",
|
|
3186
|
-
inputSchema:
|
|
3187
|
-
records:
|
|
3275
|
+
inputSchema: z15.object({
|
|
3276
|
+
records: z15.array(z15.record(z15.string(), z15.unknown()))
|
|
3188
3277
|
}),
|
|
3189
3278
|
execute: async (input) => {
|
|
3190
3279
|
revised = input.records;
|
|
@@ -3240,8 +3329,8 @@ async function generateInstructions(entityName, entityIndex, totalEntities, isFi
|
|
|
3240
3329
|
const { logger, onStepFinish } = buildDefaultStepLogger(`instructions:${entityName}`, 15);
|
|
3241
3330
|
const finishTool = tool14({
|
|
3242
3331
|
description: "Submit the implementation instructions.",
|
|
3243
|
-
inputSchema:
|
|
3244
|
-
instructions:
|
|
3332
|
+
inputSchema: z15.object({
|
|
3333
|
+
instructions: z15.string().describe("Complete, copy-pasteable implementation instructions")
|
|
3245
3334
|
}),
|
|
3246
3335
|
execute: async (input) => {
|
|
3247
3336
|
result = input.instructions;
|
|
@@ -3660,13 +3749,13 @@ When done, call finish with the instructions text.`;
|
|
|
3660
3749
|
// src/agents/04-recipe-builder/phases/full-validation.ts
|
|
3661
3750
|
import * as p6 from "@clack/prompts";
|
|
3662
3751
|
import { tool as tool15 } from "ai";
|
|
3663
|
-
import { z as
|
|
3752
|
+
import { z as z16 } from "zod";
|
|
3664
3753
|
async function reviseFullRecipe(current, feedback, model, outputDir, entityOrder) {
|
|
3665
3754
|
let revised;
|
|
3666
3755
|
const finishTool = tool15({
|
|
3667
3756
|
description: "Submit the revised full recipe: an object mapping each entity name to its array of records.",
|
|
3668
|
-
inputSchema:
|
|
3669
|
-
recipe:
|
|
3757
|
+
inputSchema: z16.object({
|
|
3758
|
+
recipe: z16.record(z16.string(), z16.array(z16.record(z16.string(), z16.unknown())))
|
|
3670
3759
|
}),
|
|
3671
3760
|
execute: async (input) => {
|
|
3672
3761
|
revised = input.recipe;
|
|
@@ -3895,6 +3984,8 @@ var recipe_builder_exports = {};
|
|
|
3895
3984
|
__export(recipe_builder_exports, {
|
|
3896
3985
|
runRecipeBuilder: () => runRecipeBuilder
|
|
3897
3986
|
});
|
|
3987
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
3988
|
+
import { join as join23 } from "path";
|
|
3898
3989
|
import * as p8 from "@clack/prompts";
|
|
3899
3990
|
async function runRecipeBuilder(input) {
|
|
3900
3991
|
const model = getModel(input.modelId);
|
|
@@ -3910,7 +4001,14 @@ async function runRecipeBuilder(input) {
|
|
|
3910
4001
|
input.modelId,
|
|
3911
4002
|
input.nonInteractive
|
|
3912
4003
|
);
|
|
3913
|
-
|
|
4004
|
+
let importanceRank;
|
|
4005
|
+
try {
|
|
4006
|
+
const auditMarkdown = await readFile17(join23(input.outputDir, "entity-audit.md"), "utf-8");
|
|
4007
|
+
importanceRank = await rankEntitiesByImportance(models, auditMarkdown, model);
|
|
4008
|
+
} catch {
|
|
4009
|
+
importanceRank = void 0;
|
|
4010
|
+
}
|
|
4011
|
+
const entityOrder = resolveEntityOrder(models, importanceRank);
|
|
3914
4012
|
state.entityOrder = entityOrder;
|
|
3915
4013
|
state.entities = {};
|
|
3916
4014
|
for (const name of entityOrder) {
|
|
@@ -3926,7 +4024,7 @@ async function runRecipeBuilder(input) {
|
|
|
3926
4024
|
state.sdkEndpointUrl = input.config.sdkEndpointUrl;
|
|
3927
4025
|
}
|
|
3928
4026
|
await saveRecipeState(input.outputDir, state);
|
|
3929
|
-
p8.log.info(`Found ${entityOrder.length} entities needing factories. Processing in dependency order.`);
|
|
4027
|
+
p8.log.info(`Found ${entityOrder.length} entities needing factories. Processing core entities first, in dependency order.`);
|
|
3930
4028
|
}
|
|
3931
4029
|
if (state.phase === "entity-loop") {
|
|
3932
4030
|
await runEntityLoop(
|
|
@@ -3998,6 +4096,7 @@ var init_recipe_builder = __esm({
|
|
|
3998
4096
|
init_model();
|
|
3999
4097
|
init_state();
|
|
4000
4098
|
init_entity_order();
|
|
4099
|
+
init_entity_relevance();
|
|
4001
4100
|
init_tech_detect();
|
|
4002
4101
|
init_entity_loop();
|
|
4003
4102
|
init_full_validation();
|
|
@@ -4006,22 +4105,22 @@ var init_recipe_builder = __esm({
|
|
|
4006
4105
|
});
|
|
4007
4106
|
|
|
4008
4107
|
// src/agents/05-test-generator/rubrics.ts
|
|
4009
|
-
import { z as
|
|
4108
|
+
import { z as z17 } from "zod";
|
|
4010
4109
|
var dimensionResultSchema, structuralIntentRubric, flowCompletenessRubric, uiTextRubric, dataAccuracyRubric, ALL_RUBRICS;
|
|
4011
4110
|
var init_rubrics = __esm({
|
|
4012
4111
|
"src/agents/05-test-generator/rubrics.ts"() {
|
|
4013
4112
|
"use strict";
|
|
4014
4113
|
init_esm_shims();
|
|
4015
|
-
dimensionResultSchema =
|
|
4016
|
-
pass:
|
|
4017
|
-
evidence:
|
|
4018
|
-
suggestion:
|
|
4114
|
+
dimensionResultSchema = z17.object({
|
|
4115
|
+
pass: z17.boolean(),
|
|
4116
|
+
evidence: z17.string().describe("What you checked and found \u2014 cite file paths, line content, or specific strings"),
|
|
4117
|
+
suggestion: z17.string().optional().describe("What the planner agent should fix, if failing")
|
|
4019
4118
|
});
|
|
4020
4119
|
structuralIntentRubric = {
|
|
4021
4120
|
name: "structural-intent",
|
|
4022
4121
|
maxSteps: 8,
|
|
4023
4122
|
dimensions: ["structuralValidity", "intentQuality", "missionAlignment"],
|
|
4024
|
-
resultSchema:
|
|
4123
|
+
resultSchema: z17.object({
|
|
4025
4124
|
structuralValidity: dimensionResultSchema.describe(
|
|
4026
4125
|
"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?"
|
|
4027
4126
|
),
|
|
@@ -4062,7 +4161,7 @@ When done reviewing, call finish with your structured evaluation.`
|
|
|
4062
4161
|
name: "flow-completeness",
|
|
4063
4162
|
maxSteps: 12,
|
|
4064
4163
|
dimensions: ["actionCompletion", "mutationVerification"],
|
|
4065
|
-
resultSchema:
|
|
4164
|
+
resultSchema: z17.object({
|
|
4066
4165
|
actionCompletion: dimensionResultSchema.describe(
|
|
4067
4166
|
"Does the test complete a core action and reach an OUTCOME? Not just opening a modal or clicking a tab."
|
|
4068
4167
|
),
|
|
@@ -4098,7 +4197,7 @@ When done reviewing, call finish with your structured evaluation.`
|
|
|
4098
4197
|
name: "ui-text",
|
|
4099
4198
|
maxSteps: 20,
|
|
4100
4199
|
dimensions: ["uiTextAuthenticity"],
|
|
4101
|
-
resultSchema:
|
|
4200
|
+
resultSchema: z17.object({
|
|
4102
4201
|
uiTextAuthenticity: dimensionResultSchema.describe(
|
|
4103
4202
|
"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."
|
|
4104
4203
|
)
|
|
@@ -4137,7 +4236,7 @@ When done reviewing, call finish with your structured evaluation.`
|
|
|
4137
4236
|
name: "data-accuracy",
|
|
4138
4237
|
maxSteps: 20,
|
|
4139
4238
|
dimensions: ["dataAccuracy"],
|
|
4140
|
-
resultSchema:
|
|
4239
|
+
resultSchema: z17.object({
|
|
4141
4240
|
dataAccuracy: dimensionResultSchema.describe(
|
|
4142
4241
|
"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?"
|
|
4143
4242
|
)
|
|
@@ -4252,8 +4351,8 @@ var init_review_pass = __esm({
|
|
|
4252
4351
|
});
|
|
4253
4352
|
|
|
4254
4353
|
// src/agents/05-test-generator/review.ts
|
|
4255
|
-
import { readFile as
|
|
4256
|
-
import { join as
|
|
4354
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
4355
|
+
import { join as join24, relative as relative6, basename as basename3 } from "path";
|
|
4257
4356
|
import { glob as glob5 } from "glob";
|
|
4258
4357
|
import "ai";
|
|
4259
4358
|
async function reviewSingleTest(testContent, testPath, projectRoot, model, scenarioData) {
|
|
@@ -4280,19 +4379,19 @@ async function reviewSingleTest(testContent, testPath, projectRoot, model, scena
|
|
|
4280
4379
|
return merged;
|
|
4281
4380
|
}
|
|
4282
4381
|
async function runConsolidatedReview(outputDir, projectRoot, model) {
|
|
4283
|
-
const testsDir =
|
|
4382
|
+
const testsDir = join24(outputDir, "qa-tests");
|
|
4284
4383
|
const logger = createStepLogger("review", 5);
|
|
4285
4384
|
let scenarioData;
|
|
4286
4385
|
try {
|
|
4287
|
-
scenarioData = await
|
|
4386
|
+
scenarioData = await readFile18(join24(outputDir, "scenarios.md"), "utf-8");
|
|
4288
4387
|
} catch {
|
|
4289
4388
|
}
|
|
4290
|
-
const testFiles = await glob5(
|
|
4389
|
+
const testFiles = await glob5(join24(testsDir, "**/*.md"));
|
|
4291
4390
|
const tests = [];
|
|
4292
4391
|
for (const testPath of testFiles) {
|
|
4293
4392
|
if (basename3(testPath) === "INDEX.md") continue;
|
|
4294
4393
|
if (testPath.includes("/_invalid/")) continue;
|
|
4295
|
-
const content = await
|
|
4394
|
+
const content = await readFile18(testPath, "utf-8");
|
|
4296
4395
|
const flowMatch = content.match(/^---\n[\s\S]*?flow:\s*["']?([^"'\n]+)["']?\s*\n[\s\S]*?---/m);
|
|
4297
4396
|
tests.push({
|
|
4298
4397
|
path: testPath,
|
|
@@ -4369,16 +4468,16 @@ var init_review2 = __esm({
|
|
|
4369
4468
|
});
|
|
4370
4469
|
|
|
4371
4470
|
// src/agents/05-test-generator/graph.ts
|
|
4372
|
-
import { readFile as
|
|
4373
|
-
import { join as
|
|
4471
|
+
import { readFile as readFile19, writeFile as writeFile9 } from "fs/promises";
|
|
4472
|
+
import { join as join25 } from "path";
|
|
4374
4473
|
async function saveBfsState(outputDir, state) {
|
|
4375
|
-
const path3 =
|
|
4474
|
+
const path3 = join25(outputDir, STATE_FILE3);
|
|
4376
4475
|
await writeFile9(path3, JSON.stringify(state.serialize(), null, 2), "utf-8");
|
|
4377
4476
|
}
|
|
4378
4477
|
async function loadBfsState(outputDir) {
|
|
4379
|
-
const path3 =
|
|
4478
|
+
const path3 = join25(outputDir, STATE_FILE3);
|
|
4380
4479
|
try {
|
|
4381
|
-
const raw = await
|
|
4480
|
+
const raw = await readFile19(path3, "utf-8");
|
|
4382
4481
|
return CoverageState.deserialize(JSON.parse(raw));
|
|
4383
4482
|
} catch {
|
|
4384
4483
|
return null;
|
|
@@ -4470,17 +4569,17 @@ var init_graph = __esm({
|
|
|
4470
4569
|
});
|
|
4471
4570
|
|
|
4472
4571
|
// src/agents/00b-feature-discovery/index.ts
|
|
4473
|
-
import { readFile as
|
|
4474
|
-
import { join as
|
|
4475
|
-
import { z as
|
|
4572
|
+
import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
4573
|
+
import { join as join26 } from "path";
|
|
4574
|
+
import { z as z18 } from "zod";
|
|
4476
4575
|
import { tool as tool17 } from "ai";
|
|
4477
4576
|
async function saveFeatures(outputDir, features) {
|
|
4478
4577
|
const obj = Object.fromEntries(features);
|
|
4479
|
-
await writeFile10(
|
|
4578
|
+
await writeFile10(join26(outputDir, FEATURES_FILE), JSON.stringify(obj, null, 2), "utf-8");
|
|
4480
4579
|
}
|
|
4481
4580
|
async function loadFeatures(outputDir) {
|
|
4482
4581
|
try {
|
|
4483
|
-
const raw = await
|
|
4582
|
+
const raw = await readFile20(join26(outputDir, FEATURES_FILE), "utf-8");
|
|
4484
4583
|
const obj = JSON.parse(raw);
|
|
4485
4584
|
return new Map(Object.entries(obj));
|
|
4486
4585
|
} catch {
|
|
@@ -4514,7 +4613,7 @@ Process every page. Call add_feature for each sub-feature you discover. When don
|
|
|
4514
4613
|
add_feature: tool17({
|
|
4515
4614
|
description: "Add a discovered sub-feature",
|
|
4516
4615
|
inputSchema: Feature.extend({
|
|
4517
|
-
id:
|
|
4616
|
+
id: z18.string().min(1).describe("Unique kebab-case ID (e.g. 'settings-notifications-tab')")
|
|
4518
4617
|
}),
|
|
4519
4618
|
execute: (featureInput) => {
|
|
4520
4619
|
const { id, ...rest } = featureInput;
|
|
@@ -4528,17 +4627,17 @@ Process every page. Call add_feature for each sub-feature you discover. When don
|
|
|
4528
4627
|
}),
|
|
4529
4628
|
view_features: tool17({
|
|
4530
4629
|
description: "View all discovered features so far",
|
|
4531
|
-
inputSchema:
|
|
4630
|
+
inputSchema: z18.object({}),
|
|
4532
4631
|
execute: () => collector.viewFeatures()
|
|
4533
4632
|
}),
|
|
4534
4633
|
view_pages: tool17({
|
|
4535
4634
|
description: "View the pages list to know what to analyze",
|
|
4536
|
-
inputSchema:
|
|
4635
|
+
inputSchema: z18.object({}),
|
|
4537
4636
|
execute: () => pagesDescription
|
|
4538
4637
|
}),
|
|
4539
4638
|
finish: tool17({
|
|
4540
4639
|
description: "Signal that feature discovery is complete",
|
|
4541
|
-
inputSchema:
|
|
4640
|
+
inputSchema: z18.object({ summary: z18.string() }),
|
|
4542
4641
|
execute: async (finishInput) => {
|
|
4543
4642
|
result = {
|
|
4544
4643
|
success: true,
|
|
@@ -4569,13 +4668,13 @@ var init_b_feature_discovery = __esm({
|
|
|
4569
4668
|
init_model();
|
|
4570
4669
|
init_tools();
|
|
4571
4670
|
FEATURES_FILE = "features.json";
|
|
4572
|
-
Feature =
|
|
4573
|
-
name:
|
|
4574
|
-
type:
|
|
4575
|
-
parentPagePath:
|
|
4576
|
-
sourceFiles:
|
|
4577
|
-
interactiveElements:
|
|
4578
|
-
description:
|
|
4671
|
+
Feature = z18.object({
|
|
4672
|
+
name: z18.string().min(1).describe("Human-readable name (e.g. 'Settings > Notifications Tab', 'Create Project Modal')"),
|
|
4673
|
+
type: z18.enum(["tab", "modal", "form", "table", "wizard", "nested-route", "complex-component"]),
|
|
4674
|
+
parentPagePath: z18.string().min(1).describe("The page path this feature belongs to (from the pages list)"),
|
|
4675
|
+
sourceFiles: z18.array(z18.string()).min(1).describe("Relative paths to the source files for this sub-feature"),
|
|
4676
|
+
interactiveElements: z18.number().int().min(0).describe("Count of interactive elements found (buttons, inputs, toggles, etc.)"),
|
|
4677
|
+
description: z18.string().min(10).describe("What this sub-feature does")
|
|
4579
4678
|
});
|
|
4580
4679
|
FeatureCollector = class {
|
|
4581
4680
|
features = /* @__PURE__ */ new Map();
|
|
@@ -4718,18 +4817,18 @@ var init_validation = __esm({
|
|
|
4718
4817
|
|
|
4719
4818
|
// src/agents/05-test-generator/tools.ts
|
|
4720
4819
|
import { mkdir as mkdir3, writeFile as writeFile11 } from "fs/promises";
|
|
4721
|
-
import { dirname as dirname2, join as
|
|
4820
|
+
import { dirname as dirname2, join as join27 } from "path";
|
|
4722
4821
|
import { hasToolCall as hasToolCall3, stepCountIs as stepCountIs3, tool as tool18, ToolLoopAgent as ToolLoopAgent3 } from "ai";
|
|
4723
4822
|
import matter4 from "gray-matter";
|
|
4724
|
-
import { z as
|
|
4823
|
+
import { z as z19 } from "zod";
|
|
4725
4824
|
function buildWriteTestTool(state, outputDir) {
|
|
4726
4825
|
return tool18({
|
|
4727
4826
|
description: "Write a test file to qa-tests/{folder}/{filename}.md. Validates frontmatter before writing. Returns error if frontmatter is invalid.",
|
|
4728
|
-
inputSchema:
|
|
4729
|
-
folder:
|
|
4730
|
-
filename:
|
|
4731
|
-
content:
|
|
4732
|
-
nodeId:
|
|
4827
|
+
inputSchema: z19.object({
|
|
4828
|
+
folder: z19.string().describe("Subfolder name under qa-tests/"),
|
|
4829
|
+
filename: z19.string().describe("File name (e.g. login-valid-credentials.md)"),
|
|
4830
|
+
content: z19.string().describe("Full file content including YAML frontmatter"),
|
|
4831
|
+
nodeId: z19.string().describe("The FeatureNode ID this test belongs to")
|
|
4733
4832
|
}),
|
|
4734
4833
|
execute: async (input) => {
|
|
4735
4834
|
const frontmatter = extractFrontmatter(input.content);
|
|
@@ -4778,8 +4877,8 @@ function buildWriteTestTool(state, outputDir) {
|
|
|
4778
4877
|
};
|
|
4779
4878
|
}
|
|
4780
4879
|
}
|
|
4781
|
-
const relPath =
|
|
4782
|
-
const absPath =
|
|
4880
|
+
const relPath = join27("qa-tests", input.folder, input.filename);
|
|
4881
|
+
const absPath = join27(outputDir, relPath);
|
|
4783
4882
|
try {
|
|
4784
4883
|
await mkdir3(dirname2(absPath), { recursive: true });
|
|
4785
4884
|
await writeFile11(absPath, input.content, "utf-8");
|
|
@@ -4796,14 +4895,14 @@ function buildWriteTestTool(state, outputDir) {
|
|
|
4796
4895
|
function buildCreateFolderTool(outputDir) {
|
|
4797
4896
|
return tool18({
|
|
4798
4897
|
description: "Create a folder under qa-tests/ for organizing tests.",
|
|
4799
|
-
inputSchema:
|
|
4800
|
-
folder:
|
|
4898
|
+
inputSchema: z19.object({
|
|
4899
|
+
folder: z19.string().describe("Folder name (kebab-case)")
|
|
4801
4900
|
}),
|
|
4802
4901
|
execute: async (input) => {
|
|
4803
|
-
const absPath =
|
|
4902
|
+
const absPath = join27(outputDir, "qa-tests", input.folder);
|
|
4804
4903
|
try {
|
|
4805
4904
|
await mkdir3(absPath, { recursive: true });
|
|
4806
|
-
return { path:
|
|
4905
|
+
return { path: join27("qa-tests", input.folder) };
|
|
4807
4906
|
} catch (err) {
|
|
4808
4907
|
const message = err instanceof Error ? err.message : String(err);
|
|
4809
4908
|
return { error: `Failed to create folder: ${message}` };
|
|
@@ -4814,7 +4913,7 @@ function buildCreateFolderTool(outputDir) {
|
|
|
4814
4913
|
function buildNextNodeTool(state, outputDir) {
|
|
4815
4914
|
return tool18({
|
|
4816
4915
|
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.",
|
|
4817
|
-
inputSchema:
|
|
4916
|
+
inputSchema: z19.object({}),
|
|
4818
4917
|
execute: async () => {
|
|
4819
4918
|
const next = state.nextNode();
|
|
4820
4919
|
await saveBfsState(outputDir, state);
|
|
@@ -4843,7 +4942,7 @@ function buildNextNodeTool(state, outputDir) {
|
|
|
4843
4942
|
function buildGetProgressTool(state) {
|
|
4844
4943
|
return tool18({
|
|
4845
4944
|
description: "Check how many nodes have been tested vs how many remain.",
|
|
4846
|
-
inputSchema:
|
|
4945
|
+
inputSchema: z19.object({}),
|
|
4847
4946
|
execute: async () => {
|
|
4848
4947
|
const stats = state.summary();
|
|
4849
4948
|
const nodes = [...state.nodes.values()].map((n) => ({
|
|
@@ -4859,12 +4958,12 @@ function buildGetProgressTool(state) {
|
|
|
4859
4958
|
function buildSpawnResearcherTool(model, workingDirectory, onHeartbeat) {
|
|
4860
4959
|
return tool18({
|
|
4861
4960
|
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.",
|
|
4862
|
-
inputSchema:
|
|
4863
|
-
instruction:
|
|
4961
|
+
inputSchema: z19.object({
|
|
4962
|
+
instruction: z19.string().describe("What to research \u2014 be specific about files and what to look for")
|
|
4864
4963
|
}),
|
|
4865
4964
|
execute: async (input) => {
|
|
4866
|
-
const resultSchema2 =
|
|
4867
|
-
findings:
|
|
4965
|
+
const resultSchema2 = z19.object({
|
|
4966
|
+
findings: z19.string().describe("Summary of what was found")
|
|
4868
4967
|
});
|
|
4869
4968
|
let result;
|
|
4870
4969
|
const subagent = new ToolLoopAgent3({
|
|
@@ -4916,14 +5015,14 @@ var init_tools2 = __esm({
|
|
|
4916
5015
|
init_tools();
|
|
4917
5016
|
init_graph();
|
|
4918
5017
|
init_validation();
|
|
4919
|
-
testFrontmatterSchema =
|
|
4920
|
-
title:
|
|
4921
|
-
description:
|
|
4922
|
-
intent:
|
|
4923
|
-
criticality:
|
|
4924
|
-
scenario:
|
|
4925
|
-
flow:
|
|
4926
|
-
verification:
|
|
5018
|
+
testFrontmatterSchema = z19.object({
|
|
5019
|
+
title: z19.string().min(1),
|
|
5020
|
+
description: z19.string().min(1),
|
|
5021
|
+
intent: z19.string().min(30, "Intent must be at least 30 characters \u2014 describe the BEHAVIOR being tested, not the steps"),
|
|
5022
|
+
criticality: z19.enum(["critical", "high", "mid", "low"]),
|
|
5023
|
+
scenario: z19.string().min(1),
|
|
5024
|
+
flow: z19.string().min(1),
|
|
5025
|
+
verification: z19.string().min(20, "Verification must describe WHERE to navigate and WHAT to assert at the source of truth \u2014 not UI acknowledgments like toasts")
|
|
4927
5026
|
});
|
|
4928
5027
|
}
|
|
4929
5028
|
});
|
|
@@ -5319,10 +5418,10 @@ var test_generator_exports = {};
|
|
|
5319
5418
|
__export(test_generator_exports, {
|
|
5320
5419
|
runTestGenerator: () => runTestGenerator
|
|
5321
5420
|
});
|
|
5322
|
-
import { mkdir as mkdir4, readFile as
|
|
5323
|
-
import { basename as basename4, join as
|
|
5421
|
+
import { mkdir as mkdir4, readFile as readFile21, rmdir, unlink, writeFile as writeFile12 } from "fs/promises";
|
|
5422
|
+
import { basename as basename4, join as join28 } from "path";
|
|
5324
5423
|
import { tool as tool19 } from "ai";
|
|
5325
|
-
import { z as
|
|
5424
|
+
import { z as z20 } from "zod";
|
|
5326
5425
|
import { glob as glob6 } from "glob";
|
|
5327
5426
|
async function preseedQueue(state, projectRoot, pages, features) {
|
|
5328
5427
|
let seeded = 0;
|
|
@@ -5372,8 +5471,8 @@ async function runTestGenerator(input) {
|
|
|
5372
5471
|
let result;
|
|
5373
5472
|
const finishTool = tool19({
|
|
5374
5473
|
description: "Call when the BFS queue is empty and all routes have been explored.",
|
|
5375
|
-
inputSchema:
|
|
5376
|
-
summary:
|
|
5474
|
+
inputSchema: z20.object({
|
|
5475
|
+
summary: z20.string().describe("Coverage summary")
|
|
5377
5476
|
}),
|
|
5378
5477
|
execute: async (finishInput) => {
|
|
5379
5478
|
const stats = state.summary();
|
|
@@ -5402,8 +5501,8 @@ async function runTestGenerator(input) {
|
|
|
5402
5501
|
});
|
|
5403
5502
|
let kbContext = "";
|
|
5404
5503
|
try {
|
|
5405
|
-
const autonomaMd = await
|
|
5406
|
-
|
|
5504
|
+
const autonomaMd = await readFile21(
|
|
5505
|
+
join28(input.outputDir, "AUTONOMA.md"),
|
|
5407
5506
|
"utf-8"
|
|
5408
5507
|
);
|
|
5409
5508
|
kbContext += `
|
|
@@ -5414,8 +5513,8 @@ ${autonomaMd}
|
|
|
5414
5513
|
} catch {
|
|
5415
5514
|
}
|
|
5416
5515
|
try {
|
|
5417
|
-
const scenariosMd = await
|
|
5418
|
-
|
|
5516
|
+
const scenariosMd = await readFile21(
|
|
5517
|
+
join28(input.outputDir, "scenarios.md"),
|
|
5419
5518
|
"utf-8"
|
|
5420
5519
|
);
|
|
5421
5520
|
kbContext += `
|
|
@@ -5611,18 +5710,18 @@ IMPORTANT: Do NOT try to finish early. Process every node via next_node until it
|
|
|
5611
5710
|
console.log(` Fix pass complete`);
|
|
5612
5711
|
}
|
|
5613
5712
|
const allTestFiles = await glob6(
|
|
5614
|
-
|
|
5713
|
+
join28(input.outputDir, "qa-tests", "**/*.md")
|
|
5615
5714
|
);
|
|
5616
5715
|
let markedInvalid = 0;
|
|
5617
5716
|
for (const testPath of allTestFiles) {
|
|
5618
5717
|
if (basename4(testPath) === "INDEX.md") continue;
|
|
5619
5718
|
if (testPath.includes("/_invalid/")) continue;
|
|
5620
|
-
const content = await
|
|
5719
|
+
const content = await readFile21(testPath, "utf-8");
|
|
5621
5720
|
const validation = validateTestContent(content);
|
|
5622
5721
|
if (!validation.valid) {
|
|
5623
|
-
const invalidDir =
|
|
5722
|
+
const invalidDir = join28(input.outputDir, "qa-tests", "_invalid");
|
|
5624
5723
|
await mkdir4(invalidDir, { recursive: true });
|
|
5625
|
-
const dest =
|
|
5724
|
+
const dest = join28(invalidDir, basename4(testPath));
|
|
5626
5725
|
const annotated = `<!-- VALIDATION ERRORS: ${validation.errors.join("; ")} -->
|
|
5627
5726
|
${content}`;
|
|
5628
5727
|
await writeFile12(dest, annotated, "utf-8");
|
|
@@ -5635,7 +5734,7 @@ ${content}`;
|
|
|
5635
5734
|
` ${markedInvalid} tests still invalid after review cycles \u2014 moved to _invalid/`
|
|
5636
5735
|
);
|
|
5637
5736
|
}
|
|
5638
|
-
const dirs = await glob6(
|
|
5737
|
+
const dirs = await glob6(join28(input.outputDir, "qa-tests", "**/"), {
|
|
5639
5738
|
dot: false
|
|
5640
5739
|
});
|
|
5641
5740
|
for (const dir of dirs.sort((a, b) => b.length - a.length)) {
|
|
@@ -5727,7 +5826,7 @@ async function generateIndex(outputDir, state) {
|
|
|
5727
5826
|
for (const paths of state.testsWritten.values()) {
|
|
5728
5827
|
for (const p10 of paths) {
|
|
5729
5828
|
try {
|
|
5730
|
-
const content2 = await
|
|
5829
|
+
const content2 = await readFile21(join28(outputDir, p10), "utf-8");
|
|
5731
5830
|
const critMatch = content2.match(/criticality:\s*(\w+)/);
|
|
5732
5831
|
const critVal = critMatch?.[1] ?? "";
|
|
5733
5832
|
if (critCounts.has(critVal))
|
|
@@ -5778,26 +5877,26 @@ ${folders.map((f) => `| ${f.name} | ${f.test_count} |`).join("\n")}
|
|
|
5778
5877
|
|
|
5779
5878
|
${[...testsByFolder.entries()].flatMap(([_folder, tests]) => tests.map((t) => `- \`${t}\``)).join("\n")}
|
|
5780
5879
|
`;
|
|
5781
|
-
await writeFile12(
|
|
5880
|
+
await writeFile12(join28(outputDir, "qa-tests", "INDEX.md"), content, "utf-8");
|
|
5782
5881
|
}
|
|
5783
5882
|
async function generateJourneyTests(outputDir, model, projectRoot) {
|
|
5784
5883
|
const logger = createStepLogger("journeys", 50);
|
|
5785
5884
|
let autonomaMd = "";
|
|
5786
5885
|
let scenariosMd = "";
|
|
5787
5886
|
try {
|
|
5788
|
-
autonomaMd = await
|
|
5887
|
+
autonomaMd = await readFile21(join28(outputDir, "AUTONOMA.md"), "utf-8");
|
|
5789
5888
|
} catch {
|
|
5790
5889
|
}
|
|
5791
5890
|
try {
|
|
5792
|
-
scenariosMd = await
|
|
5891
|
+
scenariosMd = await readFile21(join28(outputDir, "scenarios.md"), "utf-8");
|
|
5793
5892
|
} catch {
|
|
5794
5893
|
}
|
|
5795
5894
|
if (!autonomaMd) return 0;
|
|
5796
|
-
const existingTests = await glob6(
|
|
5895
|
+
const existingTests = await glob6(join28(outputDir, "qa-tests", "**/*.md"));
|
|
5797
5896
|
const existingTitles = [];
|
|
5798
5897
|
for (const t of existingTests) {
|
|
5799
5898
|
if (basename4(t) === "INDEX.md") continue;
|
|
5800
|
-
const content = await
|
|
5899
|
+
const content = await readFile21(t, "utf-8");
|
|
5801
5900
|
const titleMatch = content.match(/title:\s*"([^"]+)"/);
|
|
5802
5901
|
if (titleMatch) existingTitles.push(titleMatch[1]);
|
|
5803
5902
|
}
|
|
@@ -5842,7 +5941,7 @@ Write 5-8 journey tests using the write_test tool with folder "journeys". Then c
|
|
|
5842
5941
|
let journeyResult;
|
|
5843
5942
|
const journeyFinish = tool19({
|
|
5844
5943
|
description: "Signal journey generation is complete.",
|
|
5845
|
-
inputSchema:
|
|
5944
|
+
inputSchema: z20.object({ summary: z20.string() }),
|
|
5846
5945
|
execute: async (finishInput) => {
|
|
5847
5946
|
journeyResult = {
|
|
5848
5947
|
success: true,
|
|
@@ -5902,8 +6001,8 @@ var init_test_generator = __esm({
|
|
|
5902
6001
|
// src/index.ts
|
|
5903
6002
|
init_esm_shims();
|
|
5904
6003
|
import * as p9 from "@clack/prompts";
|
|
5905
|
-
import { readFile as
|
|
5906
|
-
import { join as
|
|
6004
|
+
import { readFile as readFile22, writeFile as writeFile13 } from "fs/promises";
|
|
6005
|
+
import { join as join29 } from "path";
|
|
5907
6006
|
|
|
5908
6007
|
// src/config.ts
|
|
5909
6008
|
init_esm_shims();
|
|
@@ -6355,11 +6454,11 @@ function nextPendingStep(state) {
|
|
|
6355
6454
|
var PAGES_FILE = "pages.json";
|
|
6356
6455
|
async function savePages(outputDir, pages) {
|
|
6357
6456
|
const obj = Object.fromEntries(pages);
|
|
6358
|
-
await writeFile13(
|
|
6457
|
+
await writeFile13(join29(outputDir, PAGES_FILE), JSON.stringify(obj, null, 2), "utf-8");
|
|
6359
6458
|
}
|
|
6360
6459
|
async function loadPages(outputDir) {
|
|
6361
6460
|
try {
|
|
6362
|
-
const raw = await
|
|
6461
|
+
const raw = await readFile22(join29(outputDir, PAGES_FILE), "utf-8");
|
|
6363
6462
|
const obj = JSON.parse(raw);
|
|
6364
6463
|
return new Map(Object.entries(obj));
|
|
6365
6464
|
} catch {
|
|
@@ -6628,7 +6727,6 @@ async function main() {
|
|
|
6628
6727
|
p9.log.info(`No --project flag passed; using current working directory.`);
|
|
6629
6728
|
}
|
|
6630
6729
|
p9.log.info(`Project: ${config.projectRoot}`);
|
|
6631
|
-
p9.log.info(`Model: ${modelName}`);
|
|
6632
6730
|
track("cli_run_started", { model: modelName, non_interactive: nonInteractive });
|
|
6633
6731
|
const outputDir = await ensureOutputDir(config.projectSlug);
|
|
6634
6732
|
let state = await loadState(outputDir);
|