@blzsky/variant 0.2.1 → 0.3.0-canary.1

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.
Files changed (58) hide show
  1. package/README.md +222 -85
  2. package/dist/{build-3DSBMMI3.js → build-GCUFPPIB.js} +11 -10
  3. package/dist/check-AKYABBIV.js +1459 -0
  4. package/dist/{chunk-43VLELTL.js → chunk-2UVLKBLV.js} +1 -1
  5. package/dist/chunk-34VGKPEM.js +59 -0
  6. package/dist/chunk-BNACT47L.js +590 -0
  7. package/dist/chunk-C3NZIFW6.js +174 -0
  8. package/dist/{chunk-ZH2PQUNL.js → chunk-EFCMBBBT.js} +72 -110
  9. package/dist/chunk-H55GYSQ6.js +102 -0
  10. package/dist/chunk-HL65IPEQ.js +167 -0
  11. package/dist/chunk-IHXJ72XA.js +31 -0
  12. package/dist/{chunk-FZEXZBXB.js → chunk-JEJRXPCU.js} +41 -9
  13. package/dist/chunk-LUTP2TVU.js +94 -0
  14. package/dist/{chunk-7OPTWU6E.js → chunk-MKUYZIXF.js} +1 -1
  15. package/dist/chunk-MNYKLWRC.js +377 -0
  16. package/dist/{chunk-Q7RLUZSN.js → chunk-NJSTAWDM.js} +10 -3
  17. package/dist/{chunk-26DHEXGS.js → chunk-NOC53Z5Q.js} +1 -1
  18. package/dist/chunk-O6FCFZDF.js +127 -0
  19. package/dist/{chunk-6BXSZPHC.js → chunk-OON6O4S5.js} +99 -6
  20. package/dist/{chunk-FA64KWJ2.js → chunk-TPMHY4L6.js} +16 -31
  21. package/dist/chunk-TRUK3TJF.js +575 -0
  22. package/dist/chunk-WTXWEEI2.js +21 -0
  23. package/dist/{chunk-RC23JQKI.js → chunk-WULBOSG5.js} +15 -2
  24. package/dist/chunk-X3VVGTEV.js +268 -0
  25. package/dist/{chunk-TCISS2AS.js → chunk-XXMD4NC5.js} +120 -25
  26. package/dist/cli.js +89 -30
  27. package/dist/{diff-HSJBZFMC.js → diff-ST5KHT7P.js} +5 -5
  28. package/dist/{doctor-B7MHFIYJ.js → doctor-OEIY4Y5X.js} +41 -8
  29. package/dist/{env-I7JZRWDN.js → env-EQAOZCFR.js} +8 -9
  30. package/dist/git-VGHUF2SY.js +30 -0
  31. package/dist/impact-AXLZTLVT.js +266 -0
  32. package/dist/index.js +2 -2
  33. package/dist/init-QCOZWLOO.js +793 -0
  34. package/dist/{insight-JQLTGFYB.js → insight-KRWT6Z47.js} +9 -10
  35. package/dist/pr-7JXUC5BQ.js +710 -0
  36. package/dist/{run-C6VAQOVM.js → run-UOPS5FRU.js} +11 -10
  37. package/dist/stryker-host.d.ts +2 -0
  38. package/dist/stryker-host.js +135 -0
  39. package/dist/stryker-plugins.d.ts +33 -0
  40. package/dist/stryker-plugins.js +142 -0
  41. package/dist/{symbol-graph-E5GXKDFZ.js → symbol-graph-GYOI4GU4.js} +3 -3
  42. package/dist/{tsconfig-paths-E3UEDGXZ.js → tsconfig-paths-SV3PULCW.js} +3 -1
  43. package/dist/vitest.cjs +629 -0
  44. package/dist/vitest.d.cts +181 -0
  45. package/dist/vitest.d.ts +181 -0
  46. package/dist/vitest.js +358 -0
  47. package/dist/workspace-IQFLR3RY.js +65 -0
  48. package/package.json +17 -3
  49. package/dist/check-SIQJU6Z2.js +0 -44
  50. package/dist/chunk-DRMASVYN.js +0 -25
  51. package/dist/chunk-KSHWC2XN.js +0 -78
  52. package/dist/chunk-QRPI6TJE.js +0 -68
  53. package/dist/chunk-VYWPQKMS.js +0 -88
  54. package/dist/chunk-XTYVVUWW.js +0 -279
  55. package/dist/impact-PRZ6HEP5.js +0 -735
  56. package/dist/init-O2BY3FOY.js +0 -158
  57. package/dist/pr-Y724QAHE.js +0 -165
  58. package/dist/workspace-HRTFGWKN.js +0 -186
