@autonoma-ai/planner 0.1.13 → 0.1.14-canary.9e91c81

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
@@ -604,7 +604,7 @@ async function runAgent(config, prompt, extractResult) {
604
604
  err
605
605
  );
606
606
  };
607
- const YELLOW5 = "\x1B[33m";
607
+ const YELLOW4 = "\x1B[33m";
608
608
  const RESET8 = "\x1B[0m";
609
609
  for (let modelIdx = 0; modelIdx < modelsToTry.length; modelIdx++) {
610
610
  const model = modelsToTry[modelIdx];
@@ -630,7 +630,7 @@ async function runAgent(config, prompt, extractResult) {
630
630
  while (extractResult() === void 0 && nudges < MAX_NUDGES) {
631
631
  nudges++;
632
632
  console.log(
633
- ` ${YELLOW5}[${config.id}] agent stopped without finishing \u2014 nudging (${nudges}/${MAX_NUDGES})...${RESET8}`
633
+ ` ${YELLOW4}[${config.id}] agent stopped without finishing \u2014 nudging (${nudges}/${MAX_NUDGES})...${RESET8}`
634
634
  );
635
635
  track("cli_agent_nudged", { agent: config.id, nudge: nudges });
636
636
  messages.push(...generation.response.messages);
@@ -646,14 +646,14 @@ async function runAgent(config, prompt, extractResult) {
646
646
  if (errorClass === "fatal") fail(err, model);
647
647
  const msg = err instanceof Error ? err.message : String(err);
648
648
  if (errorClass === "timeout") {
649
- console.log(` ${YELLOW5}[${config.id}] step timed out after ${stepTimeout / 1e3}s${RESET8}`);
649
+ console.log(` ${YELLOW4}[${config.id}] step timed out after ${stepTimeout / 1e3}s${RESET8}`);
650
650
  } else {
651
- console.log(` ${YELLOW5}[${config.id}] provider error: ${msg}${RESET8}`);
651
+ console.log(` ${YELLOW4}[${config.id}] provider error: ${msg}${RESET8}`);
652
652
  }
653
653
  track("cli_agent_retryable_error", { agent: config.id, error_class: errorClass, retry });
654
654
  if (retry < RETRIES_BEFORE_FALLBACK - 1) {
655
655
  console.log(
656
- ` ${YELLOW5}[${config.id}] retrying (${retry + 1}/${RETRIES_BEFORE_FALLBACK})...${RESET8}`
656
+ ` ${YELLOW4}[${config.id}] retrying (${retry + 1}/${RETRIES_BEFORE_FALLBACK})...${RESET8}`
657
657
  );
658
658
  if (errorClass === "transient") {
659
659
  await sleep(Math.min(2e3 * 2 ** retry, 1e4));
@@ -663,7 +663,7 @@ async function runAgent(config, prompt, extractResult) {
663
663
  if (modelIdx < modelsToTry.length - 1) {
664
664
  const nextModel = FALLBACK_MODELS[modelIdx];
665
665
  console.log(
666
- ` ${YELLOW5}[${config.id}] ${RETRIES_BEFORE_FALLBACK} failed attempts, switching to ${nextModel}${RESET8}`
666
+ ` ${YELLOW4}[${config.id}] ${RETRIES_BEFORE_FALLBACK} failed attempts, switching to ${nextModel}${RESET8}`
667
667
  );
668
668
  break;
669
669
  }
@@ -2839,19 +2839,10 @@ async function parseScenario(outputDir) {
2839
2839
  }
2840
2840
  try {
2841
2841
  const data = matter3(raw).data;
2842
- const varsByEntity = /* @__PURE__ */ new Map();
2843
- for (const vf of data.variable_fields ?? []) {
2844
- const entity = vf?.entity != null ? String(vf.entity) : "";
2845
- const field = vf?.field != null ? String(vf.field) : "";
2846
- if (!entity || !field) continue;
2847
- const list = varsByEntity.get(entity) ?? [];
2848
- list.push(field);
2849
- varsByEntity.set(entity, list);
2850
- }
2851
2842
  const entityTypes = (data.entity_types ?? []).map((e) => {
2852
2843
  const name = e?.name != null ? String(e.name).trim() : "";
2853
2844
  const count = typeof e?.count === "number" ? e.count : Number(e?.count ?? 0) || 0;
2854
- return { name, count, variableFields: varsByEntity.get(name) ?? [] };
2845
+ return { name, count };
2855
2846
  }).filter((e) => e.name.length > 0);
2856
2847
  const scenarioNames = (data.scenarios ?? []).map((s) => s?.name != null ? String(s.name).trim() : "").filter((n) => n.length > 0);
2857
2848
  return { scenarioNames, entityTypes };
@@ -2859,6 +2850,23 @@ async function parseScenario(outputDir) {
2859
2850
  return { scenarioNames: [], entityTypes: [] };
2860
2851
  }
2861
2852
  }
2853
+ function validateScenarioIsConcrete(content) {
2854
+ const errors = [];
2855
+ const checks = [
2856
+ { pattern: /\{\{[a-zA-Z0-9_]+\}\}/g, label: "{{token}} placeholder" },
2857
+ { pattern: /(?<!\{)\{[a-z][a-zA-Z]*\}(?!\})/g, label: "bare {variable} placeholder" },
2858
+ { pattern: /^\s*variable_fields\s*:/m, label: "variable_fields block" }
2859
+ ];
2860
+ for (const { pattern, label } of checks) {
2861
+ const match = content.match(pattern);
2862
+ if (match && match.length > 0) {
2863
+ errors.push(
2864
+ `${label}: "${match[0].trim()}" \u2014 scenario values must be concrete. Replace it with the exact static value (there is no variable mechanism).`
2865
+ );
2866
+ }
2867
+ }
2868
+ return errors;
2869
+ }
2862
2870
  function truncate3(s, max) {
2863
2871
  if (s.length <= max) return s;
2864
2872
  return s.slice(0, max - 1).trimEnd() + "\u2026";
@@ -2869,40 +2877,32 @@ function pad3(s, width) {
2869
2877
  function renderScenarioTable(parsed) {
2870
2878
  if (parsed.entityTypes.length === 0) return "";
2871
2879
  const NAME_MAX = 32;
2872
- const VARS_MAX = 40;
2873
2880
  const rows = parsed.entityTypes.map((e, i) => ({
2874
2881
  num: String(i + 1),
2875
2882
  name: truncate3(e.name, NAME_MAX),
2876
- count: String(e.count),
2877
- vars: e.variableFields.length ? truncate3(e.variableFields.join(", "), VARS_MAX) : "\u2014",
2878
- hasVars: e.variableFields.length > 0
2883
+ count: String(e.count)
2879
2884
  }));
2880
2885
  const numW = Math.max(1, ...rows.map((r) => r.num.length));
2881
2886
  const nameW = Math.max("Entity".length, ...rows.map((r) => r.name.length));
2882
2887
  const countW = Math.max("Count".length, ...rows.map((r) => r.count.length));
2883
2888
  const totalRecords = parsed.entityTypes.reduce((sum, e) => sum + e.count, 0);
2884
- const withVars = parsed.entityTypes.filter((e) => e.variableFields.length > 0).length;
2885
- const header = `${BOLD4}${pad3("#", numW)} ${pad3("Entity", nameW)} ${pad3("Count", countW)} Variable fields${RESET6}`;
2886
- const sep = `${DIM6}${"\u2500".repeat(numW + nameW + countW + VARS_MAX + 6)}${RESET6}`;
2887
- const body = rows.map((r) => {
2888
- const line = `${pad3(r.num, numW)} ${pad3(r.name, nameW)} ${pad3(r.count, countW)} ${r.vars}`;
2889
- return r.hasVars ? `${YELLOW4}${line}${RESET6}` : line;
2890
- }).join("\n");
2889
+ const header = `${BOLD4}${pad3("#", numW)} ${pad3("Entity", nameW)} ${pad3("Count", countW)}${RESET6}`;
2890
+ const sep = `${DIM6}${"\u2500".repeat(numW + nameW + countW + 4)}${RESET6}`;
2891
+ const body = rows.map((r) => `${pad3(r.num, numW)} ${pad3(r.name, nameW)} ${pad3(r.count, countW)}`).join("\n");
2891
2892
  const scenarioLabel = parsed.scenarioNames.length ? `scenario: ${parsed.scenarioNames.join(", ")} \xB7 ` : "";
2892
- const caption = `${DIM6}${scenarioLabel}${parsed.entityTypes.length} entity types \xB7 ${totalRecords} records \xB7 ${withVars} with variable fields${RESET6}`;
2893
+ const caption = `${DIM6}${scenarioLabel}${parsed.entityTypes.length} entity types \xB7 ${totalRecords} records${RESET6}`;
2893
2894
  return `${header}
2894
2895
  ${sep}
2895
2896
  ${body}
2896
2897
  ${caption}`;
2897
2898
  }
2898
- var RESET6, DIM6, YELLOW4, BOLD4;
2899
+ var RESET6, DIM6, BOLD4;
2899
2900
  var init_scenario_table = __esm({
2900
2901
  "src/agents/03-scenario-recipe/scenario-table.ts"() {
2901
2902
  "use strict";
2902
2903
  init_esm_shims();
2903
2904
  RESET6 = "\x1B[0m";
2904
2905
  DIM6 = "\x1B[2m";
2905
- YELLOW4 = "\x1B[33m";
2906
2906
  BOLD4 = "\x1B[1m";
2907
2907
  }
2908
2908
  });
@@ -2948,27 +2948,23 @@ entity_types:
2948
2948
  count: 3
2949
2949
  - name: Project
2950
2950
  count: 5
2951
- variable_fields:
2952
- - token: "{{user_email_1}}"
2953
- entity: User
2954
- field: email
2955
- test_reference: "primary test user email"
2956
2951
  ---
2957
2952
  \`\`\`
2958
2953
 
2959
2954
  After frontmatter, write entity tables showing the data for the standard scenario.
2960
2955
  Use markdown tables. Show enough detail that a recipe builder can generate the exact records.
2961
2956
 
2962
- ## Variable fields
2963
- Some values must change between test runs (emails, unique slugs, timestamps).
2964
- List these in variable_fields with a {{token}} placeholder.
2965
- The rest can be static.`;
2957
+ ## Data values
2958
+ Every value in the scenario is concrete and static \u2014 write the actual data the
2959
+ records should hold. Do NOT use placeholders, tokens, or templated/"dynamic"
2960
+ values; the recipe builder generates the exact records from what you write.`;
2966
2961
  }
2967
2962
  });
2968
2963
 
2969
2964
  // src/agents/03-scenario-recipe/index.ts
2970
2965
  var scenario_recipe_exports = {};
2971
2966
  __export(scenario_recipe_exports, {
2967
+ buildFinishTool: () => buildFinishTool3,
2972
2968
  feedbackToScenario: () => feedbackToScenario,
2973
2969
  runScenarioRecipe: () => runScenarioRecipe
2974
2970
  });
@@ -2985,23 +2981,30 @@ function buildFinishTool3(requiredEntities, outputDir, onFinish) {
2985
2981
  artifacts: z13.array(z13.string()).describe("Files written")
2986
2982
  }),
2987
2983
  execute: async (input) => {
2984
+ let content;
2985
+ try {
2986
+ content = await readFile13(join19(outputDir, "scenarios.md"), "utf-8");
2987
+ } catch {
2988
+ return { error: "Cannot finish: scenarios.md not found. Write it first." };
2989
+ }
2988
2990
  if (requiredEntities.length > 0) {
2989
- try {
2990
- const content = await readFile13(join19(outputDir, "scenarios.md"), "utf-8");
2991
- const missing = requiredEntities.filter(
2992
- (e) => !content.includes(e)
2993
- );
2994
- if (missing.length > 0) {
2995
- return {
2996
- error: `Cannot finish: ${missing.length} entities from the entity audit are missing from scenarios.md.
2991
+ const missing = requiredEntities.filter((e) => !content.includes(e));
2992
+ if (missing.length > 0) {
2993
+ return {
2994
+ error: `Cannot finish: ${missing.length} entities from the entity audit are missing from scenarios.md.
2997
2995
  Add these entities to the scenario before calling finish:
2998
2996
  ` + missing.map((e) => ` - ${e}`).join("\n")
2999
- };
3000
- }
3001
- } catch {
3002
- return { error: "Cannot finish: scenarios.md not found. Write it first." };
2997
+ };
3003
2998
  }
3004
2999
  }
3000
+ const concretenessErrors = validateScenarioIsConcrete(content);
3001
+ if (concretenessErrors.length > 0) {
3002
+ return {
3003
+ error: `Cannot finish: scenario data must be fully concrete \u2014 no variables, tokens, or placeholders.
3004
+ Fix these before calling finish:
3005
+ ` + concretenessErrors.map((e) => ` - ${e}`).join("\n")
3006
+ };
3007
+ }
3005
3008
  onFinish({
3006
3009
  success: true,
3007
3010
  artifacts: input.artifacts,
@@ -3033,7 +3036,6 @@ The scenario should:
3033
3036
  1. Cover ALL ${requiredEntities.length || ""} entity types from the entity audit \u2014 no exceptions
3034
3037
  2. Use realistic data volumes (not just 1 of each)
3035
3038
  3. Cover all enum values (at least one record per value)
3036
- 4. Include variable_fields for values that must change between runs
3037
3039
 
3038
3040
  When done, call finish.`;
3039
3041
  const agentConfig = {
@@ -3059,7 +3061,7 @@ When done, call finish.`;
3059
3061
  const parsed = await parseScenario(input.outputDir);
3060
3062
  return parsed.entityTypes.length ? renderScenarioTable(parsed) : void 0;
3061
3063
  },
3062
- reviewGuidance: "The scenario defines test data that will exist in the database during E2E tests.\nEach entity_type should have a realistic count and data values.\n\nWhat to check:\n - Every entity from the entity audit should appear here\n - Counts should be realistic (not just 1 of each)\n - Enum fields should have diverse values (not all the same)\n - variable_fields should mark values that must change between runs (emails, slugs)\n - Data values should match your actual database patterns",
3064
+ reviewGuidance: "The scenario defines test data that will exist in the database during E2E tests.\nEach entity_type should have a realistic count and data values.\n\nWhat to check:\n - Every entity from the entity audit should appear here\n - Counts should be realistic (not just 1 of each)\n - Enum fields should have diverse values (not all the same)\n - Data values should match your actual database patterns",
3063
3065
  onFeedback: async (feedback) => {
3064
3066
  result = void 0;
3065
3067
  const feedbackPrompt = `The user reviewed the scenario design and has this feedback:
@@ -5535,6 +5537,22 @@ import { dirname as dirname3, join as join28 } from "path";
5535
5537
  import { hasToolCall as hasToolCall3, stepCountIs as stepCountIs3, tool as tool18, ToolLoopAgent as ToolLoopAgent3 } from "ai";
5536
5538
  import matter5 from "gray-matter";
5537
5539
  import { z as z21 } from "zod";
5540
+ function findForbiddenPlaceholder(stepsSection) {
5541
+ const placeholderPatterns = [
5542
+ { pattern: /Dynamic:\s/gi, name: '"Dynamic:" placeholder' },
5543
+ { pattern: /\{\{[a-zA-Z0-9_]+\}\}/g, name: "{{token}} placeholder" },
5544
+ { pattern: /(?<!\{)\{[a-z][a-zA-Z]*\}(?!\})/g, name: "bare {variable}" },
5545
+ { pattern: /\(e\.g\./gi, name: '"(e.g." example' },
5546
+ { pattern: /(?:^|\s)e\.g\.,?\s/gim, name: '"e.g." example' }
5547
+ ];
5548
+ for (const { pattern, name } of placeholderPatterns) {
5549
+ const matches = stepsSection.match(pattern);
5550
+ if (matches && matches.length > 0) {
5551
+ return { name, match: matches[0] };
5552
+ }
5553
+ }
5554
+ return null;
5555
+ }
5538
5556
  function buildWriteTestTool(state, outputDir) {
5539
5557
  return tool18({
5540
5558
  description: "Write a test file to qa-tests/{folder}/{filename}.md. Validates frontmatter before writing. Returns error if frontmatter is invalid.",
@@ -5577,19 +5595,11 @@ function buildWriteTestTool(state, outputDir) {
5577
5595
  const bodyStart = input.content.indexOf("---", 3);
5578
5596
  const body = bodyStart > -1 ? input.content.slice(bodyStart + 3) : input.content;
5579
5597
  const stepsSection = body.slice(body.indexOf("**Steps**") || 0);
5580
- const placeholderPatterns = [
5581
- { pattern: /Dynamic:\s/gi, name: '"Dynamic:" placeholder' },
5582
- { pattern: /(?<!\{)\{[a-z][a-zA-Z]*\}(?!\})/g, name: "bare {variable}" },
5583
- { pattern: /\(e\.g\./gi, name: '"(e.g." example' },
5584
- { pattern: /(?:^|\s)e\.g\.,?\s/gim, name: '"e.g." example' }
5585
- ];
5586
- for (const { pattern, name } of placeholderPatterns) {
5587
- const matches = stepsSection.match(pattern);
5588
- if (matches && matches.length > 0) {
5589
- return {
5590
- error: `Test steps contain ${name}: "${matches[0]}". Use EXACT values from scenarios.md \u2014 not placeholders or examples.`
5591
- };
5592
- }
5598
+ const placeholder = findForbiddenPlaceholder(stepsSection);
5599
+ if (placeholder) {
5600
+ return {
5601
+ error: `Test steps contain ${placeholder.name}: "${placeholder.match}". Use EXACT values from scenarios.md \u2014 not placeholders or examples.`
5602
+ };
5593
5603
  }
5594
5604
  const relPath = join28("qa-tests", input.folder, input.filename);
5595
5605
  const absPath = join28(outputDir, relPath);
@@ -6012,7 +6022,7 @@ EVERY assertion MUST include location context \u2014 where on the page the eleme
6012
6022
  - Be specific: use exact button text, field names, toast messages FROM THE CODE
6013
6023
  - One test per file
6014
6024
  - Never write meta-tests that "audit" scenario/fixture contents
6015
- - Reference scenario data when needed for real user flows, using {{token}} placeholders for variable fields
6025
+ - Reference scenario data when needed for real user flows, using the exact values from the scenario
6016
6026
  - Do NOT write tests that verify the test infrastructure itself
6017
6027
  - Every step must be concrete and reproducible. "assert: text 'Deal Created' is visible in toast" is GOOD. "assert: success indicator appears" is BAD.
6018
6028
 
@@ -6041,8 +6051,8 @@ Therefore:
6041
6051
  The scenarios define EXACTLY what data exists in the database. Since WE control the test data, assertions should reference EXACT values from the scenario.
6042
6052
  - When a test needs to verify data is displayed, use the EXACT values from the scenario (names, emails, titles, counts)
6043
6053
  - Read scenarios.md carefully and use the exact entity names, counts, and field values in your assertions
6044
- - Use {{token}} placeholders ONLY for values that genuinely vary between runs (like auto-generated IDs)
6045
- - NEVER use "Dynamic:", "{variableName}", or "e.g." in steps or assertions. You have exact data \u2014 use it.
6054
+ - Do NOT assert on values that are auto-generated or vary at runtime (like database IDs); assert on the stable scenario values instead
6055
+ - NEVER use "Dynamic:", "{variableName}", "{{token}}", or "e.g." in steps or assertions. You have exact data \u2014 use it.
6046
6056
  - NEVER assume facts not stated in the scenario data.
6047
6057
 
6048
6058
  ## Test generation ordering (for consistency)