@blzsky/variant 0.2.1 → 0.3.0-canary.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.
Files changed (47) hide show
  1. package/README.md +110 -56
  2. package/dist/{build-3DSBMMI3.js → build-GKWNLU4Q.js} +9 -8
  3. package/dist/{check-SIQJU6Z2.js → check-KO5VY67Z.js} +6 -6
  4. package/dist/{chunk-FA64KWJ2.js → chunk-7NC7AO75.js} +15 -28
  5. package/dist/{chunk-VYWPQKMS.js → chunk-ACTY42RE.js} +20 -0
  6. package/dist/{chunk-ZH2PQUNL.js → chunk-CN25SRKD.js} +6 -6
  7. package/dist/{chunk-43VLELTL.js → chunk-CWF6QXEW.js} +1 -1
  8. package/dist/{chunk-7OPTWU6E.js → chunk-DMDAJPFJ.js} +1 -1
  9. package/dist/{chunk-RC23JQKI.js → chunk-DSQDMFQ6.js} +9 -1
  10. package/dist/chunk-H5C4SE73.js +184 -0
  11. package/dist/chunk-IFFZ46YS.js +575 -0
  12. package/dist/chunk-IHXJ72XA.js +31 -0
  13. package/dist/chunk-KCEGA56S.js +670 -0
  14. package/dist/{chunk-FZEXZBXB.js → chunk-MAK3HG46.js} +41 -9
  15. package/dist/chunk-MT4BOAAV.js +167 -0
  16. package/dist/{chunk-TCISS2AS.js → chunk-NFFXYYM6.js} +120 -25
  17. package/dist/{chunk-26DHEXGS.js → chunk-RJBFCURK.js} +1 -1
  18. package/dist/{chunk-6BXSZPHC.js → chunk-RSIOYQOQ.js} +67 -8
  19. package/dist/chunk-SGP5JX6I.js +377 -0
  20. package/dist/chunk-X3VVGTEV.js +268 -0
  21. package/dist/{chunk-Q7RLUZSN.js → chunk-YII4TAKS.js} +10 -3
  22. package/dist/cli.js +62 -27
  23. package/dist/{diff-HSJBZFMC.js → diff-A3TQWWUJ.js} +5 -5
  24. package/dist/{doctor-B7MHFIYJ.js → doctor-6ONS6LKY.js} +4 -4
  25. package/dist/{env-I7JZRWDN.js → env-XF2US3CB.js} +6 -6
  26. package/dist/git-W6N3WMYQ.js +24 -0
  27. package/dist/impact-DPZIAJR4.js +263 -0
  28. package/dist/index.js +2 -2
  29. package/dist/init-VNK5E4H3.js +777 -0
  30. package/dist/{insight-JQLTGFYB.js → insight-LNYVMCAE.js} +7 -7
  31. package/dist/pr-6CYMM2XU.js +707 -0
  32. package/dist/{run-C6VAQOVM.js → run-44PKMOV5.js} +9 -8
  33. package/dist/{symbol-graph-E5GXKDFZ.js → symbol-graph-OB4AJCLW.js} +3 -3
  34. package/dist/{tsconfig-paths-E3UEDGXZ.js → tsconfig-paths-SV3PULCW.js} +3 -1
  35. package/dist/vitest.cjs +629 -0
  36. package/dist/vitest.d.cts +181 -0
  37. package/dist/vitest.d.ts +181 -0
  38. package/dist/vitest.js +356 -0
  39. package/dist/workspace-PIQXG4ZD.js +65 -0
  40. package/package.json +13 -2
  41. package/dist/chunk-DRMASVYN.js +0 -25
  42. package/dist/chunk-KSHWC2XN.js +0 -78
  43. package/dist/chunk-XTYVVUWW.js +0 -279
  44. package/dist/impact-PRZ6HEP5.js +0 -735
  45. package/dist/init-O2BY3FOY.js +0 -158
  46. package/dist/pr-Y724QAHE.js +0 -165
  47. package/dist/workspace-HRTFGWKN.js +0 -186