package/README.md CHANGED
@@ -8,56 +8,46 @@
8
8
 
9
9
  ## What is variant?
10
10
 
11
- variant tells you which tests a change in a TypeScript monorepo actually needs.
12
- It reads the diff at the level of the syntax tree, not the line, classifies
13
- each changed file by what it does to the file's exported surface, and follows
14
- every test file's imports across workspace packages to see which ones reach the
15
- change.
11
+ variant answers one question about the code you just changed:
12
+
13
+ > **Your tests pass. Would they notice if this code were wrong?**
14
+
15
+ `variant check` makes small, deliberate mistakes in the lines you changed, one
16
+ at a time, runs your tests against each, and lists the mistakes nobody
17
+ noticed:
16
18
 
17
- ```bash
18
- npx @blzsky/variant impact --base origin/main
19
+ ```
20
+ Undetected: 1
21
+ src/discount.ts:3:9 in qualifiesForDiscount
22
+ - total >= 100
23
+ + total > 100
24
+ Changing >= to > went undetected. Review the behavior at total = 100.
19
25
  ```
20
26
 
21
- <p align="center">
22
- <img alt="Running `variant impact --base main` after changing formatPrice's signature and documenting slugify: price.ts is breaking, slug.ts is non-impacting, 2 of 4 test files run" src="./docs/assets/impact.gif" width="840">
23
- </p>
27
+ The tests checked 50 and 150 and passed with either operator. Add a test at
28
+ 100, run it again, and the same mistake is caught. Each finding is a concrete
29
+ change to your code, not a score.
30
+
31
+ It works on the first run, with no history and no config. It looks at what you
32
+ have not committed yet, staged or not, and leaves your working tree exactly as
33
+ it was.
24
34
 
25
- In that repository, `shop` and `blog` both depend on `utils`. A tool that works
26
- from the package graph would run all four test files. variant follows the
27
- imports: only `shop` reaches the changed `price.ts`, and a doc comment on
28
- `slug.ts` selects nothing.
29
-
30
- It does not skip anything. Every prediction is logged, and
31
- `variant impact verify` checks it against the test run you did anyway,
32
- counting each failure the prediction would have missed. That count, the
33
- false-skip rate, is what has to be measured before skipping is worth turning
34
- on, and it is the point of the project right now.
35
-
36
- The same analysis powers `pr check`, which rolls a branch's changes into one
37
- build verdict for a pull request comment, and `workspace check`, which fails CI
38
- when a package imports something it never declared.
39
-
40
- > **Status**: `0.x`, report-only. `impact`, `impact verify`, `diff`,
41
- > `pr check`, `pr report` and `workspace check` work today and are where
42
- > development happens. Test skipping is **not implemented**, deliberately, and
43
- > will not be until the false-skip rate is measured on real repositories; see
44
- > [Help measure it](#help-measure-it). The task runner underneath (`build`,
45
- > `run`, `insight`) works but is frozen. Any minor release can break, and every
46
- > break is in the [CHANGELOG](./CHANGELOG.md).
35
+ > **Status**: `0.x`. `variant check` is published as a canary,
36
+ > `0.3.0-canary.1`, under the `canary` tag: install `@blzsky/variant@canary`.
37
+ > Without the tag npm serves 0.2.1, which has no `check`. Any minor release
38
+ > can break, and every break is in the [CHANGELOG](./CHANGELOG.md).
47
39
 
48
40
  ## Install
49
41
 
50
- variant needs Node 20 or newer and a git repository with at least one commit
51
- before the one you are checking. There is nothing to configure.
42
+ variant needs Node 22 or newer, a git repository, and Vitest (4.1 is tested; 5.0 has been tried).
52
43
 
53
44
  ```bash
