@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 CHANGED
@@ -134,7 +134,7 @@ function toolCallSummary(name, input) {
134
134
  }
135
135
  }
136
136
  }
137
- function createStepLogger(agentId, maxSteps) {
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(`${stepPrefix} \u2014 reading ${summary2}`);
167
+ writeSpinner(`reading ${summary2}`);
169
168
  break;
170
169
  case "glob":
171
- writeSpinner(`${stepPrefix} \u2014 glob ${summary2}`);
170
+ writeSpinner(`glob ${summary2}`);
172
171
  break;
173
172
  case "grep":
174
- writeSpinner(`${stepPrefix} \u2014 grep ${summary2}`);
173
+ writeSpinner(`grep ${summary2}`);
175
174
  break;
176
175
  case "list_directory":
177
- writeSpinner(`${stepPrefix} \u2014 listing ${summary2}`);
176
+ writeSpinner(`listing ${summary2}`);
178
177
  break;
179
178
  case "bash":
180
- writeSpinner(`${stepPrefix} \u2014 bash: ${summary2}`);
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(`${stepPrefix} \u2014 ${tc.name}${summary2 ? " " + summary2 : ""}`);
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 readFile22 = onFileRead ? tool6({
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: readFile22
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("kb", 150);
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 z13 } from "zod";
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: z13.object({
2862
- language: z13.string().describe("Programming language: typescript, python, go, ruby, java, php, rust, elixir"),
2863
- framework: z13.string().describe("Web framework: express, node, hono, web, flask, fastapi, django, gin, rails, rack, spring, laravel, axum, actix, plug")
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 z14 } from "zod";
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: z14.object({
3145
- records: z14.array(z14.record(z14.string(), z14.unknown())).describe("Array of record objects for this entity")
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: z14.object({
3187
- records: z14.array(z14.record(z14.string(), z14.unknown()))
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: z14.object({
3244
- instructions: z14.string().describe("Complete, copy-pasteable implementation 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 z15 } from "zod";
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: z15.object({
3669
- recipe: z15.record(z15.string(), z15.array(z15.record(z15.string(), z15.unknown())))
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
- const entityOrder = resolveEntityOrder(models);
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 z16 } from "zod";
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 = z16.object({
4016
- pass: z16.boolean(),
4017
- evidence: z16.string().describe("What you checked and found \u2014 cite file paths, line content, or specific strings"),
4018
- suggestion: z16.string().optional().describe("What the planner agent should fix, if failing")
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: z16.object({
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: z16.object({
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: z16.object({
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: z16.object({
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 readFile17 } from "fs/promises";
4256
- import { join as join23, relative as relative6, basename as basename3 } from "path";
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 = join23(outputDir, "qa-tests");
4382
+ const testsDir = join24(outputDir, "qa-tests");
4284
4383
  const logger = createStepLogger("review", 5);
4285
4384
  let scenarioData;
4286
4385
  try {
4287
- scenarioData = await readFile17(join23(outputDir, "scenarios.md"), "utf-8");
4386
+ scenarioData = await readFile18(join24(outputDir, "scenarios.md"), "utf-8");
4288
4387
  } catch {
4289
4388
  }
4290
- const testFiles = await glob5(join23(testsDir, "**/*.md"));
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 readFile17(testPath, "utf-8");
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 readFile18, writeFile as writeFile9 } from "fs/promises";
4373
- import { join as join24 } from "path";
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 = join24(outputDir, STATE_FILE3);
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 = join24(outputDir, STATE_FILE3);
4478
+ const path3 = join25(outputDir, STATE_FILE3);
4380
4479
  try {
4381
- const raw = await readFile18(path3, "utf-8");
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 readFile19, writeFile as writeFile10 } from "fs/promises";
4474
- import { join as join25 } from "path";
4475
- import { z as z17 } from "zod";
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(join25(outputDir, FEATURES_FILE), JSON.stringify(obj, null, 2), "utf-8");
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 readFile19(join25(outputDir, FEATURES_FILE), "utf-8");
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: z17.string().min(1).describe("Unique kebab-case ID (e.g. 'settings-notifications-tab')")
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: z17.object({}),
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: z17.object({}),
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: z17.object({ summary: z17.string() }),
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 = z17.object({
4573
- name: z17.string().min(1).describe("Human-readable name (e.g. 'Settings > Notifications Tab', 'Create Project Modal')"),
4574
- type: z17.enum(["tab", "modal", "form", "table", "wizard", "nested-route", "complex-component"]),
4575
- parentPagePath: z17.string().min(1).describe("The page path this feature belongs to (from the pages list)"),
4576
- sourceFiles: z17.array(z17.string()).min(1).describe("Relative paths to the source files for this sub-feature"),
4577
- interactiveElements: z17.number().int().min(0).describe("Count of interactive elements found (buttons, inputs, toggles, etc.)"),
4578
- description: z17.string().min(10).describe("What this sub-feature does")
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 join26 } from "path";
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 z18 } from "zod";
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: z18.object({
4729
- folder: z18.string().describe("Subfolder name under qa-tests/"),
4730
- filename: z18.string().describe("File name (e.g. login-valid-credentials.md)"),
4731
- content: z18.string().describe("Full file content including YAML frontmatter"),
4732
- nodeId: z18.string().describe("The FeatureNode ID this test belongs to")
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 = join26("qa-tests", input.folder, input.filename);
4782
- const absPath = join26(outputDir, relPath);
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: z18.object({
4800
- folder: z18.string().describe("Folder name (kebab-case)")
4898
+ inputSchema: z19.object({
4899
+ folder: z19.string().describe("Folder name (kebab-case)")
4801
4900
  }),
4802
4901
  execute: async (input) => {
4803
- const absPath = join26(outputDir, "qa-tests", input.folder);
4902
+ const absPath = join27(outputDir, "qa-tests", input.folder);
4804
4903
  try {
4805
4904
  await mkdir3(absPath, { recursive: true });
4806
- return { path: join26("qa-tests", input.folder) };
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: z18.object({}),
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: z18.object({}),
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: z18.object({
4863
- instruction: z18.string().describe("What to research \u2014 be specific about files and what to look for")
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 = z18.object({
4867
- findings: z18.string().describe("Summary of what was found")
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 = z18.object({
4920
- title: z18.string().min(1),
4921
- description: z18.string().min(1),
4922
- intent: z18.string().min(30, "Intent must be at least 30 characters \u2014 describe the BEHAVIOR being tested, not the steps"),
4923
- criticality: z18.enum(["critical", "high", "mid", "low"]),
4924
- scenario: z18.string().min(1),
4925
- flow: z18.string().min(1),
4926
- verification: z18.string().min(20, "Verification must describe WHERE to navigate and WHAT to assert at the source of truth \u2014 not UI acknowledgments like toasts")
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 readFile20, rmdir, unlink, writeFile as writeFile12 } from "fs/promises";
5323
- import { basename as basename4, join as join27 } from "path";
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 z19 } from "zod";
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: z19.object({
5376
- summary: z19.string().describe("Coverage 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 readFile20(
5406
- join27(input.outputDir, "AUTONOMA.md"),
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 readFile20(
5418
- join27(input.outputDir, "scenarios.md"),
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
- join27(input.outputDir, "qa-tests", "**/*.md")
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 readFile20(testPath, "utf-8");
5719
+ const content = await readFile21(testPath, "utf-8");
5621
5720
  const validation = validateTestContent(content);
5622
5721
  if (!validation.valid) {
5623
- const invalidDir = join27(input.outputDir, "qa-tests", "_invalid");
5722
+ const invalidDir = join28(input.outputDir, "qa-tests", "_invalid");
5624
5723
  await mkdir4(invalidDir, { recursive: true });
5625
- const dest = join27(invalidDir, basename4(testPath));
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(join27(input.outputDir, "qa-tests", "**/"), {
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 readFile20(join27(outputDir, p10), "utf-8");
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(join27(outputDir, "qa-tests", "INDEX.md"), content, "utf-8");
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 readFile20(join27(outputDir, "AUTONOMA.md"), "utf-8");
5887
+ autonomaMd = await readFile21(join28(outputDir, "AUTONOMA.md"), "utf-8");
5789
5888
  } catch {
5790
5889
  }
5791
5890
  try {
5792
- scenariosMd = await readFile20(join27(outputDir, "scenarios.md"), "utf-8");
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(join27(outputDir, "qa-tests", "**/*.md"));
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 readFile20(t, "utf-8");
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: z19.object({ summary: z19.string() }),
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 readFile21, writeFile as writeFile13 } from "fs/promises";
5906
- import { join as join28 } from "path";
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(join28(outputDir, PAGES_FILE), JSON.stringify(obj, null, 2), "utf-8");
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 readFile21(join28(outputDir, PAGES_FILE), "utf-8");
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);