package/README.md CHANGED
@@ -8,56 +8,68 @@
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 a change to a TypeScript repository: what
12
+ has to be verified before this is safe to merge?
13
+
14
+ It reads the diff at the level of the syntax tree, not the line, follows every
15
+ import across workspace packages, and says, on the pull request:
16
+
17
+ - how risky each changed file is, and why;
18
+ - which test files reach the change, with the import chain from each test to
19
+ the file that changed;
20
+ - which typecheck, build and lint commands apply to the packages it affects;
21
+ - which changed files no test reaches at all.
22
+
23
+ You set it up once. After that nobody types a variant command: your own test
24
+ command and your pull requests are the interface.
16
25
 
17
26
  ```bash
18
- npx @blzsky/variant impact --base origin/main
27
+ npx @blzsky/variant init
19
28
  ```
20
29
 
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>
30
+ `init` finds out how the repository installs, tests and runs CI, shows every
31
+ change it would make as a diff, and makes them when you agree. It adds one
32
+ line to your Vitest config and one workflow that keeps a comment on each pull
33
+ request.
34
+
35
+ It does not skip tests. Your full suite runs as it always did; in CI variant
36
+ predicts beside it which test files the change needs, then checks that
37
+ prediction against what failed and prints one line:
38
+
39
+ ```
40
+ variant: predicted 8 of 55 test files (high). 2 failed, all predicted.
41
+ ```
42
+
43
+ Leaving tests out is something variant has to earn in each repository, from
44
+ that record, and is not built yet.
24
45
 
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).
46
+ > **Status**: `0.x`. What is described here is published as a canary,
47
+ > `0.3.0-canary.0`, under the `canary` tag: write `@blzsky/variant@canary`
48
+ > wherever this page says `@blzsky/variant`. Without the tag npm serves
49
+ > 0.2.1, which has `impact`, `pr check`, `pr report` and `workspace check`,
50
+ > an `init` that only writes a task-runner config, and no adapter. The
51
+ > adapter is for Vitest; a Jest repository gets the pull request report
52
+ > only. Test skipping is **not implemented**, deliberately.
53
+ > The task runner underneath (`build`, `run`, `insight`) works but is frozen.
54
+ > Any minor release can break, and every break is in the
55
+ > [CHANGELOG](./CHANGELOG.md).
47
56
 
48
57
  ## Install
49
58
 
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.
59
+ variant needs Node 20 or newer and a git repository.
52
60
 
53
61
  ```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
62
+ npx @blzsky/variant init # shows the changes, asks, then applies
63
+ npx @blzsky/variant init --dry-run # only look
59
64
  ```
60
65
 
66
+ `init` installs `@blzsky/variant` as a dev dependency with your package
67
+ manager (npm, pnpm, Yarn or Bun), adds the
68
+ [Vitest adapter](./docs/vitest-adapter.md) to your test config, ignores
69
+ `.variant/`, gives your test workflow's checkout the history a prediction
70
+ needs, and writes `.github/workflows/variant.yml`. Anything it cannot edit
71
+ with confidence it leaves alone and tells you the lines to add.
72
+
61
73
  The package is `@blzsky/variant`; the binary it installs is `variant`.
62
74
  `npx variant` fetches an unrelated package, so always use the scope with `npx`.
63
75
 
@@ -67,26 +79,43 @@ The package is `@blzsky/variant`; the binary it installs is `variant`.
67
79
  npm install -D @blzsky/variant@latest
68
80
  ```
69
81
 
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.
82
+ Because variant is `0.x`, read the CHANGELOG before moving to a new minor
83
+ version.
72
84
 
73
85
  ## Usage
74
86
 
87
+ After `init` there is nothing to run. What you see:
88
+
89
+ - **On every pull request**, one comment, kept up to date: the risk of each
90
+ changed file, the tests to run and why, the checks, and what is not
91
+ verified. [An example](./docs/api.md#pr-report).
92
+ - **In every CI test run**, one line saying what was predicted and whether a
93
+ failure fell outside it.
94
+
95
+ The first runs after setup select every test, because the setup itself changes
96
+ the lockfile and the test config. Narrowed predictions start with the next
97
+ pull request.
98
+
99
+ The commands underneath are still there when you want them:
100
+
75
101
  ```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
102
+ variant pr report --markdown # the pull request comment, locally
103
+ variant impact # which tests does this change need?
80
104
  variant workspace check # undeclared dependencies (exits 1)
81
105
  ```
82
106
 
107
+ <p align="center">
108
+ <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">
109
+ </p>
110
+
83
111
  | Command | What it does |
84
112
  |---|---|
85
113
  | `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 |