54
- # run it once
55
- npx @blzsky/variant impact --base origin/main
56
-
57
- # keep it in a project
58
- npm install -D @blzsky/variant # or pnpm add -D / yarn add -D
45
+ npm install -D @blzsky/variant@canary
59
46
  ```
60
47
 
48
+ Install it in the package whose code you change, beside its Vitest; the Vitest
49
+ it runs your tests with is yours.
50
+
61
51
  The package is `@blzsky/variant`; the binary it installs is `variant`.
62
52
  `npx variant` fetches an unrelated package, so always use the scope with `npx`.
63
53
 
@@ -67,26 +57,147 @@ The package is `@blzsky/variant`; the binary it installs is `variant`.
67
57
  npm install -D @blzsky/variant@latest
68
58
  ```
69
59
 
70
- Because variant is `0.x`, pin a version in CI (`npx --yes @blzsky/variant@0.2.1`)
71
- and read the CHANGELOG before moving to a new minor version.
60
+ Because variant is `0.x`, read the CHANGELOG before moving to a new minor
61
+ version.
72
62
 
73
63
  ## Usage
74
64
 
75
65
  ```bash
76
- variant impact --base origin/main # which tests does this branch need?
77
- npx vitest run --reporter=json --outputFile=report.json
78
- variant impact verify report.json # did the prediction miss a failure?
79
- variant pr check --base origin/main # one build verdict for the branch
66
+ npx @blzsky/variant check # before you commit
67
+ npx @blzsky/variant check --base main # everything this branch changes
68
+ npx @blzsky/variant check --fail-on-survivors --json # in CI
69
+ ```
70
+
71
+ Run it from the package whose code changed, where its Vitest config is.
72
+
73
+ - **What it compares against.** With uncommitted changes, HEAD: your staged
74
+ and unstaged edits. On a clean branch, the commit it left the default branch
75
+ at. On GitHub Actions, the pull request's merge base, or the commit a push
76
+ replaced. When none of these applies it stops and asks for `--base` instead
77
+ of guessing.
78
+ - **What it mutates.** Comparisons (`>=` to `>`, `===` to `!==`), boolean
79
+ conditions (`&&` to `||`, a condition forced to `true` or `false`) and
80
+ arithmetic (`+` to `-`), in the changed lines of TypeScript source files,
81
+ and all of a new file you have not `git add`ed yet. Test files, declaration
82
+ files and files whose change is only formatting or comments are not
83
+ mutated; the report lists them and why.
84
+ - **First, the tests must pass as they are.** If one fails, variant stops: a
85
+ failure could not then be told apart from one a mutation caused.
86
+ - **What it reports.** Each mutation the tests did not notice, with the exact
87
+ change and what to review; code no test executed, listed apart; timeouts and
88
+ errors as inconclusive; what was not run and why; how many tests ran; and how
89
+ long it took. When every mutation is detected it says so, and says that this
90
+ is evidence the tests check those lines, not proof the change is correct.
91
+ - **Bounded.** At most `--max-mutants` mutations (default 50, the same ones
92
+ every run) within `--budget` (default 5 minutes, the first test run
93
+ included). A run that stops early says `Incomplete` and lists what it did
94
+ not run. It never reads as clean.
95
+ - **Advisory.** It exits 0 whatever it finds. `--fail-on-survivors` exits 1
96
+ when a mutation went undetected or unreached, or the run is incomplete.
97
+
98
+ The [`check` guide](./docs/check.md) has every flag, the CI setup, and how to
99
+ read a result. [`examples/check-demo`](./examples/check-demo) is the example
100
+ above, ready to run.
101
+
102
+ ## How it compares
103
+
104
+ **Mutation testing is not new.** [StrykerJS](https://stryker-mutator.io/)
105
+ mutates JavaScript and TypeScript and runs your tests against each mutant, has
106
+ an incremental mode that reuses earlier results, and can be pointed at a set of
107
+ files or lines. variant runs on Stryker; it does not replace it. What variant
108
+ adds is the workflow around it: it works out which lines changed, mutates only
109
+ those, keeps the run bounded, explains each surviving mutation in terms of the
110
+ line you wrote, and leaves your working tree alone. If you already run Stryker
111
+ over a whole project, you have the data `check` gives you, and more.
112
+
113
+ **Coverage is weaker evidence.** A line counts as covered when a test runs it,
114
+ whether or not anything checks the result. The example above is 100% covered.
115
+
116
+ ## Limitations
117
+
118
+ - **A surviving mutation is a finding to review, not a bug.** Some mutations
119
+ change nothing a valid input can observe: `>=` and `>` agree everywhere a
120
+ value can never equal the boundary. variant does not detect those, and does
121
+ not promise to.
122
+ - **Detected is not verified.** All mutations detected means the tests that ran
123
+ noticed these particular mistakes. It says nothing about mistakes of other
124
+ kinds, or about lines that were not mutated.
125
+ - **Only three kinds of mistake.** Comparisons, conditions and arithmetic.
126
+ Strings, literals, removed calls and removed statements are not mutated.
127
+ - **Deleted lines cannot be mutated.** A change that only removes code gets no
128
+ mutation; the report says how many lines that was.
129
+ - **Mutations are not type-checked.** One that TypeScript would reject still
130
+ runs, because Vitest does not check types.
131
+ - **Code run in another process reads as unreached.** Coverage is measured in
132
+ the test process, so a test that drives a CLI or a server it starts does not
133
+ count as executing that code.
134
+ - **Flaky tests can count as detection.** A test that fails at random while a
135
+ mutation is in place is read as noticing it.
136
+ - **One Vitest project, run from its package.** Workspace roots, Vitest browser
137
+ mode and Jest are not supported yet.
138
+
139
+ ## Pull request reports
140
+
141
+ variant also explains pull requests: one comment on each that says what the
142
+ change affects, which tests reach it and why, and what no test reaches. This is
143
+ what `variant init` sets up. It predates `check`, and works as it did.
144
+
145
+ ### Setting up
146
+
147
+ ```bash
148
+ npx @blzsky/variant init # shows the changes, asks, then applies
149
+ npx @blzsky/variant init --dry-run # only look
150
+ ```
151
+
152
+ ```bash
153
+ npx @blzsky/variant init # shows the changes, asks, then applies
154
+ npx @blzsky/variant init --dry-run # only look
155
+ ```
156
+
157
+ `init` installs `@blzsky/variant` as a dev dependency with your package
158
+ manager (npm, pnpm, Yarn or Bun), adds the
159
+ [Vitest adapter](./docs/vitest-adapter.md) to your test config, ignores
160
+ `.variant/`, gives your test workflow's checkout the history a prediction
161
+ needs, and writes `.github/workflows/variant.yml`. Anything it cannot edit
162
+ with confidence it leaves alone and tells you the lines to add.
163
+
164
+ The package is `@blzsky/variant`; the binary it installs is `variant`.
165
+ `npx variant` fetches an unrelated package, so always use the scope with `npx`.
166
+
167
+ ### What you see
168
+
169
+ After `init` there is nothing to run. What you see:
170
+
171
+ - **On every pull request**, one comment, kept up to date: the risk of each
172
+ changed file, the tests to run and why, the checks, and what is not
173
+ verified. [An example](./docs/api.md#pr-report).
174
+ - **In every CI test run**, one line saying what was predicted and whether a
175
+ failure fell outside it.
176
+
177
+ The first runs after setup select every test, because the setup itself changes
178
+ the lockfile and the test config. Narrowed predictions start with the next
179
+ pull request.
180
+
181
+ The commands underneath are still there when you want them:
182
+
183
+ ```bash
184
+ variant pr report --markdown # the pull request comment, locally
185
+ variant impact # which tests does this change need?
80
186
  variant workspace check # undeclared dependencies (exits 1)
81
187
  ```
82
188
 
189
+ <p align="center">
190
+ <img alt="Running `variant impact --base main` after changing formatPrice's signature and documenting slugify: price.ts is breaking, slug.ts is non-impacting, 2 of 4 test files run" src="./docs/assets/impact.gif" width="840">
191
+ </p>
192
+
83
193
  | Command | What it does |
84
194
  |---|---|
85
195
  | `impact` | Predict which test files a change requires, and how much of the import graph resolved. Report-only. |
86
- | `impact verify <report>` | Reconcile the last prediction against a Vitest or Jest JSON report and report false skips |
196
+ | `impact verify <report...>` | Reconcile the last prediction against one or more Vitest or Jest JSON reports and report false skips |
87
197
  | `diff <file>` | Classify one file's change and list the exported symbols that changed |
198
+ | `init` | Set a repository up: the dependency, the adapter, the workflow. Once. |
88
199
  | `pr check` | Classify every TypeScript change on the branch and roll them into one build verdict |
89
- | `pr report` | The `pr check` verdict as JSON, or as markdown for a PR comment |
200
+ | `pr report` | What the branch changes and what has to be verified, as JSON or as markdown for a PR comment |
90
201
  | `workspace check` | Fail when a package imports a dependency it does not declare |
91
202
 
92
203
  Run `variant <command> --help` for its flags, or see the
@@ -101,9 +212,11 @@ follows the importers of the changed names for `pr check`'s verdict. A change
101
212
  to `package.json`, a lockfile, `tsconfig*.json`, a test runner config or a test
102
213
  setup file selects every test.
103
214
 
104
- ### In CI
215
+ ### In CI, by hand
105
216
 
106
- Check out with `fetch-depth: 0` and pass `--base origin/<branch>`. A shallow
217
+ `init` writes what CI needs. To wire the commands in by hand: check out
218
+ with `fetch-depth: 0`; the base is detected on GitHub Actions, and elsewhere
219
+ pass `--base origin/<branch>`. A shallow
107
220
  clone has no base commit to compare against, and `actions/checkout` creates no
108
221
  local branch for the target; variant stops with `GIT_REF_ERROR` rather than
109
222
  guess.
@@ -117,6 +230,9 @@ Ready-to-copy GitHub Actions workflows are in
117
230
  the build on an undeclared dependency.
118
231
  - [`impact-shadow.yml`](./examples/github-actions/impact-shadow.yml) logs a
119
232
  prediction, runs the full suite anyway, and reconciles the two.
233
+ - [`impact-shadow-workspace.yml`](./examples/github-actions/impact-shadow-workspace.yml)
234
+ does the same for a pnpm workspace that tests each package on its own. It
235
+ needs a release newer than 0.2.1.
120
236
 
121
237
  ### Help measure it
122
238
 
@@ -128,34 +244,10 @@ If you add `impact-shadow.yml` to your CI, it changes nothing about your build.
128
244
  It uploads the counts as a `variant-reconciliation` artifact: commit SHAs,
129
245
  branch names and numbers, no file paths and no source.
130
246
  [Share them in an issue](https://github.com/saintparish4/variant/issues/new?template=3.shadow_results.yml),
131
- and they go into the measurement that decides whether skipping ever ships.
132
-
133
- ## Benchmarks
134
-
135
- Measured by CI on 2026-09-28 with [`benchmarks/bench.mjs`](./benchmarks/bench.mjs),
136
- on a generated git repository of 300 TypeScript files with 25 changed. Each
137
- number is the median of the runs shown. Machine: AMD EPYC 9V74 (4 cores),
138
- 16 GB RAM, Linux, Node 22.23.2, hyperfine 1.18.0.
247
+ and they go into the measurement that decides whether skipping ever ships. The
248
+ [measurement log](./docs/measurement.md) has every number collected so far.
139
249
 
140
- | | Median | Runs |
141
- |---|---:|---:|
142
- | `variant --help` (startup) | 36.7 ms | 20 |
143
- | `variant impact`, 25 changed files of 300 | 988 ms | 10 |
144
-
145
- - **Startup is a CI gate.** The benchmark workflow fails when the `--help`
146
- median passes 200 ms, because every command is loaded only when it runs.
147
- - **The symbol index is cached.** `.variant/graph/symbols.json` keeps each
148
- file's exports and imports keyed by content hash, so a later run re-parses
149
- only the files whose content changed.
150
- - **Large real repositories are not measured yet.** 300 generated files say
151
- how the pipeline behaves, not what it costs on your monorepo. Whether the
152
- analysis pays for itself depends on how long your suite takes, and that
153
- number has to come from real runs.
154
-
155
- Reproduce with `pnpm build && pnpm bench`; the methodology is in
156
- [`benchmarks/README.md`](./benchmarks/README.md).
157
-
158
- ## How it compares
250
+ ### How the reports compare
159
251
 
160
252
  **Nx `affected`, Turborepo `--filter`.** These work at the level of projects
161
253
  and tasks: a change inside `utils` reruns the test target of `utils` and of
@@ -174,7 +266,7 @@ file runs, whichever functions it calls. The exported-surface classification
174
266
  decides `pr check`'s build verdict and whether a change selects anything, not
175
267
  which functions a test touches.
176
268
 
177
- ## Limitations
269
+ ### What the reports cannot see
178
270
 
179
271
  Worth knowing before you trust a prediction. variant reads source statically,
180
272
  so it sees imports and nothing else. It widens the selection or lowers its
@@ -182,41 +274,86 @@ confidence wherever it can tell it is missing something, but some edges it
182
274
  cannot see at all:
183
275
 
184
276
  - **Runtime-only wiring is invisible.** Dependency injection by token,
185
- string-keyed registries, computed `require()` and `eval` never appear as
186
- imports. A test that reaches code only this way is not selected, and nothing
187
- in the output says so. This is the largest blind spot.
277
+ string-keyed registries and `eval` never appear as imports. A test that
278
+ reaches code only this way is not selected, and nothing in the output says
279
+ so. This is the largest blind spot.
188
280
  - **Service boundaries end the graph.** An end-to-end test that calls a running
189
281
  server imports none of the code it exercises.
190
282
  - **Setup files are recognized by name.** `vitest.setup.ts`, `setupTests.ts`,
191
283
  `global-setup.ts` and the like select every test; a setup file named
192
284
  anything else selects none. variant does not read the runner config.
193
- - **Fixtures, snapshots and non-TypeScript assets** a test reads at runtime are
194
- outside its import closure. Unresolved imports lower the confidence score and
195
- add a note, but the missing edges stay missing.
285
+ - **Files read at runtime are outside the closure.** A changed stylesheet or
286
+ JSON file that a TypeScript file imports selects that file's tests, but a
287
+ fixture, snapshot or asset a test reads with `fs` is invisible. A changed
288
+ file that reaches no test is listed as `Unreached`.
289
+ - **Only the directory it runs in is indexed.** A file changed elsewhere in
290
+ the repository selects the tests of the files that import it by relative
291
+ path, and is listed as `Unreached` otherwise.
292
+ - **Unreached is not the same as untested.** A changed file no test reaches
293
+ is listed as `Unreached` and the prediction reads `low`. variant cannot tell
294
+ an untested file from one a runtime lookup uses.
295
+ - **JavaScript is indexed when git says it is yours.** Tracked or new
296
+ `.js`, `.jsx`, `.mjs` and `.cjs` files are read like TypeScript; build
297
+ output and bundles are not. A changed CommonJS module reaches everything
298
+ that requires it.
299
+ - **Computed imports are followed only as far as their literal start.**
300
+ `` import(`./locales/${lang}.js`) `` reaches everything under `./locales/`;
301
+ `import(name)` could load anything, and is a standing note on every run.
196
302
  - **Dynamic `import()` is assumed to use everything.** The change propagates
197
303
  and a note is printed; the run gets wider, not narrower.
304
+ - **Workspace discovery can miss a package**, and a bare import of a package it
305
+ missed counts as external. `impact` prints how many packages it found and
306
+ notes imports no discovered package or manifest accounts for.
198
307
  - **Type-only changes are not narrowed.** Changing an interface selects every
199
308
  test that imports the file, though no runtime behavior changed.
200
309
  - **The confidence score is not a safety number.** It says how much of the
201
- import graph resolved. Only the false-skip rate says how safe a skip would
202
- have been.
310
+ import graph resolved, as `high`, `medium` or `low` and a percentage. Only
311
+ the false-skip rate says how safe a skip would have been.
203
312
 
204
313
  The full list, with how each case is handled, is in
205
314
  [Impact & workspace](./docs/impact-and-workspace.md#limitations).
206
315
 
316
+ ## Benchmarks
317
+
318
+ Measured by CI on 2026-09-28 with [`benchmarks/bench.mjs`](./benchmarks/bench.mjs),
319
+ on a generated git repository of 300 TypeScript files with 25 changed. Each
320
+ number is the median of the runs shown. Machine: AMD EPYC 9V74 (4 cores),
321
+ 16 GB RAM, Linux, Node 22.23.2, hyperfine 1.18.0.
322
+
323
+ | | Median | Runs |
324
+ |---|---:|---:|
325
+ | `variant --help` (startup) | 36.7 ms | 20 |
326
+ | `variant impact`, 25 changed files of 300 | 988 ms | 10 |
327
+
328
+ - **Startup is a CI gate.** The benchmark workflow fails when the `--help`
329
+ median passes 200 ms, because every command is loaded only when it runs.
330
+ - **The symbol index is cached.** `.variant/graph/symbols.json` keeps each
331
+ file's exports and imports keyed by content hash, so a later run re-parses
332
+ only the files whose content changed.
333
+ - **Large real repositories are not measured yet.** 300 generated files say
334
+ how the pipeline behaves, not what it costs on your monorepo. Whether the
335
+ analysis pays for itself depends on how long your suite takes, and that
336
+ number has to come from real runs.
337
+
338
+ Reproduce with `pnpm build && pnpm bench`; the methodology is in
339
+ [`benchmarks/README.md`](./benchmarks/README.md).
340
+
207
341
  ## Documentation
208
342
 
209
343
  | | |
210
344
  |---|---|
345
+ | [`check`](./docs/check.md) | Would your tests notice? Flags, CI, reading a result, limits |
211
346
  | [Tutorial](./docs/tutorial.md) | Every command, step by step, on a sample monorepo |
212
347
  | [Getting started](./docs/getting-started.md) | Run it on your own repository, then set up the task runner |
213
348
  | [Impact & workspace](./docs/impact-and-workspace.md) | How `impact` and `workspace check` work, and what static analysis cannot see |
214
349
  | [PR commands](./docs/pr-commands.md) | `pr check` and `pr report` |
350
+ | [Vitest adapter](./docs/vitest-adapter.md) | One config line in place of the commands: predict and check on every test run |
215
351
  | [CLI reference](./docs/cli-reference.md) | Every command, flag, exit code and file |
216
352
  | [API reference](./docs/api.md) | `defineConfig`, the config types, and every JSON output |
217
353
  | [Monorepo setup](./docs/monorepo.md) | Workspace tasks and `--affected` |
218
354
  | [Config reference](./docs/config-reference.md) | Every `variant.config.ts` key |
219
355
  | [Troubleshooting](./docs/troubleshooting.md) | Common problems and what they mean |
356
+ | [Measurement log](./docs/measurement.md) | Every prediction checked against a full run so far, and the gate skipping has to pass |
220
357
 
221
358
  ## Contributing
222
359
 
@@ -2,23 +2,24 @@ import {
2
2
  executeTarget,
3
3
  renderDryRunPlan,
4
4
  reportRunInsights
5
- } from "./chunk-FA64KWJ2.js";
6
- import "./chunk-43VLELTL.js";
7
- import "./chunk-QRPI6TJE.js";
5
+ } from "./chunk-TPMHY4L6.js";
6
+ import "./chunk-2UVLKBLV.js";
8
7
  import {
9
8
  createContext,
10
9
  toRunOptions
11
- } from "./chunk-ZH2PQUNL.js";
12
- import "./chunk-V5CTGXMM.js";
13
- import "./chunk-26DHEXGS.js";
14
- import "./chunk-YYGCSKIQ.js";
10
+ } from "./chunk-EFCMBBBT.js";
11
+ import "./chunk-H55GYSQ6.js";
15
12
  import "./chunk-GDSZFIHM.js";
16
- import "./chunk-7OPTWU6E.js";
13
+ import "./chunk-MKUYZIXF.js";
14
+ import "./chunk-IHXJ72XA.js";
15
+ import "./chunk-V5CTGXMM.js";
17
16
  import "./chunk-3E5LBU3O.js";
18
- import "./chunk-6BXSZPHC.js";
19
17
  import "./chunk-6J4XDG44.js";
18
+ import "./chunk-NOC53Z5Q.js";
19
+ import "./chunk-YYGCSKIQ.js";
20
20
  import "./chunk-EISTWXDW.js";
21
- import "./chunk-VYWPQKMS.js";
21
+ import "./chunk-OON6O4S5.js";
22
+ import "./chunk-C3NZIFW6.js";
22
23
 
23
24
  // src/core/scope/task-filter.ts
24
25
  function taskPackage(taskName) {