blastproof 0.18.0 → 0.19.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/README.md CHANGED
@@ -133,7 +133,7 @@ steps:
133
133
  - complete checkout
134
134
  ```
135
135
 
136
- `priority` is P0–P2 (default P1). `tags`, `setup` steps and `auth` are optional — `auth: false` runs the test signed out, which a login test needs. `routes` declares the URLs a test covers, which is what `--impacted` selects on; write route strings consistently, since they compare by exact equality (`/cart` ≠ `/cart/`). `run` warns to stderr — non-fatal — when a test declares a route no `routes:` mapping declares, since that route contributes nothing to `--impacted` selection.
136
+ `priority` is P0–P2 (default P1). `tags`, `setup` steps and `auth` are optional — `auth: false` runs the test signed out, which a login test needs, and a selection in which *every* test declares it skips the login altogether. `routes` declares the URLs a test covers, which is what `--impacted` selects on; write route strings consistently, since they compare by exact equality (`/cart` ≠ `/cart/`). `run` warns to stderr — non-fatal — when a test declares a route no `routes:` mapping declares, since that route contributes nothing to `--impacted` selection.
137
137
 
138
138
  ### Say what each step should produce
139
139
 
@@ -227,6 +227,10 @@ The key is the file glob and the value is the routes it can affect — the oppos
227
227
 
228
228
  Every changed file lands in one of three buckets: it matches `routes:` and contributes them, matches `ignore:` and is knowingly irrelevant, or **matches neither — nobody has said what it affects**. `--fail-on-unmapped` blocks on that third case, naming the files and both ways to resolve them.
229
229
 
230
+ **The diff is `git diff <base>...HEAD`, so it compares commits.** Work you have not committed — edited, staged or untracked — is not in it. That is the right comparison in CI, where everything is committed by the time the workflow runs, and the wrong one on the machine you are writing the change on: a run against a dirty working tree selects from your last commit, not from your editor.
231
+
232
+ It says so rather than leaving you to notice. Any working-tree change the diff excluded is named on stderr, with the routes it maps to, on `run --impacted` and `plan --base` alike — non-fatal, and it changes no exit code and nothing about what is selected. Files your `ignore:` globs already cover stay silent, and so do files already in the diff, whose routes are selected either way.
233
+
230
234
  **Nothing is ignored by default**, on purpose: a default that guesses on your behalf would hide the first files worth thinking about. The flag is additive — a run can meet `--min-score` and still be blocked here, because "the tests I ran passed" and "something changed that nobody classified" are different claims.
231
235
 
232
236
  Its limit is worth knowing: it catches files that are *unclassified*, not *misclassified*. A shared module mapped to one route when it can break five still slips through. Impact by import graph is the fix, and blastproof does not do it yet.
package/dist/cli.js CHANGED
@@ -1316,6 +1316,20 @@ async function getChangedFiles(baseRef, cwd = process.cwd()) {
1316
1316
  throw toDiffError(error, baseRef);
1317
1317
  }
1318
1318
  }
1319
+ async function getUncommittedFiles(cwd = process.cwd()) {
1320
+ try {
1321
+ const status = await simpleGit(cwd).status();
1322
+ const paths = /* @__PURE__ */ new Set();
1323
+ for (const file of status.files) {
1324
+ paths.add(file.path.replace(/\\/g, "/"));
1325
+ const from = file.from;
1326
+ if (from) paths.add(from.replace(/\\/g, "/"));
1327
+ }
1328
+ return [...paths].sort();
1329
+ } catch {
1330
+ return [];
1331
+ }
1332
+ }
1319
1333
 
1320
1334
  // src/impact.ts
1321
1335
  import picomatch from "picomatch";
@@ -1354,6 +1368,25 @@ function mapImpact(changedFiles, routes, ignore = []) {
1354
1368
  };
1355
1369
  }
1356
1370
 
1371
+ // src/report/uncommitted.ts
1372
+ function printUncommitted(uncommitted, changedFiles, config, baseRef) {
1373
+ const inDiff = new Set(changedFiles.map((file) => file.replace(/\\/g, "/")));
1374
+ const routes = config.routes ?? {};
1375
+ const ignore = config.ignore ?? [];
1376
+ const findings = uncommitted.filter((file) => !inDiff.has(file)).map((file) => ({ file, impact: mapImpact([file], routes, ignore) })).filter(({ impact }) => impact.ignoredFiles.length === 0);
1377
+ if (findings.length === 0) return;
1378
+ console.error(
1379
+ `
1380
+ warning: ${findings.length} file(s) changed in the working tree are not in the diff against '${baseRef}':`
1381
+ );
1382
+ for (const { file, impact } of findings) {
1383
+ console.error(
1384
+ impact.affectedRoutes.length > 0 ? ` ${file} -> ${impact.affectedRoutes.join(", ")}` : ` ${file} (matched by no routes: or ignore: glob)`
1385
+ );
1386
+ }
1387
+ console.error("Nothing above was considered. Commit or stash them to include them.");
1388
+ }
1389
+
1357
1390
  // src/llm/brain.ts
1358
1391
  import { generateObject } from "ai";
1359
1392
 
@@ -2574,7 +2607,8 @@ Dry run: ${selected.length} test(s) selected, base_url=${config.base_url}`);
2574
2607
  for (const test of selected) {
2575
2608
  console.log(` ${test.summary} [${test.priority}] (${path9.relative(cwd, test.path)})`);
2576
2609
  }