114
+ | `impact verify <report...>` | Reconcile the last prediction against one or more Vitest or Jest JSON reports and report false skips |
87
115
  | `diff <file>` | Classify one file's change and list the exported symbols that changed |
116
+ | `init` | Set a repository up: the dependency, the adapter, the workflow. Once. |
88
117
  | `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 |
118
+ | `pr report` | What the branch changes and what has to be verified, as JSON or as markdown for a PR comment |
90
119
  | `workspace check` | Fail when a package imports a dependency it does not declare |
91
120
 
92
121
  Run `variant <command> --help` for its flags, or see the
@@ -103,7 +132,9 @@ setup file selects every test.
103
132
 
104
133
  ### In CI
105
134
 
106
- Check out with `fetch-depth: 0` and pass `--base origin/<branch>`. A shallow
135
+ `init` writes what CI needs. To wire the commands in by hand: check out
136
+ with `fetch-depth: 0`; the base is detected on GitHub Actions, and elsewhere
137
+ pass `--base origin/<branch>`. A shallow
107
138
  clone has no base commit to compare against, and `actions/checkout` creates no
108
139
  local branch for the target; variant stops with `GIT_REF_ERROR` rather than
109
140
  guess.
@@ -117,6 +148,9 @@ Ready-to-copy GitHub Actions workflows are in
117
148
  the build on an undeclared dependency.
118
149
  - [`impact-shadow.yml`](./examples/github-actions/impact-shadow.yml) logs a
119
150
  prediction, runs the full suite anyway, and reconciles the two.
151
+ - [`impact-shadow-workspace.yml`](./examples/github-actions/impact-shadow-workspace.yml)
152
+ does the same for a pnpm workspace that tests each package on its own. It
153
+ needs a release newer than 0.2.1.
120
154
 
121
155
  ### Help measure it
122
156
 
@@ -128,7 +162,8 @@ If you add `impact-shadow.yml` to your CI, it changes nothing about your build.
128
162
  It uploads the counts as a `variant-reconciliation` artifact: commit SHAs,
129
163
  branch names and numbers, no file paths and no source.
130
164
  [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.
165
+ and they go into the measurement that decides whether skipping ever ships. The
166
+ [measurement log](./docs/measurement.md) has every number collected so far.
132
167
 
133
168
  ## Benchmarks
134
169
 
@@ -182,24 +217,41 @@ confidence wherever it can tell it is missing something, but some edges it
182
217
  cannot see at all:
183
218
 
184
219
  - **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.
220
+ string-keyed registries and `eval` never appear as imports. A test that
221
+ reaches code only this way is not selected, and nothing in the output says
222
+ so. This is the largest blind spot.
188
223
  - **Service boundaries end the graph.** An end-to-end test that calls a running
189
224
  server imports none of the code it exercises.
190
225
  - **Setup files are recognized by name.** `vitest.setup.ts`, `setupTests.ts`,
191
226
  `global-setup.ts` and the like select every test; a setup file named
192
227
  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.
228
+ - **Files read at runtime are outside the closure.** A changed stylesheet or
229
+ JSON file that a TypeScript file imports selects that file's tests, but a
230
+ fixture, snapshot or asset a test reads with `fs` is invisible. A changed
231
+ file that reaches no test is listed as `Unreached`.
232
+ - **Only the directory it runs in is indexed.** A file changed elsewhere in
233
+ the repository selects the tests of the files that import it by relative
234
+ path, and is listed as `Unreached` otherwise.
235
+ - **Unreached is not the same as untested.** A changed file no test reaches
236
+ is listed as `Unreached` and the prediction reads `low`. variant cannot tell
237
+ an untested file from one a runtime lookup uses.
238
+ - **JavaScript is indexed when git says it is yours.** Tracked or new
239
+ `.js`, `.jsx`, `.mjs` and `.cjs` files are read like TypeScript; build
240
+ output and bundles are not. A changed CommonJS module reaches everything
241
+ that requires it.
242
+ - **Computed imports are followed only as far as their literal start.**
243
+ `` import(`./locales/${lang}.js`) `` reaches everything under `./locales/`;
244
+ `import(name)` could load anything, and is a standing note on every run.
196
245
  - **Dynamic `import()` is assumed to use everything.** The change propagates
197
246
  and a note is printed; the run gets wider, not narrower.
247
+ - **Workspace discovery can miss a package**, and a bare import of a package it
248
+ missed counts as external. `impact` prints how many packages it found and
249
+ notes imports no discovered package or manifest accounts for.
198
250
  - **Type-only changes are not narrowed.** Changing an interface selects every
199
251
  test that imports the file, though no runtime behavior changed.
200
252
  - **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.
253
+ import graph resolved, as `high`, `medium` or `low` and a percentage. Only
254
+ the false-skip rate says how safe a skip would have been.
203
255
 
204
256
  The full list, with how each case is handled, is in
205
257
  [Impact & workspace](./docs/impact-and-workspace.md#limitations).
@@ -212,11 +264,13 @@ The full list, with how each case is handled, is in
212
264
  | [Getting started](./docs/getting-started.md) | Run it on your own repository, then set up the task runner |
213
265
  | [Impact & workspace](./docs/impact-and-workspace.md) | How `impact` and `workspace check` work, and what static analysis cannot see |
214
266
  | [PR commands](./docs/pr-commands.md) | `pr check` and `pr report` |
267
+ | [Vitest adapter](./docs/vitest-adapter.md) | One config line in place of the commands: predict and check on every test run |
215
268
  | [CLI reference](./docs/cli-reference.md) | Every command, flag, exit code and file |
216
269
  | [API reference](./docs/api.md) | `defineConfig`, the config types, and every JSON output |
217
270
  | [Monorepo setup](./docs/monorepo.md) | Workspace tasks and `--affected` |
218
271
  | [Config reference](./docs/config-reference.md) | Every `variant.config.ts` key |
219
272
  | [Troubleshooting](./docs/troubleshooting.md) | Common problems and what they mean |
273
+ | [Measurement log](./docs/measurement.md) | Every prediction checked against a full run so far, and the gate skipping has to pass |
220
274
 
221
275
  ## Contributing
222
276
 
@@ -2,23 +2,24 @@ import {
2
2
  executeTarget,
3
3
  renderDryRunPlan,
4
4
  reportRunInsights
5
- } from "./chunk-FA64KWJ2.js";
6
- import "./chunk-43VLELTL.js";
5
+ } from "./chunk-7NC7AO75.js";
6
+ import "./chunk-IHXJ72XA.js";
7
+ import "./chunk-CWF6QXEW.js";
7
8
  import "./chunk-QRPI6TJE.js";
8
9
  import {
9
10
  createContext,
10
11
  toRunOptions
11
- } from "./chunk-ZH2PQUNL.js";
12
+ } from "./chunk-CN25SRKD.js";
12
13
  import "./chunk-V5CTGXMM.js";
13
- import "./chunk-26DHEXGS.js";
14
- import "./chunk-YYGCSKIQ.js";
14
+ import "./chunk-RJBFCURK.js";
15
15
  import "./chunk-GDSZFIHM.js";
16
- import "./chunk-7OPTWU6E.js";
16
+ import "./chunk-DMDAJPFJ.js";
17
17
  import "./chunk-3E5LBU3O.js";
18
- import "./chunk-6BXSZPHC.js";
18
+ import "./chunk-YYGCSKIQ.js";
19
19
  import "./chunk-6J4XDG44.js";
20
20
  import "./chunk-EISTWXDW.js";
21
- import "./chunk-VYWPQKMS.js";
21
+ import "./chunk-RSIOYQOQ.js";
22
+ import "./chunk-ACTY42RE.js";
22
23
 
23
24
  // src/core/scope/task-filter.ts
24
25
  function taskPackage(taskName) {
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  createContext
3
- } from "./chunk-ZH2PQUNL.js";
3
+ } from "./chunk-CN25SRKD.js";
4
4
  import "./chunk-V5CTGXMM.js";
5
- import "./chunk-26DHEXGS.js";
6
- import "./chunk-YYGCSKIQ.js";
7
- import "./chunk-7OPTWU6E.js";
5
+ import "./chunk-RJBFCURK.js";
6
+ import "./chunk-DMDAJPFJ.js";
8
7
  import "./chunk-3E5LBU3O.js";
9
- import "./chunk-6BXSZPHC.js";
8
+ import "./chunk-YYGCSKIQ.js";
10
9
  import {
11
10
  lines
12
11
  } from "./chunk-6J4XDG44.js";
13
12
  import {
14
13
  getPrinter
15
14
  } from "./chunk-EISTWXDW.js";
15
+ import "./chunk-RSIOYQOQ.js";
16
16
  import {
17
17
  ConfigError
18
- } from "./chunk-VYWPQKMS.js";
18
+ } from "./chunk-ACTY42RE.js";
19
19
 
20
20
  // src/core/graph/validation.ts
21
21
  function validateTaskGraph(tasks, graph) {
@@ -1,23 +1,32 @@
1
+ import {
2
+ CLEAR_LINE,
3
+ HIDE_CURSOR,
4
+ SHOW_CURSOR,
5
+ SYNC_END,
6
+ SYNC_START,
7
+ cursorUp,
8
+ fitWidth
9
+ } from "./chunk-IHXJ72XA.js";
1
10
  import {
2
11
  computeInsights,
3
12
  evictStaleEntries,
4
13
  readCache,
5
14
  writeCache,
6
15
  writeCacheSync
7
- } from "./chunk-43VLELTL.js";
16
+ } from "./chunk-CWF6QXEW.js";
8
17
  import {
9
18
  renderInsights
10
19
  } from "./chunk-QRPI6TJE.js";
11
20
  import {
12
21
  recordCacheMiss,
13
22
  runScheduled
14
- } from "./chunk-ZH2PQUNL.js";
15
- import {
16
- getColors
17
- } from "./chunk-YYGCSKIQ.js";
23
+ } from "./chunk-CN25SRKD.js";
18
24
  import {
19
25
  mapLimit
20
26
  } from "./chunk-GDSZFIHM.js";
27
+ import {
28
+ getColors
29
+ } from "./chunk-YYGCSKIQ.js";
21
30
  import {
22
31
  lines
23
32
  } from "./chunk-6J4XDG44.js";
@@ -27,7 +36,7 @@ import {
27
36
  import {
28
37
  TaskExecutionError,
29
38
  VariantError
30
- } from "./chunk-VYWPQKMS.js";
39
+ } from "./chunk-ACTY42RE.js";
31
40
 
32
41
  // src/core/execution/runner.ts
33
42
  import { createHash as createHash2 } from "crypto";
@@ -283,28 +292,6 @@ async function runTasksWithDeps(target, graph, options, executor = executeTask)
283
292
  return results;
284
293
  }
285
294
 
286
- // src/cli/visuals/ansi.ts
287
- var ESC = "\x1B";
288
- var HIDE_CURSOR = `${ESC}[?25l`;
289
- var SHOW_CURSOR = `${ESC}[?25h`;
290
- var CLEAR_LINE = `${ESC}[2K`;
291
- var SYNC_START = `${ESC}[?2026h`;
292
- var SYNC_END = `${ESC}[?2026l`;
293
- function cursorUp(lines2) {
294
- return lines2 > 0 ? `${ESC}[${lines2}A` : "";
295
- }
296
- var STYLE_PATTERN = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
297
- function stripStyles(text) {
298
- return text.replace(STYLE_PATTERN, "");
299
- }
300
- function visibleLength(line) {
301
- return stripStyles(line).length;
302
- }
303
- function fitWidth(line, columns) {
304
- if (visibleLength(line) <= columns) return line;
305
- return stripStyles(line).slice(0, Math.max(0, columns - 1));
306
- }
307
-
308
295
  // src/cli/visuals/progress.ts
309
296
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
310
297
  var TICK_MS = 80;
@@ -62,12 +62,30 @@ var GitRefError = class extends VariantError {
62
62
  this.hint = "Check the ref with `git rev-parse --verify <ref>`. In CI, check out with `fetch-depth: 0` and pass a remote-tracking ref such as `origin/main`.";
63
63
  }
64
64
  };
65
+ var NoBaseCommitError = class extends VariantError {
66
+ constructor() {
67
+ super(
68
+ "NO_BASE_COMMIT",
69
+ "This push has no previous commit to compare against"
70
+ );
71
+ this.hint = "The first push of a branch, or a force-push whose old head is gone. Pass --base to compare against a commit of your choice.";
72
+ }
73
+ };
65
74
  var ImpactReportError = class extends VariantError {
66
75
  constructor(message) {
67
76
  super("IMPACT_REPORT_ERROR", `Could not read the test report: ${message}`);
68
77
  this.hint = "Pass a Vitest `--reporter=json --outputFile=<path>` or Jest `--json --outputFile=<path>` report.";
69
78
  }
70
79
  };
80
+ var NoTestFilesError = class extends VariantError {
81
+ constructor() {
82
+ super(
83
+ "NO_TEST_FILES",
84
+ "No test files found, so there is nothing to predict"
85
+ );
86
+ this.hint = "variant finds TypeScript tests named `*.test.ts` or `*.spec.ts` (also `.tsx`, `.mts`, `.cts`) or placed under `__tests__/`. JavaScript files are not indexed.";
87
+ }
88
+ };
71
89
  var CliUsageError = class extends VariantError {
72
90
  constructor(message) {
73
91
  super("CLI_USAGE", message);
@@ -83,6 +101,8 @@ export {
83
101
  CacheError,
84
102
  GraphError,
85
103
  GitRefError,
104
+ NoBaseCommitError,
86
105
  ImpactReportError,
106
+ NoTestFilesError,
87
107
  CliUsageError
88
108
  };
@@ -7,19 +7,19 @@ import {
7
7
  } from "./chunk-V5CTGXMM.js";
8
8
  import {
9
9
  loadConfig
10
- } from "./chunk-26DHEXGS.js";
11
- import {
12
- getColors
13
- } from "./chunk-YYGCSKIQ.js";
10
+ } from "./chunk-RJBFCURK.js";
14
11
  import {
15
12
  changedFilesToPackages,
16
13
  getChangedFiles
17
- } from "./chunk-7OPTWU6E.js";
14
+ } from "./chunk-DMDAJPFJ.js";
18
15
  import {
19
16
  computeAffectedPackages,
20
17
  loadPackageGraph,
21
18
  tasksFromPackageGraph
22
19
  } from "./chunk-3E5LBU3O.js";
20
+ import {
21
+ getColors
22
+ } from "./chunk-YYGCSKIQ.js";
23
23
  import {
24
24
  errorLines
25
25
  } from "./chunk-6J4XDG44.js";
@@ -29,7 +29,7 @@ import {
29
29
  import {
30
30
  ConfigError,
31
31
  CycleError
32
- } from "./chunk-VYWPQKMS.js";
32
+ } from "./chunk-ACTY42RE.js";
33
33
 
34
34
  // src/adapters/frameworks/plugin.ts
35
35
  var wrapFrameworkAsPlugin = (a) => ({
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CacheError
3
- } from "./chunk-VYWPQKMS.js";
3
+ } from "./chunk-ACTY42RE.js";
4
4
 
5
5
  // src/core/cache/store.ts
6
6
  import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-3E5LBU3O.js";
4
4
  import {
5
5
  listChangedFiles
6
- } from "./chunk-6BXSZPHC.js";
6
+ } from "./chunk-RSIOYQOQ.js";
7
7
 
8
8
  // src/core/cache/git-diff.ts
9
9
  import path from "path";
@@ -1,4 +1,11 @@
1
1
  // src/core/semantic/surface.ts
2
+ var STAR_REEXPORT_PREFIX = "* from ";
3
+ function starReexportKey(moduleSpec) {
4
+ return `${STAR_REEXPORT_PREFIX}${moduleSpec}`;
5
+ }
6
+ function isStarReexportKey(name) {
7
+ return name.startsWith(STAR_REEXPORT_PREFIX);
8
+ }
2
9
  function collectExportedSurface(file, tsm) {
3
10
  const symbols = /* @__PURE__ */ new Map();
4
11
  const notes = [];
@@ -42,7 +49,7 @@ function collectExportedSurface(file, tsm) {
42
49
  });
43
50
  continue;
44
51
  }
45
- symbols.set(`* from ${moduleSpec}`, {
52
+ symbols.set(starReexportKey(moduleSpec), {
46
53
  signature: reexportSignature("*", moduleSpec, declTypeOnly),
47
54
  body: "",
48
55
  typeSpace: declTypeOnly,
@@ -345,6 +352,7 @@ function normalize(text) {
345
352
  }
346
353
 
347
354
  export {
355
+ isStarReexportKey,
348
356
  collectExportedSurface,
349
357
  significantText
350
358
  };