blastproof 0.21.0 → 0.22.0

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/cli.js CHANGED
@@ -333,6 +333,9 @@ function referencedEnvVars(text) {
333
333
  }
334
334
  return [...names];
335
335
  }
336
+ function placeholdersAsLabels(text) {
337
+ return text.replace(ENV_PLACEHOLDER, (_match, name) => redactionLabel(name));
338
+ }
336
339
  function escapeRegExp(value) {
337
340
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
338
341
  }
@@ -343,6 +346,12 @@ var REDACTION_PREFIX = "[redacted";
343
346
  function redactionLabel(name) {
344
347
  return name === void 0 ? `${REDACTION_PREFIX}]` : `${REDACTION_PREFIX} ${name}]`;
345
348
  }
349
+ var LABEL = /\[redacted ([A-Za-z_][A-Za-z0-9_]*)\]/g;
350
+ function labelledVariables(text) {
351
+ const names = /* @__PURE__ */ new Set();
352
+ for (const match of text.matchAll(LABEL)) if (match[1]) names.add(match[1]);
353
+ return [...names];
354
+ }
346
355
  function compile(secrets, names) {
347
356
  return [...secrets].filter(Boolean).sort((a, b) => b.length - a.length).map((value) => ({ value, pattern: secretPattern(value), label: redactionLabel(names?.get(value)) }));
348
357
  }
@@ -1607,8 +1616,10 @@ function parseAgentAction(value) {
1607
1616
  return parsedAgentActionSchema.safeParse(value);
1608
1617
  }
1609
1618
  var assertJudgmentSchema = z2.object({
1610
- pass: z2.boolean().describe("Whether the snapshot satisfies the expectation."),
1611
- reason: z2.string().describe("One sentence explaining the judgment.")
1619
+ reason: z2.string().describe("One sentence: what the STEP requires to be true now, and whether the snapshot shows it."),
1620
+ pass: z2.boolean().describe(
1621
+ "Whether the snapshot shows the STEP's own outcome. The expectation is only a claim offered in support; it never replaces the step. False if any part of the outcome the step asks for is not shown, or cannot be assessed from this snapshot."
1622
+ )
1612
1623
  });
1613
1624
  var generatedTestSchema = z2.object({
1614
1625
  summary: z2.string().min(1).describe('One line naming the user journey this test covers, e.g. "Applying a discount updates the cart total".'),
@@ -1665,11 +1676,20 @@ function createBrain(model, generate = generateObject, budget) {
1665
1676
  return parsed.data;
1666
1677
  },
1667
1678
  async judge(step, expectation, snapshot, stepHistory) {
1679
+ const asJudged = stepHistory?.map((entry) => ({
1680
+ action: placeholdersAsLabels(entry.action),
1681
+ result: placeholdersAsLabels(entry.result)
1682
+ }));
1668
1683
  const result = await countedGenerate(generate, budget, {
1669
1684
  model,
1670
1685
  schema: assertJudgmentSchema,
1671
1686
  system: assertSystemPrompt(),
1672
- prompt: assertUserPrompt(step, expectation, snapshot, stepHistory),
1687
+ prompt: assertUserPrompt(
1688
+ placeholdersAsLabels(step),
1689
+ placeholdersAsLabels(expectation),
1690
+ snapshot,
1691
+ asJudged
1692
+ ),
1673
1693
  temperature: 0
1674
1694
  });
1675
1695
  const parsed = assertJudgmentSchema.safeParse(result.object);
@@ -1678,10 +1698,26 @@ function createBrain(model, generate = generateObject, budget) {
1678
1698
  `Model returned an invalid assert judgment: ${parsed.error.issues[0]?.message ?? "unknown"}`
1679
1699
  );
1680
1700
  }
1681
- return parsed.data;
1701
+ return secretMismatch(placeholdersAsLabels(step), snapshot, asJudged, parsed.data);
1682
1702
  }
1683
1703
  };
1684
1704
  }
1705
+ function secretMismatch(judgedStep, snapshot, judgedRecord, judgment) {
1706
+ if (!judgment.pass) return judgment;
1707
+ const onPage = labelledVariables(snapshot);
1708
+ const inRecord = labelledVariables((judgedRecord ?? []).map((e) => `${e.action}
1709
+ ${e.result}`).join("\n"));
1710
+ for (const name of labelledVariables(judgedStep)) {
1711
+ const others = onPage.filter((other) => other !== name);
1712
+ if (onPage.includes(name) || inRecord.includes(name) || others.length === 0) continue;
1713
+ const shown = others.map((other) => redactionLabel(other)).join(", ");
1714
+ return {
1715
+ pass: false,
1716
+ reason: `The step names ${redactionLabel(name)}, which appears neither on the page nor in this step's actions, while the page shows ${shown}: a different secret cannot satisfy it. (The judge had said: ${judgment.reason})`
1717
+ };
1718
+ }
1719
+ return judgment;
1720
+ }
1685
1721
  function createPlanner(model, generate = generateObject, budget) {
1686
1722
  return {
1687
1723
  async planTest(input) {
@@ -3256,7 +3292,7 @@ function parsePositiveNumber(flag) {
3256
3292
  };
3257
3293
  }
3258
3294
  var program = new Command();
3259
- program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.21.0");
3295
+ program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.22.0");
3260
3296
  program.command("init").description("Scaffold .blastproof/ (config, tests, sample tests) in the current directory").action(async () => {
3261
3297
  try {
3262
3298
  const result = await initProject(process.cwd());