2577
- const authSteps = config.auth?.steps ?? [];
2610
+ const needsLogin = selected.some((test) => test.auth);
2611
+ const authSteps = needsLogin ? config.auth?.steps ?? [] : [];
2578
2612
  const withAuth = authSteps.length > 0 ? [...selected, { steps: authSteps }] : selected;
2579
2613
  const ceiling = estimateMaxModelCalls(
2580
2614
  withAuth,
@@ -2619,6 +2653,7 @@ async function runCommand(options) {
2619
2653
  throw error;
2620
2654
  }
2621
2655
  impact = mapImpact(changedFiles, config.routes ?? {}, config.ignore ?? []);
2656
+ printUncommitted(await getUncommittedFiles(options.cwd), changedFiles, config, base);
2622
2657
  }
2623
2658
  const testsDir = path9.join(options.cwd, TESTS_RELATIVE_DIR);
2624
2659
  let files;
@@ -2711,7 +2746,7 @@ ${results.length} test file(s) could not be parsed:`);
2711
2746
  let incomplete;
2712
2747
  try {
2713
2748
  let session;
2714
- if (config.auth) {
2749
+ if (config.auth && selected.some((test) => test.auth)) {
2715
2750
  try {
2716
2751
  console.log("Authenticating...");
2717
2752
  session = await authenticate({
@@ -2832,6 +2867,7 @@ async function resolveTargets(options, config, suite) {
2832
2867
  const base = options.base ?? "main";
2833
2868
  const changedFiles = await getChangedFiles(base, options.cwd);
2834
2869
  const impact = mapImpact(changedFiles, config.routes ?? {}, config.ignore ?? []);
2870
+ printUncommitted(await getUncommittedFiles(options.cwd), changedFiles, config, base);
2835
2871
  const filesForRoute = (route) => changedFiles.filter(
2836
2872
  (file) => mapImpact([file], config.routes ?? {}, config.ignore ?? []).affectedRoutes.includes(route)
2837
2873
  );
@@ -3109,7 +3145,7 @@ function parsePositiveNumber(flag) {
3109
3145
  };
3110
3146
  }
3111
3147
  var program = new Command();
3112
- program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.18.0");
3148
+ program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.19.0");
3113
3149
  program.command("init").description("Scaffold .blastproof/ (config, tests, sample tests) in the current directory").action(async () => {
3114
3150
  try {
3115
3151
  const result = await initProject(process.cwd());