diff --git a/.github/scripts/upgrade-deps.ts b/.github/scripts/upgrade-deps.ts
index 3f7d4ac1ad..f5d0b819c7 100644
--- a/.github/scripts/upgrade-deps.ts
+++ b/.github/scripts/upgrade-deps.ts
@@ -152,13 +152,41 @@ async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise<voi
   let content = fs.readFileSync(filePath, 'utf8');
 
   // oxlint's trailing \n in the pattern disambiguates from oxlint-tsgolint.
+  // All @vitest/* catalog entries (browser + core direct deps) must stay pinned
+  // to the same exact version as `vitest` itself, otherwise the catalog drifts
+  // from VITEST_VERSION.
+  const vitestExactVersionPackages = [
+    '@vitest/browser',
+    '@vitest/browser-playwright',
+    '@vitest/browser-preview',
+    '@vitest/browser-webdriverio',
+    '@vitest/expect',
+    '@vitest/mocker',
+    '@vitest/pretty-format',
+    '@vitest/runner',
+    '@vitest/snapshot',
+    '@vitest/spy',
+    '@vitest/utils',
+  ];
+  const vitestExactVersionEntries: PnpmWorkspaceEntry[] = vitestExactVersionPackages.map((pkg) => ({
+    name: pkg,
+    pattern: new RegExp(`'${pkg.replaceAll('/', '\\/')}': ([\\d.]+(?:-[\\w.]+)?)`),
+    replacement: `'${pkg}': ${versions.vitest}`,
+    newVersion: versions.vitest,
+  }));
   const entries: PnpmWorkspaceEntry[] = [
     {
       name: 'vitest',
-      pattern: /vitest-dev: npm:vitest@\^([\d.]+(?:-[\w.]+)?)/,
-      replacement: `vitest-dev: npm:vitest@^${versions.vitest}`,
+      // The `@voidzero-dev/vite-plus-test` wrapper (which used to be aliased
+      // here via `vitest-dev: npm:vitest@^…`) has been removed. Vitest is now
+      // a plain catalog entry pinned to an exact version (`vitest: x.y.z`),
+      // so match that shape directly. The leading newline anchor disambiguates
+      // from neighbouring keys like `vitepress-*` and `@vitest/browser`.
+      pattern: /\n {2}vitest: ([\d.]+(?:-[\w.]+)?)\n/,
+      replacement: `\n  vitest: ${versions.vitest}\n`,
       newVersion: versions.vitest,
     },
+    ...vitestExactVersionEntries,
     {
       name: 'tsdown',
       pattern: /tsdown: \^([\d.]+(?:-[\w.]+)?)/,
@@ -246,34 +274,129 @@ async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise<voi
   console.log('Updated pnpm-workspace.yaml');
 }
 
-// ============ Update packages/test/package.json ============
-async function updateTestPackage(vitestVersion: string): Promise<void> {
-  const filePath = path.join(ROOT, 'packages/test/package.json');
-  const pkg: PackageJson = readJsonFile(filePath);
-  const devDependencies = pkg.devDependencies;
-  if (!devDependencies) {
-    throw new Error('packages/test/package.json is missing devDependencies');
+// ============ Update VITEST_VERSION constant ============
+// Keeps the TypeScript source-of-truth (`packages/cli/src/utils/constants.ts`)
+// in sync with the `vitest:` catalog entry in pnpm-workspace.yaml. The
+// constant is consumed by both `packages/cli` and `ecosystem-ci/patch-project.ts`
+// (which re-imports it), so daily upstream bumps must update it here too.
+async function updateVitestVersionConstant(vitestVersion: string): Promise<void> {
+  const filePath = path.join(ROOT, 'packages/cli/src/utils/constants.ts');
+  const content = fs.readFileSync(filePath, 'utf8');
+  const pattern = /export const VITEST_VERSION = '([\d.]+(?:-[\w.]+)?)';/;
+  let oldVersion: string | undefined;
+  const updated = content.replace(pattern, (_match: string, captured: string) => {
+    oldVersion = captured;
+    return `export const VITEST_VERSION = '${vitestVersion}';`;
+  });
+  if (oldVersion === undefined) {
+    throw new Error(
+      `Failed to match VITEST_VERSION in ${filePath} — the pattern ${pattern} is stale, ` +
+        `please update it in .github/scripts/upgrade-deps.ts`,
+    );
   }
+  fs.writeFileSync(filePath, updated);
+  recordChange('VITEST_VERSION constant', oldVersion, vitestVersion);
+  console.log('Updated packages/cli/src/utils/constants.ts');
+}
 
-  // Update all @vitest/* devDependencies
-  for (const dep of Object.keys(devDependencies)) {
-    if (dep.startsWith('@vitest/')) {
-      devDependencies[dep] = vitestVersion;
+// ============ Update .github/workflows/test-vp-create.yml ============
+// The `vp create` smoke-test workflow pins every vitest-family package via the
+// `VP_OVERRIDE_PACKAGES` env var so that template installs use the bundled
+// version. Daily upstream bumps must rewrite those pins so the workflow does
+// not drift behind the rest of the repo.
+async function updateTestVpCreateWorkflow(vitestVersion: string): Promise<void> {
+  const filePath = path.join(ROOT, '.github/workflows/test-vp-create.yml');
+  const content = fs.readFileSync(filePath, 'utf8');
+  const vitestKeys = [
+    'vitest',
+    '@vitest/expect',
+    '@vitest/runner',
+    '@vitest/snapshot',
+    '@vitest/spy',
+    '@vitest/utils',
+    '@vitest/mocker',
+    '@vitest/pretty-format',
+    '@vitest/coverage-v8',
+    '@vitest/coverage-istanbul',
+  ];
+  let updated = content;
+  let oldVersion: string | undefined;
+  for (const key of vitestKeys) {
+    const pattern = new RegExp(`"${key.replaceAll('/', '\\/')}":"([\\d.]+(?:-[\\w.]+)?)"`);
+    let matched = false;
+    updated = updated.replace(pattern, (_match: string, captured: string) => {
+      matched = true;
+      oldVersion ??= captured;
+      return `"${key}":"${vitestVersion}"`;
+    });
+    if (!matched) {
+      throw new Error(
+        `Failed to match "${key}" in ${filePath} — the pattern ${pattern} is stale, ` +
+          `please update it in .github/scripts/upgrade-deps.ts`,
+      );
     }
   }
+  fs.writeFileSync(filePath, updated);
+  recordChange('test-vp-create workflow', oldVersion ?? null, vitestVersion);
+  console.log('Updated .github/workflows/test-vp-create.yml');
+}
 
-  // Update vitest-dev devDependency
-  if (devDependencies['vitest-dev']) {
-    devDependencies['vitest-dev'] = `^${vitestVersion}`;
-  }
-
-  // Update @vitest/ui peerDependency if present
-  if (pkg.peerDependencies?.['@vitest/ui']) {
-    pkg.peerDependencies['@vitest/ui'] = vitestVersion;
+// ============ Update README.md manual-migration vitest pins ============
+// The manual-migration guide pins `vitest` to an exact version in three places —
+// the npm/Bun `overrides` block, the pnpm-workspace `overrides` block, and the
+// Yarn `resolutions` block — so a hand-migrated project shares one Vitest copy
+// with the bundled `vp test`. Those literals are NOT interpolated from
+// VITEST_VERSION, so a daily bump must rewrite them or the guide drifts behind the
+// bundled version. The packages/cli/README.md mirror (refreshed from the root
+// README's suffix at build time) is kept in sync here too so the daily PR stays
+// self-consistent without depending on a build step running first.
+async function updateReadmeVitestPins(vitestVersion: string): Promise<void> {
+  const readmePaths = [path.join(ROOT, 'README.md'), path.join(ROOT, 'packages/cli/README.md')];
+  // JSON form: `"vitest": "4.1.9"` — npm/Bun `overrides` + Yarn `resolutions` (2 blocks)
+  const jsonPattern = /("vitest": ")[\d.]+(?:-[\w.]+)?(")/g;
+  // YAML form: `  vitest: 4.1.9` — pnpm-workspace `overrides` (1 block)
+  const yamlPattern = /(\n\s*vitest: )[\d.]+(?:-[\w.]+)?(\n)/g;
+  // Both READMEs carry the same three manual-migration pins (the cli copy mirrors the
+  // root's suffix). Assert the exact shape so a daily run fails loudly — like every
+  // other updater here — if a block is reworded or removed, instead of silently
+  // shipping a README where only some pins were bumped.
+  const EXPECTED_JSON = 2;
+  const EXPECTED_YAML = 1;
+  let oldVersion: string | undefined;
+  const capture = (match: string): void => {
+    const found = /(\d[\d.]*(?:-[\w.]+)?)/.exec(match)?.[1];
+    if (found && oldVersion === undefined) {
+      oldVersion = found;
+    }
+  };
+  for (const filePath of readmePaths) {
+    if (!fs.existsSync(filePath)) {
+      continue;
+    }
+    const content = fs.readFileSync(filePath, 'utf8');
+    let jsonMatched = 0;
+    let yamlMatched = 0;
+    let updated = content.replace(jsonPattern, (match: string, pre: string, post: string) => {
+      jsonMatched++;
+      capture(match);
+      return `${pre}${vitestVersion}${post}`;
+    });
+    updated = updated.replace(yamlPattern, (match: string, pre: string, post: string) => {
+      yamlMatched++;
+      capture(match);
+      return `${pre}${vitestVersion}${post}`;
+    });
+    if (jsonMatched !== EXPECTED_JSON || yamlMatched !== EXPECTED_YAML) {
+      throw new Error(
+        `Expected ${EXPECTED_JSON} JSON + ${EXPECTED_YAML} YAML vitest pins in ${filePath}, ` +
+          `found ${jsonMatched} + ${yamlMatched} — the manual-migration section changed, ` +
+          `please update updateReadmeVitestPins in .github/scripts/upgrade-deps.ts`,
+      );
+    }
+    fs.writeFileSync(filePath, updated);
+    console.log(`Updated ${path.relative(ROOT, filePath)}`);
   }
-
-  fs.writeFileSync(filePath, JSON.stringify(pkg, null, 2) + '\n');
-  console.log('Updated packages/test/package.json');
+  recordChange('README vitest pins', oldVersion ?? null, vitestVersion);
 }
 
 // ============ Update packages/core/package.json ============
@@ -430,7 +553,9 @@ await updatePnpmWorkspace({
   oxcParser: oxcParserVersion,
   oxcTransform: oxcTransformVersion,
 });
-await updateTestPackage(vitestVersion);
+await updateVitestVersionConstant(vitestVersion);
+await updateTestVpCreateWorkflow(vitestVersion);
+await updateReadmeVitestPins(vitestVersion);
 await updateCorePackage(devtoolsVersion);
 
 writeMetaFiles();
diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index 6bcd13c694..e63f1a51f1 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -109,9 +109,32 @@ jobs:
       - name: Pack packages into tgz
         run: |
           mkdir -p tmp/tgz
+          # Every tgz consumer below references fixed `*-0.0.0.tgz` filenames.
+          # A release commit can leave `packages/{core,cli}` at a published
+          # version (e.g. 0.1.22), which would make `pnpm pack` emit
+          # `*-0.1.22.tgz` instead. Pin both to 0.0.0 so the names stay stable.
+          (cd packages/core && npm pkg set version=0.0.0)
+          (cd packages/cli && npm pkg set version=0.0.0)
           cd packages/core && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
-          cd packages/test && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
           cd packages/cli && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
+          # Bun is uniquely strict about peer-dep resolution:
+          #   1. It checks the *resolved target's* package name and version
+          #      against the peer range (vitest 4.1.9 declares peer
+          #      `vite ^6 || ^7 || ^8`).
+          #   2. A file: override pointing at the vite-plus-core tgz fails
+          #      both the name check (target is `@voidzero-dev/vite-plus-core`,
+          #      not `vite`) and the version check (0.0.0 is outside `^6|^7|^8`).
+          # pnpm/npm/yarn don't enforce either, and using the same core tgz as
+          # the file: target for both `vite` and `@voidzero-dev/vite-plus-core`
+          # is the only configuration they install cleanly. See
+          # https://github.com/oven-sh/bun/issues/8406.
+          #
+          # Generate a sibling vite-7.99.0.tgz: a copy of the core tgz with
+          # `package.json#name` rewritten to "vite" and `version` to 7.99.0.
+          # ecosystem-ci/patch-project.ts points its vite override at this
+          # tgz only for bun-based projects (e.g. bun-vite-template); pnpm-,
+          # npm- and yarn-based ecosystem projects use the real core tgz.
+          pnpm exec tool repack-vite-tgz tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz tmp/tgz/vite-7.99.0.tgz vite 7.99.0
           # Copy vp binary for e2e-test job (findVpBinary expects it in target/)
           cp target/${{ matrix.target }}/release/vp tmp/tgz/vp 2>/dev/null || cp target/${{ matrix.target }}/release/vp.exe tmp/tgz/vp.exe 2>/dev/null || true
           cp target/${{ matrix.target }}/release/vp-shim.exe tmp/tgz/vp-shim.exe 2>/dev/null || true
diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml
index 5d5d062284..8291ad7b36 100644
--- a/.github/workflows/prepare_release.yml
+++ b/.github/workflows/prepare_release.yml
@@ -54,7 +54,6 @@ jobs:
           }
           update_json packages/cli/package.json
           update_json packages/core/package.json
-          update_json packages/test/package.json
           update_toml packages/cli/binding/Cargo.toml
           update_toml crates/vite_global_cli/Cargo.toml
 
diff --git a/.github/workflows/publish-to-pkg.pr.new.yml b/.github/workflows/publish-to-pkg.pr.new.yml
index e343d57855..e367da6166 100644
--- a/.github/workflows/publish-to-pkg.pr.new.yml
+++ b/.github/workflows/publish-to-pkg.pr.new.yml
@@ -136,5 +136,4 @@ jobs:
             './packages/cli/cli-npm/*' \
             './packages/cli' \
             './packages/core' \
-            './packages/prompts' \
-            './packages/test'
+            './packages/prompts'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1539b7291f..97bd0e0c0b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -161,7 +161,6 @@ jobs:
       - name: Publish
         run: |
           pnpm publish --filter=./packages/core --tag latest --access public --no-git-checks
-          pnpm publish --filter=./packages/test --tag latest --access public --no-git-checks
           pnpm publish --filter=./packages/cli --tag latest --access public --no-git-checks
 
       - name: Create release body
@@ -177,7 +176,6 @@ jobs:
           ### Published Packages
 
           - \`@voidzero-dev/vite-plus-core@${VERSION}\`
-          - \`@voidzero-dev/vite-plus-test@${VERSION}\`
           - \`vite-plus@${VERSION}\`
 
           ### Installation
@@ -228,7 +226,6 @@ jobs:
 
             **Published Packages:**
             • @voidzero-dev/vite-plus-core@${{ env.VERSION }}
-            • @voidzero-dev/vite-plus-test@${{ env.VERSION }}
             • vite-plus@${{ env.VERSION }}
 
             **Install:**
diff --git a/.github/workflows/test-vp-create.yml b/.github/workflows/test-vp-create.yml
index 5c1afaefd0..1b04f32029 100644
--- a/.github/workflows/test-vp-create.yml
+++ b/.github/workflows/test-vp-create.yml
@@ -95,9 +95,34 @@ jobs:
       - name: Pack packages into tgz
         run: |
           mkdir -p tmp/tgz
+          # Every tgz consumer below references fixed `*-0.0.0.tgz` filenames.
+          # A release commit can leave `packages/{core,cli}` at a published
+          # version (e.g. 0.1.22), which would make `pnpm pack` emit
+          # `*-0.1.22.tgz` instead. Pin both to 0.0.0 so the names stay stable.
+          (cd packages/core && npm pkg set version=0.0.0)
+          (cd packages/cli && npm pkg set version=0.0.0)
           cd packages/core && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
-          cd packages/test && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
           cd packages/cli && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
+          # Bun is uniquely strict about peer-dep resolution:
+          #   1. It checks the *resolved target's* package name and version
+          #      against the peer range (vitest 4.1.9 declares peer
+          #      `vite ^6 || ^7 || ^8`).
+          #   2. A file: override pointing at the vite-plus-core tgz fails
+          #      both the name check (target is `@voidzero-dev/vite-plus-core`,
+          #      not `vite`) and the version check (0.0.0 is outside `^6|^7|^8`).
+          # pnpm/npm/yarn don't enforce either, and using the same core tgz as
+          # the file: target for both `vite` and `@voidzero-dev/vite-plus-core`
+          # is the only configuration they install cleanly. See
+          # https://github.com/oven-sh/bun/issues/8406.
+          #
+          # Generate a sibling vite-7.99.0.tgz: a copy of the core tgz with
+          # `package.json#name` rewritten to "vite" and `version` to 7.99.0.
+          # Only the bun matrix entry below points its vite override at this
+          # alias tgz; pnpm/npm/yarn keep pointing at the real core tgz so
+          # pnpm's workspace resolver doesn't trip on a "vite@<version>"
+          # registry lookup (the renamed tgz makes pnpm register the dep as
+          # vite@7.99.0 and then probe npmjs.org to validate the version).
+          pnpm exec tool repack-vite-tgz tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz tmp/tgz/vite-7.99.0.tgz vite 7.99.0
           # Copy vp binary for test jobs
           cp target/x86_64-unknown-linux-gnu/release/vp tmp/tgz/vp
           ls -la tmp/tgz
@@ -154,6 +179,13 @@ jobs:
           - yarn
           - bun
     env:
+      # Bun's strict peer check requires the `vite` override target's tgz to be
+      # named "vite" with a version satisfying vitest's `peer vite ^6 || ^7 || ^8`.
+      # The bun matrix entry uses the masquerade tgz (vite-7.99.0.tgz). pnpm/npm/yarn
+      # point at the real core tgz — anything else trips a registry lookup for
+      # vite@<version> when sub-package and override targets are both file: tgz aliases.
+      VITE_OVERRIDE_TGZ: ${{ matrix.package-manager == 'bun' && 'vite-7.99.0.tgz' || 'voidzero-dev-vite-plus-core-0.0.0.tgz' }}
+      VP_VERSION: 'file:${{ github.workspace }}/tmp/tgz/vite-plus-0.0.0.tgz'
       # Force full dependency rewriting so the library template's existing
       # vite-plus dep gets overridden with the local tgz
       VP_FORCE_MIGRATE: '1'
@@ -170,22 +202,12 @@ jobs:
           name: vite-plus-packages
           path: tmp/tgz
 
-      - name: Resolve tgz paths
-        run: |
-          CLI_TGZ=$(ls "$GITHUB_WORKSPACE"/tmp/tgz/vite-plus-[0-9]*.tgz | head -n1)
-          CORE_TGZ=$(ls "$GITHUB_WORKSPACE"/tmp/tgz/voidzero-dev-vite-plus-core-[0-9]*.tgz | head -n1)
-          TEST_TGZ=$(ls "$GITHUB_WORKSPACE"/tmp/tgz/voidzero-dev-vite-plus-test-[0-9]*.tgz | head -n1)
-          echo "CLI_TGZ=$CLI_TGZ" >> $GITHUB_ENV
-          echo "VP_VERSION=file:$CLI_TGZ" >> $GITHUB_ENV
-          printf 'VP_OVERRIDE_PACKAGES={"vite":"file:%s","vitest":"file:%s","@voidzero-dev/vite-plus-core":"file:%s","@voidzero-dev/vite-plus-test":"file:%s"}\n' \
-            "$CORE_TGZ" "$TEST_TGZ" "$CORE_TGZ" "$TEST_TGZ" >> $GITHUB_ENV
-
       - name: Install vp CLI
         run: |
           mkdir -p target/release
           cp tmp/tgz/vp target/release/vp
           chmod +x target/release/vp
-          node $GITHUB_WORKSPACE/packages/tools/src/install-global-cli.ts --tgz "$CLI_TGZ"
+          node $GITHUB_WORKSPACE/packages/tools/src/install-global-cli.ts --tgz $GITHUB_WORKSPACE/tmp/tgz/vite-plus-0.0.0.tgz
           echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH
 
       - name: Verify vp installation
@@ -195,6 +217,8 @@ jobs:
 
       - name: Run vp create ${{ matrix.template.name }} with ${{ matrix.package-manager }}
         working-directory: ${{ runner.temp }}
+        env:
+          VP_OVERRIDE_PACKAGES: '{"vite":"file:${{ github.workspace }}/tmp/tgz/${{ env.VITE_OVERRIDE_TGZ }}","@voidzero-dev/vite-plus-core":"file:${{ github.workspace }}/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz","vitest":"4.1.9","@vitest/expect":"4.1.9","@vitest/runner":"4.1.9","@vitest/snapshot":"4.1.9","@vitest/spy":"4.1.9","@vitest/utils":"4.1.9","@vitest/mocker":"4.1.9","@vitest/pretty-format":"4.1.9","@vitest/coverage-v8":"4.1.9","@vitest/coverage-istanbul":"4.1.9"}'
         run: |
           vp create ${{ matrix.template.create-args }} \
             --no-interactive \
@@ -268,10 +292,9 @@ jobs:
         run: |
           node -e "
             const path = require('path');
-            const expected = require('$GITHUB_WORKSPACE/packages/cli/package.json').version;
             const pkg = require(path.resolve('node_modules/vite-plus/package.json'));
-            if (pkg.version !== expected) {
-              console.error('Expected vite-plus@' + expected + ', got ' + pkg.version);
+            if (pkg.version !== '0.0.0') {
+              console.error('Expected vite-plus@0.0.0, got ' + pkg.version);
               process.exit(1);
             }
             console.log('✓ vite-plus@' + pkg.version + ' installed correctly');
diff --git a/.typos.toml b/.typos.toml
index 996242eb1f..c50f7dc0e1 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -4,6 +4,12 @@ PUNICODE = "PUNICODE"
 Jod = "Jod" # Node.js v22 LTS codename
 flate = "flate" # flate2 crate name (gzip/deflate compression)
 
+[default.extend-identifiers]
+# Yarn Plug'n'Play — tokenizer splits `PnP` into the word `Pn`, which typos
+# otherwise wants to autocorrect to `On`. Whitelist the full identifier so
+# the parts aren't tokenized.
+PnP = "PnP"
+
 [files]
 extend-exclude = [
   "**/snap-tests/**/snap.txt",
diff --git a/README.md b/README.md
index e9019fc152..a0c1b904a5 100644
--- a/README.md
+++ b/README.md
@@ -189,12 +189,12 @@ If you are manually migrating a project to Vite+, install these dev dependencies
 npm install -D vite-plus @voidzero-dev/vite-plus-core@latest
 ```
 
-You need to add overrides to your package manager for `vite` and `vitest` so that other packages depending on Vite and Vitest will use the Vite+ versions:
+You need to add overrides to your package manager so that other packages resolve the Vite+ versions: alias `vite` to `@voidzero-dev/vite-plus-core`, and pin `vitest` to the version Vite+ bundles (run `vp --version`) so the whole project shares a single Vitest copy with `vp test`. Without the `vitest` pin, a dependency or workspace package can pull a different Vitest than the bundled runner, splitting Vitest's internals (mocks, `expect`, runner state):
 
 ```json
 "overrides": {
   "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-  "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+  "vitest": "4.1.9"
 }
 ```
 
@@ -203,7 +203,7 @@ If you are using `pnpm`, add this to your `pnpm-workspace.yaml`:
 ```yaml
 overrides:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: 4.1.9
 ```
 
 Or, if you are using Yarn:
@@ -211,7 +211,7 @@ Or, if you are using Yarn:
 ```json
 "resolutions": {
   "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-  "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+  "vitest": "4.1.9"
 }
 ```
 
diff --git a/crates/vite_global_cli/src/commands/version.rs b/crates/vite_global_cli/src/commands/version.rs
index 96d56bf36f..73b80245ac 100644
--- a/crates/vite_global_cli/src/commands/version.rs
+++ b/crates/vite_global_cli/src/commands/version.rs
@@ -47,11 +47,7 @@ const TOOL_SPECS: [ToolSpec; 7] = [
         package_name: "@voidzero-dev/vite-plus-core",
         bundled_version_key: Some("rolldown"),
     },
-    ToolSpec {
-        display_name: "vitest",
-        package_name: "@voidzero-dev/vite-plus-test",
-        bundled_version_key: Some("vitest"),
-    },
+    ToolSpec { display_name: "vitest", package_name: "vitest", bundled_version_key: None },
     ToolSpec { display_name: "oxfmt", package_name: "oxfmt", bundled_version_key: None },
     ToolSpec { display_name: "oxlint", package_name: "oxlint", bundled_version_key: None },
     ToolSpec {
diff --git a/crates/vite_migration/src/file_walker.rs b/crates/vite_migration/src/file_walker.rs
index 9ab86a638c..83e4e48c7e 100644
--- a/crates/vite_migration/src/file_walker.rs
+++ b/crates/vite_migration/src/file_walker.rs
@@ -3,9 +3,12 @@ use std::path::{Path, PathBuf};
 use ignore::WalkBuilder;
 use vite_error::Error;
 
-// TODO: only support esm files for now
-/// File extensions to process for import rewriting
-const TS_JS_EXTENSIONS: &[&str] = &["ts", "tsx", "mts", "js", "jsx", "mjs"];
+/// File extensions to process for import rewriting.
+///
+/// Includes CommonJS (`.cjs`/`.cts`) variants in addition to ESM, because
+/// the legacy reverse-migration rules also cover `require('vite-plus/test/browser-*')`
+/// calls that are most commonly found in `.cjs` / `vitest.config.cjs` files.
+const TS_JS_EXTENSIONS: &[&str] = &["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
 
 /// Result of walking TypeScript/JavaScript files
 #[derive(Debug)]
@@ -164,7 +167,7 @@ mod tests {
 
         let result = find_ts_files(temp.path()).unwrap();
 
-        assert_eq!(result.files.len(), 6);
+        assert_eq!(result.files.len(), 8);
     }
 
     #[test]
diff --git a/crates/vite_migration/src/import_rewriter.rs b/crates/vite_migration/src/import_rewriter.rs
index 175ef58491..d0de5a0840 100644
--- a/crates/vite_migration/src/import_rewriter.rs
+++ b/crates/vite_migration/src/import_rewriter.rs
@@ -17,8 +17,20 @@ use crate::{ast_grep, file_walker};
 /// This rewrites:
 /// - `import { ... } from 'vite'` → `import { ... } from 'vite-plus'`
 /// - `import { ... } from 'vite/{name}'` → `import { ... } from 'vite-plus/{name}'`
+/// - `require('vite')` → `require('vite-plus')`
+/// - `require('vite/{name}')` → `require('vite-plus/{name}')`
 /// - `declare module 'vite' { ... }` → `declare module 'vite-plus' { ... }`
 /// - `declare module 'vite/{name}' { ... }` → `declare module 'vite-plus/{name}' { ... }`
+///
+/// The `require(...)` sibling rules constrain themselves to a literal `require`
+/// callee so unrelated calls like `my_require('vite')` or `require.cache['vite']`
+/// are NOT touched. The `*-dynamic-import` sibling rules match dynamic
+/// `import('vite')` calls (and TS type-position `typeof import('vite')`) by
+/// pinning the call_expression's `function` field to the `import` token kind,
+/// which excludes ordinary identifier calls. Together with the upstream
+/// `import_statement` rule (covering ES `import { ... } from 'vite'`), this
+/// gives full coverage of static imports/exports, dynamic imports, and
+/// CommonJS requires.
 const REWRITE_VITE_RULES: &str = r#"---
 id: rewrite-vite-import
 language: TypeScript
@@ -36,6 +48,64 @@ transform:
       by: "vite-plus"
 fix: $NEW_IMPORT
 ---
+id: rewrite-vite-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vite['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vite
+      by: "vite-plus"
+fix: $NEW_IMPORT
+---
+id: rewrite-vite-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vite['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vite
+      by: "vite-plus"
+fix: $NEW_IMPORT
+---
+id: rewrite-vite-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vite['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vite
+      by: "vite-plus"
+fix: $NEW_IMPORT
+---
 id: rewrite-vite-subpath-import
 language: TypeScript
 rule:
@@ -52,6 +122,64 @@ transform:
       by: "vite-plus/"
 fix: $NEW_IMPORT
 ---
+id: rewrite-vite-subpath-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vite/.+['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vite/
+      by: "vite-plus/"
+fix: $NEW_IMPORT
+---
+id: rewrite-vite-subpath-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vite/.+['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vite/
+      by: "vite-plus/"
+fix: $NEW_IMPORT
+---
+id: rewrite-vite-subpath-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vite/.+['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vite/
+      by: "vite-plus/"
+fix: $NEW_IMPORT
+---
 id: rewrite-declare-module-vite
 language: TypeScript
 rule:
@@ -85,31 +213,53 @@ transform:
 fix: $NEW_IMPORT
 "#;
 
-/// ast-grep rules for rewriting vitest imports and declare module statements
+/// ast-grep rules for rewriting vitest imports.
 ///
-/// This rewrites:
+/// This rewrites (the canonical mapping shared with the `oxlint-plugin.ts`
+/// `rewriteVitePlusImportSpecifier` autofix — both implementations MUST stay
+/// in sync and only produce targets that exist in the `vite-plus` package
+/// `exports` map, otherwise Node fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`):
 /// - `import { ... } from 'vitest'` → `import { ... } from 'vite-plus/test'`
 /// - `import { ... } from 'vitest/config'` → `import { ... } from 'vite-plus'`
 /// - `import { ... } from 'vitest/{name}'` → `import { ... } from 'vite-plus/test/{name}'`
 /// - `import { ... } from '@vitest/browser'` → `import { ... } from 'vite-plus/test/browser'`
-/// - `import { ... } from '@vitest/browser/{name}'` → `import { ... } from 'vite-plus/test/browser/{name}'`
+/// - `import { ... } from '@vitest/browser/context'` → `import { ... } from 'vite-plus/test/browser/context'`
+/// - `import { ... } from '@vitest/browser/client'` → `import { ... } from 'vite-plus/test/client'`
+/// - `import { ... } from '@vitest/browser/locators'` → `import { ... } from 'vite-plus/test/locators'`
+/// - `import { ... } from '@vitest/browser/matchers'` → `import { ... } from 'vite-plus/test/matchers'`
+/// - `import { ... } from '@vitest/browser/utils'` → `import { ... } from 'vite-plus/test/utils'`
+///
+///   Note: `vite-plus` only exports `./test/browser/context` under the nested
+///   `browser/` path; `client`, `locators`, `matchers` and `utils` are exposed
+///   at the bare `./test/{name}` surface, so the `/browser/` segment is stripped.
 /// - `import { ... } from '@vitest/browser-playwright'` → `import { ... } from 'vite-plus/test/browser-playwright'`
-/// - `import { ... } from '@vitest/browser-playwright/{name}'` → `import { ... } from 'vite-plus/test/browser-playwright/{name}'`
+/// - `import { ... } from '@vitest/browser-playwright/context'` → `import { ... } from 'vite-plus/test/browser/context'`
+/// - `import { ... } from '@vitest/browser-playwright/provider'` → `import { ... } from 'vite-plus/test/browser/providers/playwright'`
 /// - `import { ... } from '@vitest/browser-preview'` → `import { ... } from 'vite-plus/test/browser-preview'`
-/// - `import { ... } from '@vitest/browser-preview/{name}'` → `import { ... } from 'vite-plus/test/browser-preview/{name}'`
+/// - `import { ... } from '@vitest/browser-preview/context'` → `import { ... } from 'vite-plus/test/browser/context'`
+/// - `import { ... } from '@vitest/browser-preview/provider'` → `import { ... } from 'vite-plus/test/browser/providers/preview'`
 /// - `import { ... } from '@vitest/browser-webdriverio'` → `import { ... } from 'vite-plus/test/browser-webdriverio'`
-/// - `import { ... } from '@vitest/browser-webdriverio/{name}'` → `import { ... } from 'vite-plus/test/browser-webdriverio/{name}'`
-/// - `declare module 'vitest' { ... }` → `declare module 'vite-plus/test' { ... }`
-/// - `declare module 'vitest/config' { ... }` → `declare module 'vite-plus' { ... }`
-/// - `declare module 'vitest/{name}' { ... }` → `declare module 'vite-plus/test/{name}' { ... }`
-/// - `declare module '@vitest/browser' { ... }` → `declare module 'vite-plus/test/browser' { ... }`
-/// - `declare module '@vitest/browser/{name}' { ... }` → `declare module 'vite-plus/test/browser/{name}' { ... }`
-/// - `declare module '@vitest/browser-playwright' { ... }` → `declare module 'vite-plus/test/browser-playwright' { ... }`
-/// - `declare module '@vitest/browser-playwright/{name}' { ... }` → `declare module 'vite-plus/test/browser-playwright/{name}' { ... }`
-/// - `declare module '@vitest/browser-preview' { ... }` → `declare module 'vite-plus/test/browser-preview' { ... }`
-/// - `declare module '@vitest/browser-preview/{name}' { ... }` → `declare module 'vite-plus/test/browser-preview/{name}' { ... }`
-/// - `declare module '@vitest/browser-webdriverio' { ... }` → `declare module 'vite-plus/test/browser-webdriverio' { ... }`
-/// - `declare module '@vitest/browser-webdriverio/{name}' { ... }` → `declare module 'vite-plus/test/browser-webdriverio/{name}' { ... }`
+/// - `import { ... } from '@vitest/browser-webdriverio/context'` → `import { ... } from 'vite-plus/test/browser/context'`
+/// - `import { ... } from '@vitest/browser-webdriverio/provider'` → `import { ... } from 'vite-plus/test/browser/providers/webdriverio'`
+///
+/// `declare module 'vitest' { ... }` (and the subpath/`@vitest/*` variants) are
+/// intentionally NOT rewritten — the `vite-plus/test*` subpaths are thin shims
+/// that `export * from 'vitest'` (and the browser provider packages), so the
+/// underlying type identity is upstream. Augmenting `'vite-plus/test'` would
+/// only augment the shim module and would not merge into the upstream types
+/// the user actually sees through their `import { expect } from 'vite-plus/test'`
+/// statements. Leaving the `declare module 'vitest' { ... }` alone keeps
+/// augmentations targeting the real upstream module identity.
+///
+/// `vitest/package.json` is also intentionally NOT rewritten. It is a
+/// metadata-access pattern (typically used to read the vitest version) and
+/// `vite-plus`'s generated exports map deliberately does not expose
+/// `./test/package.json` (see `syncTestPackageExports()` in
+/// `packages/cli/build.ts`, which skips the upstream `./package.json`
+/// export). Rewriting it would yield `vite-plus/test/package.json`, which
+/// fails at runtime with `ERR_PACKAGE_PATH_NOT_EXPORTED`. The original
+/// specifier still resolves through the transitively-installed `vitest`
+/// dependency.
 const REWRITE_VITEST_RULES: &str = r#"---
 id: rewrite-vitest-config-import
 language: TypeScript
@@ -127,6 +277,64 @@ transform:
       by: "vite-plus"
 fix: $NEW_IMPORT
 ---
+id: rewrite-vitest-config-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest/config['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest/config
+      by: "vite-plus"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-config-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest/config['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest/config
+      by: "vite-plus"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-config-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest/config['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest/config
+      by: "vite-plus"
+fix: $NEW_IMPORT
+---
 id: rewrite-vitest-import
 language: TypeScript
 rule:
@@ -143,173 +351,1221 @@ transform:
       by: "vite-plus/test"
 fix: $NEW_IMPORT
 ---
-id: rewrite-vitest-scoped-import
+id: rewrite-vitest-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest
+      by: "vite-plus/test"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest
+      by: "vite-plus/test"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest
+      by: "vite-plus/test"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-import
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['"]@vitest/(browser-playwright|browser-preview|browser-webdriverio|browser)(/.*)?['"]$
+  regex: ^['"]@vitest/browser['"]$
   inside:
     kind: import_statement
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: "@vitest/"
-      by: "vite-plus/test/"
+      replace: "@vitest/browser"
+      by: "vite-plus/test/browser"
 fix: $NEW_IMPORT
 ---
-id: rewrite-vitest-subpath-import
+id: rewrite-vitest-browser-export
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['"]vitest/.+['"]$
+  regex: ^['"]@vitest/browser['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser"
+      by: "vite-plus/test/browser"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser"
+      by: "vite-plus/test/browser"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser"
+      by: "vite-plus/test/browser"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-context-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser/context['"]$
   inside:
     kind: import_statement
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: vitest/
-      by: "vite-plus/test/"
+      replace: "@vitest/browser/context"
+      by: "vite-plus/test/browser/context"
 fix: $NEW_IMPORT
 ---
-id: rewrite-declare-module-vitest-config
+id: rewrite-vitest-browser-context-export
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['\"]vitest/config['\"]$
+  regex: ^['"]@vitest/browser/context['"]$
   inside:
-    kind: module
+    kind: export_statement
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: vitest/config
-      by: "vite-plus"
+      replace: "@vitest/browser/context"
+      by: "vite-plus/test/browser/context"
 fix: $NEW_IMPORT
 ---
-id: rewrite-declare-module-vitest
+id: rewrite-vitest-browser-context-require
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['\"]vitest['\"]$
+  regex: ^['"]@vitest/browser/context['"]$
   inside:
-    kind: module
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: vitest
-      by: "vite-plus/test"
+      replace: "@vitest/browser/context"
+      by: "vite-plus/test/browser/context"
 fix: $NEW_IMPORT
 ---
-id: rewrite-declare-module-vitest-scoped
+id: rewrite-vitest-browser-context-dynamic-import
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['\"]@vitest/(browser-playwright|browser-preview|browser-webdriverio|browser)(/.*)?['\"]$
+  regex: ^['"]@vitest/browser/context['"]$
   inside:
-    kind: module
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: "@vitest/"
-      by: "vite-plus/test/"
+      replace: "@vitest/browser/context"
+      by: "vite-plus/test/browser/context"
 fix: $NEW_IMPORT
 ---
-id: rewrite-declare-module-vitest-subpath
+id: rewrite-vitest-browser-flat-subpath-import
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['\"]vitest/.+['\"]$
+  regex: ^['"]@vitest/browser/(client|locators|matchers|utils)['"]$
   inside:
-    kind: module
+    kind: import_statement
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: vitest/
+      replace: "@vitest/browser/"
       by: "vite-plus/test/"
 fix: $NEW_IMPORT
-"#;
-
-/// ast-grep rules for rewriting tsdown imports and declare module statements
-///
-/// This rewrites:
-/// - `import { ... } from 'tsdown'` → `import { ... } from 'vite-plus/pack'`
-/// - `declare module 'tsdown' { ... }` → `declare module 'vite-plus/pack' { ... }`
-const REWRITE_TSDOWN_RULES: &str = r#"---
-id: rewrite-tsdown-import
+---
+id: rewrite-vitest-browser-flat-subpath-export
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['"]tsdown['"]$
+  regex: ^['"]@vitest/browser/(client|locators|matchers|utils)['"]$
   inside:
-    kind: import_statement
+    kind: export_statement
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: tsdown
-      by: "vite-plus/pack"
+      replace: "@vitest/browser/"
+      by: "vite-plus/test/"
 fix: $NEW_IMPORT
 ---
-id: rewrite-declare-module-tsdown
+id: rewrite-vitest-browser-flat-subpath-require
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['"]tsdown['"]$
+  regex: ^['"]@vitest/browser/(client|locators|matchers|utils)['"]$
   inside:
-    kind: module
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: tsdown
-      by: "vite-plus/pack"
+      replace: "@vitest/browser/"
+      by: "vite-plus/test/"
 fix: $NEW_IMPORT
 ---
-id: rewrite-tsdown-client-import
+id: rewrite-vitest-browser-flat-subpath-dynamic-import
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['"]tsdown/client['"]$
+  regex: ^['"]@vitest/browser/(client|locators|matchers|utils)['"]$
   inside:
-    kind: import_statement
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: tsdown/client
-      by: "vite-plus/pack/client"
+      replace: "@vitest/browser/"
+      by: "vite-plus/test/"
 fix: $NEW_IMPORT
 ---
-id: rewrite-declare-module-tsdown-client
+id: rewrite-vitest-browser-playwright-import
 language: TypeScript
 rule:
   pattern: $STR
   kind: string
-  regex: ^['"]tsdown/client['"]$
+  regex: ^['"]@vitest/browser-playwright['"]$
   inside:
-    kind: module
+    kind: import_statement
 transform:
   NEW_IMPORT:
     replace:
       source: $STR
-      replace: tsdown/client
-      by: "vite-plus/pack/client"
+      replace: "@vitest/browser-playwright"
+      by: "vite-plus/test/browser-playwright"
 fix: $NEW_IMPORT
-"#;
+---
+id: rewrite-vitest-browser-playwright-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright"
+      by: "vite-plus/test/browser-playwright"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright"
+      by: "vite-plus/test/browser-playwright"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright"
+      by: "vite-plus/test/browser-playwright"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-context-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright/context['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-context-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright/context['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-context-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright/context['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-context-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright/context['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-provider-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright/provider['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright/provider"
+      by: "vite-plus/test/browser/providers/playwright"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-provider-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright/provider['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright/provider"
+      by: "vite-plus/test/browser/providers/playwright"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-provider-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright/provider['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright/provider"
+      by: "vite-plus/test/browser/providers/playwright"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-playwright-provider-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-playwright/provider['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-playwright/provider"
+      by: "vite-plus/test/browser/providers/playwright"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview"
+      by: "vite-plus/test/browser-preview"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview"
+      by: "vite-plus/test/browser-preview"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview"
+      by: "vite-plus/test/browser-preview"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview"
+      by: "vite-plus/test/browser-preview"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-context-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview/context['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-context-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview/context['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-context-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview/context['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-context-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview/context['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-provider-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview/provider['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview/provider"
+      by: "vite-plus/test/browser/providers/preview"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-provider-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview/provider['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview/provider"
+      by: "vite-plus/test/browser/providers/preview"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-provider-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview/provider['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview/provider"
+      by: "vite-plus/test/browser/providers/preview"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-preview-provider-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-preview/provider['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-preview/provider"
+      by: "vite-plus/test/browser/providers/preview"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio"
+      by: "vite-plus/test/browser-webdriverio"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio"
+      by: "vite-plus/test/browser-webdriverio"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio"
+      by: "vite-plus/test/browser-webdriverio"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio"
+      by: "vite-plus/test/browser-webdriverio"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-context-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio/context['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-context-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio/context['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-context-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio/context['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-context-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio/context['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio/context"
+      by: "vite-plus/test/browser/context"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-provider-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio/provider['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio/provider"
+      by: "vite-plus/test/browser/providers/webdriverio"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-provider-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio/provider['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio/provider"
+      by: "vite-plus/test/browser/providers/webdriverio"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-provider-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio/provider['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio/provider"
+      by: "vite-plus/test/browser/providers/webdriverio"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-browser-webdriverio-provider-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]@vitest/browser-webdriverio/provider['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: "@vitest/browser-webdriverio/provider"
+      by: "vite-plus/test/browser/providers/webdriverio"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-subpath-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest/.+['"]$
+  not:
+    regex: ^['"]vitest/(package\.json|config)['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest/
+      by: "vite-plus/test/"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-subpath-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest/.+['"]$
+  not:
+    regex: ^['"]vitest/(package\.json|config)['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest/
+      by: "vite-plus/test/"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-subpath-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest/.+['"]$
+  not:
+    regex: ^['"]vitest/(package\.json|config)['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest/
+      by: "vite-plus/test/"
+fix: $NEW_IMPORT
+---
+id: rewrite-vitest-subpath-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]vitest/.+['"]$
+  not:
+    regex: ^['"]vitest/(package\.json|config)['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: vitest/
+      by: "vite-plus/test/"
+fix: $NEW_IMPORT
+"#;
+
+/// ast-grep rules for rewriting tsdown imports and declare module statements
+///
+/// This rewrites:
+/// - `import { ... } from 'tsdown'` → `import { ... } from 'vite-plus/pack'`
+/// - `declare module 'tsdown' { ... }` → `declare module 'vite-plus/pack' { ... }`
+const REWRITE_TSDOWN_RULES: &str = r#"---
+id: rewrite-tsdown-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown
+      by: "vite-plus/pack"
+fix: $NEW_IMPORT
+---
+id: rewrite-tsdown-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown
+      by: "vite-plus/pack"
+fix: $NEW_IMPORT
+---
+id: rewrite-tsdown-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown
+      by: "vite-plus/pack"
+fix: $NEW_IMPORT
+---
+id: rewrite-tsdown-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown
+      by: "vite-plus/pack"
+fix: $NEW_IMPORT
+---
+id: rewrite-declare-module-tsdown
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown['"]$
+  inside:
+    kind: module
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown
+      by: "vite-plus/pack"
+fix: $NEW_IMPORT
+---
+id: rewrite-tsdown-client-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown/client['"]$
+  inside:
+    kind: import_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown/client
+      by: "vite-plus/pack/client"
+fix: $NEW_IMPORT
+---
+id: rewrite-tsdown-client-export
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown/client['"]$
+  inside:
+    kind: export_statement
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown/client
+      by: "vite-plus/pack/client"
+fix: $NEW_IMPORT
+---
+id: rewrite-tsdown-client-require
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown/client['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        regex: ^require$
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown/client
+      by: "vite-plus/pack/client"
+fix: $NEW_IMPORT
+---
+id: rewrite-tsdown-client-dynamic-import
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown/client['"]$
+  inside:
+    kind: arguments
+    inside:
+      kind: call_expression
+      has:
+        field: function
+        kind: import
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown/client
+      by: "vite-plus/pack/client"
+fix: $NEW_IMPORT
+---
+id: rewrite-declare-module-tsdown-client
+language: TypeScript
+rule:
+  pattern: $STR
+  kind: string
+  regex: ^['"]tsdown/client['"]$
+  inside:
+    kind: module
+transform:
+  NEW_IMPORT:
+    replace:
+      source: $STR
+      replace: tsdown/client
+      by: "vite-plus/pack/client"
+fix: $NEW_IMPORT
+"#;
 
 static PARSED_VITE_RULES: LazyLock<Vec<RuleConfig<SupportLang>>> = LazyLock::new(|| {
     ast_grep::load_rules(REWRITE_VITE_RULES).expect("failed to parse vite rewrite rules")
@@ -341,16 +1597,56 @@ static RE_REF_VITEST_SUBPATH: LazyLock<Regex> = LazyLock::new(|| {
     Regex::new(r#"^(\s*///\s*<reference\s+types\s*=\s*["'])vitest/(.+?)(["']\s*/>)"#).unwrap()
 });
 
-/// `@vitest/{pkg}[/{subpath}]` → `vite-plus/test/{pkg}[/{subpath}]`
-/// Only matches packages and subpaths that vite-plus actually exports:
+/// `@vitest/browser[/{subpath}]` references that map onto the *nested*
+/// `vite-plus/test/browser[/{subpath}]` surface (a plain `@vitest/` → `vite-plus/test/`
+/// swap is correct here):
 ///   - `@vitest/browser` → `vite-plus/test/browser`
 ///   - `@vitest/browser/context` → `vite-plus/test/browser/context`
 ///   - `@vitest/browser/providers/{name}` → `vite-plus/test/browser/providers/{name}`
-///   - `@vitest/browser-playwright[/{subpath}]` → `vite-plus/test/browser-playwright[/{subpath}]`
-///   - `@vitest/browser-preview[/{subpath}]` → `vite-plus/test/browser-preview[/{subpath}]`
-///   - `@vitest/browser-webdriverio[/{subpath}]` → `vite-plus/test/browser-webdriverio[/{subpath}]`
-static RE_REF_VITEST_SCOPED: LazyLock<Regex> = LazyLock::new(|| {
-    Regex::new(r#"^(\s*///\s*<reference\s+types\s*=\s*["'])@vitest/((?:browser-playwright|browser-preview|browser-webdriverio)(?:/.+?)?|browser(?:/(?:context|providers/.+?))?)(["']\s*/>)"#).unwrap()
+static RE_REF_VITEST_SCOPED_BROWSER: LazyLock<Regex> = LazyLock::new(|| {
+    Regex::new(
+        r#"^(\s*///\s*<reference\s+types\s*=\s*["'])@vitest/(browser(?:/(?:context|providers/.+?))?)(["']\s*/>)"#,
+    )
+    .unwrap()
+});
+
+/// `@vitest/browser/{client,locators,matchers,utils}` references. `vite-plus` only
+/// exposes these four at the *bare* `./test/{name}` surface (NOT under `./test/browser/`),
+/// so the `/browser/` segment is stripped: `@vitest/browser/{name}` → `vite-plus/test/{name}`.
+static RE_REF_VITEST_SCOPED_BROWSER_FLAT: LazyLock<Regex> = LazyLock::new(|| {
+    Regex::new(
+        r#"^(\s*///\s*<reference\s+types\s*=\s*["'])@vitest/browser/(client|locators|matchers|utils)(["']\s*/>)"#,
+    )
+    .unwrap()
+});
+
+/// `@vitest/browser-{provider}` (exact, no subpath) →
+/// `vite-plus/test/browser-{provider}` — a plain `@vitest/` → `vite-plus/test/` swap.
+static RE_REF_VITEST_SCOPED_PROVIDER: LazyLock<Regex> = LazyLock::new(|| {
+    Regex::new(
+        r#"^(\s*///\s*<reference\s+types\s*=\s*["'])@vitest/(browser-playwright|browser-preview|browser-webdriverio)(["']\s*/>)"#,
+    )
+    .unwrap()
+});
+
+/// `@vitest/browser-{provider}/context` references. `vite-plus` projects every
+/// provider's `context` onto the shared `./test/browser/context` export, so the
+/// provider segment is dropped: → `vite-plus/test/browser/context`.
+static RE_REF_VITEST_SCOPED_PROVIDER_CONTEXT: LazyLock<Regex> = LazyLock::new(|| {
+    Regex::new(
+        r#"^(\s*///\s*<reference\s+types\s*=\s*["'])@vitest/browser-(playwright|preview|webdriverio)/context(["']\s*/>)"#,
+    )
+    .unwrap()
+});
+
+/// `@vitest/browser-{provider}/provider` references. `vite-plus` exposes provider
+/// entry points at `./test/browser/providers/{provider}`, so the subpath is
+/// rewritten accordingly: → `vite-plus/test/browser/providers/{provider}`.
+static RE_REF_VITEST_SCOPED_PROVIDER_ENTRY: LazyLock<Regex> = LazyLock::new(|| {
+    Regex::new(
+        r#"^(\s*///\s*<reference\s+types\s*=\s*["'])@vitest/browser-(playwright|preview|webdriverio)/provider(["']\s*/>)"#,
+    )
+    .unwrap()
 });
 
 /// bare `vite` → `vite-plus`
@@ -510,7 +1806,47 @@ fn rewrite_reference_types(content: &mut String, skip_packages: &SkipPackages) -
                 changed = true;
                 continue;
             }
-            if apply_regex_replace(line, &RE_REF_VITEST_SCOPED, "${1}vite-plus/test/${2}${3}") {
+            // `@vitest/browser-{provider}/{context,provider}` must be matched before the
+            // bare provider regex so the more specific subpath rewrite wins.
+            if apply_regex_replace(
+                line,
+                &RE_REF_VITEST_SCOPED_PROVIDER_CONTEXT,
+                "${1}vite-plus/test/browser/context${3}",
+            ) {
+                changed = true;
+                continue;
+            }
+            if apply_regex_replace(
+                line,
+                &RE_REF_VITEST_SCOPED_PROVIDER_ENTRY,
+                "${1}vite-plus/test/browser/providers/${2}${3}",
+            ) {
+                changed = true;
+                continue;
+            }
+            if apply_regex_replace(
+                line,
+                &RE_REF_VITEST_SCOPED_PROVIDER,
+                "${1}vite-plus/test/${2}${3}",
+            ) {
+                changed = true;
+                continue;
+            }
+            // `@vitest/browser/{client,locators,matchers,utils}` strips the `/browser/`
+            // segment; the generic `@vitest/browser[/...]` rule keeps it.
+            if apply_regex_replace(
+                line,
+                &RE_REF_VITEST_SCOPED_BROWSER_FLAT,
+                "${1}vite-plus/test/${2}${3}",
+            ) {
+                changed = true;
+                continue;
+            }
+            if apply_regex_replace(
+                line,
+                &RE_REF_VITEST_SCOPED_BROWSER,
+                "${1}vite-plus/test/${2}${3}",
+            ) {
                 changed = true;
                 continue;
             }
@@ -1012,425 +2348,875 @@ describe('test', () => {});"#;
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { describe, it, expect } from "vite-plus/test";
+            r#"import { describe, it, expect } from "vite-plus/test";
+
+describe('test', () => {});"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser() {
+        let vite_config = r#"import { page } from '@vitest/browser';
+
+export default page;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { page } from 'vite-plus/test/browser';
+
+export default page;"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_double_quotes() {
+        let vite_config = r#"import { page } from "@vitest/browser";
+
+export default page;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { page } from "vite-plus/test/browser";
+
+export default page;"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_playwright() {
+        let vite_config = r#"import { playwright } from '@vitest/browser-playwright';
+
+export default playwright;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { playwright } from 'vite-plus/test/browser-playwright';
+
+export default playwright;"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_playwright_double_quotes() {
+        let vite_config = r#"import { playwright } from "@vitest/browser-playwright";
+
+export default playwright;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { playwright } from "vite-plus/test/browser-playwright";
+
+export default playwright;"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_subpath() {
+        let vite_config = r#"import { context } from '@vitest/browser/context';
+
+export default context;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { context } from 'vite-plus/test/browser/context';
+
+export default context;"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_playwright_subpath() {
+        // `@vitest/browser-{provider}/context` maps onto the shared
+        // `vite-plus/test/browser/context` export (the provider segment is dropped).
+        let vite_config = r#"import { something } from "@vitest/browser-playwright/context";
+
+export default something;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { something } from "vite-plus/test/browser/context";
+
+export default something;"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_playwright_provider() {
+        // `@vitest/browser-{provider}/provider` maps to the provider entry point
+        // under `vite-plus/test/browser/providers/{provider}`.
+        let vite_config = r#"import { playwright } from '@vitest/browser-playwright/provider';
+
+export default playwright;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { playwright } from 'vite-plus/test/browser/providers/playwright';
+
+export default playwright;"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_preview() {
+        let vite_config = r#"import { preview } from '@vitest/browser-preview';
+
+export default preview;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { preview } from 'vite-plus/test/browser-preview';
+
+export default preview;"#
+        );
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_preview_subpath() {
+        let vite_config = r#"import { something } from "@vitest/browser-preview/context";
+
+export default something;"#;
+
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { something } from "vite-plus/test/browser/context";
 
-describe('test', () => {});"#
+export default something;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser() {
-        let vite_config = r#"import { page } from '@vitest/browser';
+    fn test_rewrite_import_content_vitest_browser_preview_provider() {
+        let vite_config = r#"import { preview } from '@vitest/browser-preview/provider';
 
-export default page;"#;
+export default preview;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { page } from 'vite-plus/test/browser';
+            r#"import { preview } from 'vite-plus/test/browser/providers/preview';
 
-export default page;"#
+export default preview;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser_double_quotes() {
-        let vite_config = r#"import { page } from "@vitest/browser";
+    fn test_rewrite_import_content_vitest_browser_webdriverio() {
+        let vite_config = r#"import { webdriverio } from '@vitest/browser-webdriverio';
 
-export default page;"#;
+export default webdriverio;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { page } from "vite-plus/test/browser";
+            r#"import { webdriverio } from 'vite-plus/test/browser-webdriverio';
 
-export default page;"#
+export default webdriverio;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser_playwright() {
-        let vite_config = r#"import { playwright } from '@vitest/browser-playwright';
+    fn test_rewrite_import_content_vitest_browser_webdriverio_subpath() {
+        let vite_config = r#"import { something } from "@vitest/browser-webdriverio/context";
 
-export default playwright;"#;
+export default something;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { playwright } from 'vite-plus/test/browser-playwright';
+            r#"import { something } from "vite-plus/test/browser/context";
 
-export default playwright;"#
+export default something;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser_playwright_double_quotes() {
-        let vite_config = r#"import { playwright } from "@vitest/browser-playwright";
+    fn test_rewrite_import_content_vitest_browser_webdriverio_provider() {
+        let vite_config = r#"import { webdriverio } from '@vitest/browser-webdriverio/provider';
 
-export default playwright;"#;
+export default webdriverio;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { playwright } from "vite-plus/test/browser-playwright";
+            r#"import { webdriverio } from 'vite-plus/test/browser/providers/webdriverio';
 
-export default playwright;"#
+export default webdriverio;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser_subpath() {
-        let vite_config = r#"import { context } from '@vitest/browser/context';
+    fn test_rewrite_import_content_vitest_browser_flat_subpaths() {
+        // `@vitest/browser/{client,locators,matchers,utils}` are exposed by
+        // vite-plus at the *bare* `./test/{name}` surface, so the `/browser/`
+        // segment must be stripped (the nested `./test/browser/{name}` keys
+        // do NOT exist in the exports map → ERR_PACKAGE_PATH_NOT_EXPORTED).
+        for (sub, expected) in [
+            ("client", "vite-plus/test/client"),
+            ("locators", "vite-plus/test/locators"),
+            ("matchers", "vite-plus/test/matchers"),
+            ("utils", "vite-plus/test/utils"),
+        ] {
+            let single = format!("import x from '@vitest/browser/{sub}';");
+            let result = rewrite_import_content(&single, &SkipPackages::default()).unwrap();
+            assert!(result.updated, "single-quoted @vitest/browser/{sub} should be rewritten");
+            assert_eq!(result.content, format!("import x from '{expected}';"));
+
+            let double = format!("import x from \"@vitest/browser/{sub}\";");
+            let result = rewrite_import_content(&double, &SkipPackages::default()).unwrap();
+            assert!(result.updated, "double-quoted @vitest/browser/{sub} should be rewritten");
+            assert_eq!(result.content, format!("import x from \"{expected}\";"));
+        }
+    }
 
-export default context;"#;
+    #[test]
+    fn test_rewrite_import_content_vitest_browser_context_kept_nested() {
+        // `@vitest/browser/context` keeps the nested path — `./test/browser/context`
+        // IS exported.
+        let vite_config = r#"import { context } from '@vitest/browser/context';"#;
+        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"import { context } from 'vite-plus/test/browser/context';"#);
+    }
+
+    #[test]
+    fn test_rewrite_import_content_vite_subpath() {
+        let vite_config = r#"import { ModuleRunner } from 'vite/module-runner';
+
+export default ModuleRunner;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { context } from 'vite-plus/test/browser/context';
+            r#"import { ModuleRunner } from 'vite-plus/module-runner';
 
-export default context;"#
+export default ModuleRunner;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser_playwright_subpath() {
-        let vite_config = r#"import { something } from "@vitest/browser-playwright/context";
+    fn test_rewrite_import_content_vite_subpath_double_quotes() {
+        let vite_config = r#"import { ModuleRunner } from "vite/module-runner";
 
-export default something;"#;
+export default ModuleRunner;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { something } from "vite-plus/test/browser-playwright/context";
+            r#"import { ModuleRunner } from "vite-plus/module-runner";
 
-export default something;"#
+export default ModuleRunner;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser_preview() {
-        let vite_config = r#"import { preview } from '@vitest/browser-preview';
+    fn test_rewrite_import_content_vitest_subpath() {
+        // Test vitest/node subpath
+        let vite_config = r#"import { startVitest } from 'vitest/node';
 
-export default preview;"#;
+export default startVitest;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { preview } from 'vite-plus/test/browser-preview';
+            r#"import { startVitest } from 'vite-plus/test/node';
 
-export default preview;"#
+export default startVitest;"#
         );
-    }
 
-    #[test]
-    fn test_rewrite_import_content_vitest_browser_preview_subpath() {
-        let vite_config = r#"import { something } from "@vitest/browser-preview/context";
+        // Test vitest/plugins/runner subpath
+        let vite_config = r#"import { somePlugin } from 'vitest/plugins/runner';
 
-export default something;"#;
+export default somePlugin;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { something } from "vite-plus/test/browser-preview/context";
+            r#"import { somePlugin } from 'vite-plus/test/plugins/runner';
 
-export default something;"#
+export default somePlugin;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser_webdriverio() {
-        let vite_config = r#"import { webdriverio } from '@vitest/browser-webdriverio';
+    fn test_rewrite_import_content_vitest_subpath_double_quotes() {
+        let vite_config = r#"import { startVitest } from "vitest/node";
 
-export default webdriverio;"#;
+export default startVitest;"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { webdriverio } from 'vite-plus/test/browser-webdriverio';
+            r#"import { startVitest } from "vite-plus/test/node";
 
-export default webdriverio;"#
+export default startVitest;"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_browser_webdriverio_subpath() {
-        let vite_config = r#"import { something } from "@vitest/browser-webdriverio/context";
+    fn test_rewrite_import_content_mixed_imports() {
+        // Test multiple different imports in the same file
+        let vite_config = r#"import { defineConfig } from 'vite';
+import { ModuleRunner } from 'vite/module-runner';
+import { describe, it, expect } from 'vitest';
+import { startVitest } from 'vitest/node';
+import { page } from '@vitest/browser';
+import { playwright } from '@vitest/browser-playwright';
+import react from '@vitejs/plugin-react';
 
-export default something;"#;
+export default defineConfig({
+  plugins: [react()],
+});"#;
 
         let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { something } from "vite-plus/test/browser-webdriverio/context";
+            r#"import { defineConfig } from 'vite-plus';
+import { ModuleRunner } from 'vite-plus/module-runner';
+import { describe, it, expect } from 'vite-plus/test';
+import { startVitest } from 'vite-plus/test/node';
+import { page } from 'vite-plus/test/browser';
+import { playwright } from 'vite-plus/test/browser-playwright';
+import react from '@vitejs/plugin-react';
 
-export default something;"#
+export default defineConfig({
+  plugins: [react()],
+});"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vite_subpath() {
-        let vite_config = r#"import { ModuleRunner } from 'vite/module-runner';
-
-export default ModuleRunner;"#;
+    fn test_rewrite_imports_in_directory() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // Create src directory
+        fs::create_dir(temp.path().join("src")).unwrap();
+
+        // Create test files with vite/vitest imports
+        fs::write(
+            temp.path().join("src/config.ts"),
+            r#"import { defineConfig } from 'vite';
+export default defineConfig({});"#,
+        )
+        .unwrap();
+
+        fs::write(
+            temp.path().join("src/test.ts"),
+            r#"import { describe, it } from 'vitest';
+describe('test', () => {});"#,
+        )
+        .unwrap();
+
+        // Create a file without vite imports (should be unchanged)
+        fs::write(
+            temp.path().join("src/utils.ts"),
+            r#"export function add(a: number, b: number) {
+  return a + b;
+}"#,
+        )
+        .unwrap();
+
+        // Create node_modules (should be ignored)
+        fs::create_dir(temp.path().join("node_modules")).unwrap();
+        fs::write(
+            temp.path().join("node_modules/pkg.ts"),
+            r#"import { defineConfig } from 'vite';"#,
+        )
+        .unwrap();
+
+        // Create .gitignore
+        fs::write(temp.path().join(".gitignore"), "node_modules/").unwrap();
+
+        // Run the batch rewrite
+        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+
+        // Should have 2 modified files (config.ts and test.ts)
+        assert_eq!(result.modified_files.len(), 2);
+        // Should have 1 unchanged file (utils.ts)
+        assert_eq!(result.unchanged_files.len(), 1);
+        // Should have no errors
+        assert!(result.errors.is_empty());
+
+        // Verify the files were actually modified
+        let config_content = fs::read_to_string(temp.path().join("src/config.ts")).unwrap();
+        assert!(config_content.contains("vite-plus"));
+
+        let test_content = fs::read_to_string(temp.path().join("src/test.ts")).unwrap();
+        assert!(test_content.contains("vite-plus/test"));
+
+        // Verify utils.ts was not modified
+        let utils_content = fs::read_to_string(temp.path().join("src/utils.ts")).unwrap();
+        assert!(!utils_content.contains("vite-plus"));
+    }
+
+    #[test]
+    fn test_rewrite_imports_in_directory_empty() {
+        let temp = tempdir().unwrap();
+
+        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+
+        assert!(result.modified_files.is_empty());
+        assert!(result.unchanged_files.is_empty());
+        assert!(result.errors.is_empty());
+    }
+
+    #[test]
+    fn test_rewrite_imports_in_directory_nested() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // Create nested directory structure
+        fs::create_dir_all(temp.path().join("src/components/Button")).unwrap();
+        fs::create_dir_all(temp.path().join("tests/unit")).unwrap();
+
+        // Create files at various depths
+        fs::write(
+            temp.path().join("vite.config.ts"),
+            r#"import { defineConfig } from 'vite';
+export default defineConfig({});"#,
+        )
+        .unwrap();
+
+        fs::write(
+            temp.path().join("src/index.ts"),
+            r#"import { createServer } from 'vite';
+export { createServer };"#,
+        )
+        .unwrap();
+
+        fs::write(
+            temp.path().join("src/components/Button/Button.tsx"),
+            r#"import React from 'react';
+export const Button = () => <button>Click</button>;"#,
+        )
+        .unwrap();
+
+        fs::write(
+            temp.path().join("tests/unit/app.test.ts"),
+            r#"import { describe, it, expect } from 'vitest';
+import { page } from '@vitest/browser';
+
+describe('app', () => {
+  it('works', () => {
+    expect(true).toBe(true);
+  });
+});"#,
+        )
+        .unwrap();
+
+        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+
+        // vite.config.ts, src/index.ts, tests/unit/app.test.ts should be modified
+        assert_eq!(result.modified_files.len(), 3);
+        // Button.tsx has no vite imports
+        assert_eq!(result.unchanged_files.len(), 1);
+        assert!(result.errors.is_empty());
+
+        // Verify nested file was modified
+        let test_content = fs::read_to_string(temp.path().join("tests/unit/app.test.ts")).unwrap();
+        assert!(test_content.contains("vite-plus/test"));
+        assert!(test_content.contains("vite-plus/test/browser"));
+    }
+
+    #[test]
+    fn test_rewrite_declare_module_vite() {
+        let content = r#"declare module 'vite' {
+  interface UserConfig {
+    runtimeEnv?: RuntimeEnvConfig;
+  }
+}"#;
 
-        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { ModuleRunner } from 'vite-plus/module-runner';
-
-export default ModuleRunner;"#
+            r#"declare module 'vite-plus' {
+  interface UserConfig {
+    runtimeEnv?: RuntimeEnvConfig;
+  }
+}"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vite_subpath_double_quotes() {
-        let vite_config = r#"import { ModuleRunner } from "vite/module-runner";
-
-export default ModuleRunner;"#;
+    fn test_rewrite_declare_module_vite_double_quotes() {
+        let content = r#"declare module "vite" {
+  interface UserConfig {
+    custom?: boolean;
+  }
+}"#;
 
-        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { ModuleRunner } from "vite-plus/module-runner";
-
-export default ModuleRunner;"#
+            r#"declare module "vite-plus" {
+  interface UserConfig {
+    custom?: boolean;
+  }
+}"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_subpath() {
-        // Test vitest/node subpath
-        let vite_config = r#"import { startVitest } from 'vitest/node';
-
-export default startVitest;"#;
+    fn test_rewrite_declare_module_vitest() {
+        // `declare module 'vitest'` is intentionally NOT rewritten — the
+        // `vite-plus/test` subpath is a thin shim that re-exports vitest's
+        // types, so augmentations must target the upstream module identity.
+        let content = r#"declare module 'vitest' {
+  interface JestAssertion<T = any> {
+    toBeCustom(): void;
+  }
+}"#;
 
-        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"import { startVitest } from 'vite-plus/test/node';
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-export default startVitest;"#
-        );
+    #[test]
+    fn test_rewrite_declare_module_vitest_config() {
+        // `declare module 'vitest/config'` is intentionally NOT rewritten —
+        // `vite-plus` re-exports `vitest/config`, so augmentations must target
+        // the upstream module identity to merge correctly.
+        let content = r#"declare module 'vitest/config' {
+  interface UserConfig {
+    test?: TestConfig;
+  }
+}"#;
 
-        // Test vitest/plugins/runner subpath
-        let vite_config = r#"import { somePlugin } from 'vitest/plugins/runner';
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-export default somePlugin;"#;
+    #[test]
+    fn test_rewrite_declare_module_vite_subpath() {
+        let content = r#"declare module 'vite/module-runner' {
+  export interface ModuleRunnerOptions {
+    custom?: boolean;
+  }
+}"#;
 
-        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { somePlugin } from 'vite-plus/test/plugins/runner';
-
-export default somePlugin;"#
+            r#"declare module 'vite-plus/module-runner' {
+  export interface ModuleRunnerOptions {
+    custom?: boolean;
+  }
+}"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_vitest_subpath_double_quotes() {
-        let vite_config = r#"import { startVitest } from "vitest/node";
+    fn test_rewrite_declare_module_vitest_subpath() {
+        // `declare module 'vitest/node'` stays — the `vite-plus/test/node`
+        // shim re-exports from upstream, so augmentations must target the
+        // upstream module identity.
+        let content = r#"declare module 'vitest/node' {
+  export interface VitestOptions {
+    custom?: boolean;
+  }
+}"#;
 
-export default startVitest;"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"import { startVitest } from "vite-plus/test/node";
+    #[test]
+    fn test_rewrite_declare_module_vitest_browser() {
+        // `declare module '@vitest/browser'` stays — the
+        // `vite-plus/test/browser` shim re-exports from upstream.
+        let content = r#"declare module '@vitest/browser' {
+  interface BrowserContext {
+    custom?: boolean;
+  }
+}"#;
 
-export default startVitest;"#
-        );
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_import_content_mixed_imports() {
-        // Test multiple different imports in the same file
-        let vite_config = r#"import { defineConfig } from 'vite';
-import { ModuleRunner } from 'vite/module-runner';
-import { describe, it, expect } from 'vitest';
-import { startVitest } from 'vitest/node';
-import { page } from '@vitest/browser';
-import { playwright } from '@vitest/browser-playwright';
-import react from '@vitejs/plugin-react';
+    fn test_rewrite_declare_module_vitest_browser_subpath() {
+        // `declare module '@vitest/browser/context'` stays — shim re-exports.
+        let content = r#"declare module '@vitest/browser/context' {
+  export interface Context {
+    custom?: boolean;
+  }
+}"#;
 
-export default defineConfig({
-  plugins: [react()],
-});"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        let result = rewrite_import_content(vite_config, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"import { defineConfig } from 'vite-plus';
-import { ModuleRunner } from 'vite-plus/module-runner';
-import { describe, it, expect } from 'vite-plus/test';
-import { startVitest } from 'vite-plus/test/node';
-import { page } from 'vite-plus/test/browser';
-import { playwright } from 'vite-plus/test/browser-playwright';
-import react from '@vitejs/plugin-react';
+    #[test]
+    fn test_rewrite_declare_module_vitest_browser_playwright() {
+        // `declare module '@vitest/browser-playwright'` stays — shim re-exports.
+        let content = r#"declare module '@vitest/browser-playwright' {
+  interface PlaywrightContext {
+    custom?: boolean;
+  }
+}"#;
 
-export default defineConfig({
-  plugins: [react()],
-});"#
-        );
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_imports_in_directory() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
+    fn test_rewrite_declare_module_vitest_browser_preview() {
+        // `declare module '@vitest/browser-preview'` stays — shim re-exports.
+        let content = r#"declare module '@vitest/browser-preview' {
+  interface PreviewContext {
+    custom?: boolean;
+  }
+}"#;
 
-        // Create src directory
-        fs::create_dir(temp.path().join("src")).unwrap();
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // Create test files with vite/vitest imports
-        fs::write(
-            temp.path().join("src/config.ts"),
-            r#"import { defineConfig } from 'vite';
-export default defineConfig({});"#,
-        )
-        .unwrap();
+    #[test]
+    fn test_rewrite_declare_module_vitest_browser_webdriverio() {
+        // `declare module '@vitest/browser-webdriverio'` stays — shim re-exports.
+        let content = r#"declare module '@vitest/browser-webdriverio' {
+  interface WebDriverContext {
+    custom?: boolean;
+  }
+}"#;
 
-        fs::write(
-            temp.path().join("src/test.ts"),
-            r#"import { describe, it } from 'vitest';
-describe('test', () => {});"#,
-        )
-        .unwrap();
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // Create a file without vite imports (should be unchanged)
-        fs::write(
-            temp.path().join("src/utils.ts"),
-            r#"export function add(a: number, b: number) {
-  return a + b;
-}"#,
-        )
-        .unwrap();
+    #[test]
+    fn test_rewrite_mixed_imports_and_declare_modules() {
+        // Imports are rewritten; `declare module 'vite' { ... }` is rewritten
+        // (vite-plus bundles vite, owning the type identity), but
+        // `declare module 'vitest' { ... }` stays put — the shim re-exports
+        // upstream and augmentations must target the upstream identity.
+        let content = r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
 
-        // Create node_modules (should be ignored)
-        fs::create_dir(temp.path().join("node_modules")).unwrap();
-        fs::write(
-            temp.path().join("node_modules/pkg.ts"),
-            r#"import { defineConfig } from 'vite';"#,
-        )
-        .unwrap();
+declare module 'vite' {
+  interface UserConfig {
+    custom?: boolean;
+  }
+}
 
-        // Create .gitignore
-        fs::write(temp.path().join(".gitignore"), "node_modules/").unwrap();
+declare module 'vitest' {
+  interface JestAssertion<T = any> {
+    toBeCustom(): void;
+  }
+}
 
-        // Run the batch rewrite
-        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+export default defineConfig({});"#;
 
-        // Should have 2 modified files (config.ts and test.ts)
-        assert_eq!(result.modified_files.len(), 2);
-        // Should have 1 unchanged file (utils.ts)
-        assert_eq!(result.unchanged_files.len(), 1);
-        // Should have no errors
-        assert!(result.errors.is_empty());
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"import { defineConfig } from 'vite-plus';
+import { describe } from 'vite-plus/test';
 
-        // Verify the files were actually modified
-        let config_content = fs::read_to_string(temp.path().join("src/config.ts")).unwrap();
-        assert!(config_content.contains("vite-plus"));
+declare module 'vite-plus' {
+  interface UserConfig {
+    custom?: boolean;
+  }
+}
 
-        let test_content = fs::read_to_string(temp.path().join("src/test.ts")).unwrap();
-        assert!(test_content.contains("vite-plus/test"));
+declare module 'vitest' {
+  interface JestAssertion<T = any> {
+    toBeCustom(): void;
+  }
+}
 
-        // Verify utils.ts was not modified
-        let utils_content = fs::read_to_string(temp.path().join("src/utils.ts")).unwrap();
-        assert!(!utils_content.contains("vite-plus"));
+export default defineConfig({});"#
+        );
     }
 
     #[test]
-    fn test_rewrite_imports_in_directory_empty() {
-        let temp = tempdir().unwrap();
-
-        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+    fn test_rewrite_declare_module_already_vite_plus() {
+        let content = r#"declare module 'vite-plus' {
+  interface UserConfig {
+    custom?: boolean;
+  }
+}"#;
 
-        assert!(result.modified_files.is_empty());
-        assert!(result.unchanged_files.is_empty());
-        assert!(result.errors.is_empty());
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_imports_in_directory_nested() {
-        use std::fs;
+    fn test_rewrite_multiple_declare_modules() {
+        // `declare module 'vite'` / `'vite/<sub>'` get rewritten (vite-plus
+        // bundles vite, owning the type identity), but `declare module 'vitest'`
+        // and `declare module '@vitest/browser'` are preserved — vite-plus
+        // shims re-export upstream and augmentations must target upstream.
+        let content = r#"declare module 'vite' {
+  interface UserConfig {
+    custom?: boolean;
+  }
+}
 
-        let temp = tempdir().unwrap();
+declare module 'vite/module-runner' {
+  export interface ModuleRunnerOptions {
+    custom?: boolean;
+  }
+}
 
-        // Create nested directory structure
-        fs::create_dir_all(temp.path().join("src/components/Button")).unwrap();
-        fs::create_dir_all(temp.path().join("tests/unit")).unwrap();
+declare module 'vitest' {
+  interface JestAssertion<T = any> {
+    toBeCustom(): void;
+  }
+}
 
-        // Create files at various depths
-        fs::write(
-            temp.path().join("vite.config.ts"),
-            r#"import { defineConfig } from 'vite';
-export default defineConfig({});"#,
-        )
-        .unwrap();
+declare module '@vitest/browser' {
+  interface BrowserContext {
+    custom?: boolean;
+  }
+}"#;
 
-        fs::write(
-            temp.path().join("src/index.ts"),
-            r#"import { createServer } from 'vite';
-export { createServer };"#,
-        )
-        .unwrap();
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"declare module 'vite-plus' {
+  interface UserConfig {
+    custom?: boolean;
+  }
+}
 
-        fs::write(
-            temp.path().join("src/components/Button/Button.tsx"),
-            r#"import React from 'react';
-export const Button = () => <button>Click</button>;"#,
-        )
-        .unwrap();
+declare module 'vite-plus/module-runner' {
+  export interface ModuleRunnerOptions {
+    custom?: boolean;
+  }
+}
 
-        fs::write(
-            temp.path().join("tests/unit/app.test.ts"),
-            r#"import { describe, it, expect } from 'vitest';
-import { page } from '@vitest/browser';
+declare module 'vitest' {
+  interface JestAssertion<T = any> {
+    toBeCustom(): void;
+  }
+}
 
-describe('app', () => {
-  it('works', () => {
-    expect(true).toBe(true);
-  });
-});"#,
-        )
-        .unwrap();
+declare module '@vitest/browser' {
+  interface BrowserContext {
+    custom?: boolean;
+  }
+}"#
+        );
+    }
 
-        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+    #[test]
+    fn test_rewrite_declare_module_vitest_double_quotes() {
+        // Double-quoted `declare module "vitest"` is preserved for the same
+        // reason as the single-quoted variant — shim re-exports.
+        let content = r#"declare module "vitest" {
+  interface JestAssertion<T = any> {
+    toBeCustom(): void;
+  }
+}"#;
 
-        // vite.config.ts, src/index.ts, tests/unit/app.test.ts should be modified
-        assert_eq!(result.modified_files.len(), 3);
-        // Button.tsx has no vite imports
-        assert_eq!(result.unchanged_files.len(), 1);
-        assert!(result.errors.is_empty());
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // Verify nested file was modified
-        let test_content = fs::read_to_string(temp.path().join("tests/unit/app.test.ts")).unwrap();
-        assert!(test_content.contains("vite-plus/test"));
-        assert!(test_content.contains("vite-plus/test/browser"));
+    #[test]
+    fn test_rewrite_declare_module_vitest_browser_playwright_subpath() {
+        let content = r#"declare module '@vitest/browser-playwright/context' {
+  export interface Context {
+    custom?: boolean;
+  }
+}"#;
+
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_declare_module_vite() {
+    fn test_rewrite_declare_module_vitest_browser_preview_subpath() {
+        let content = r#"declare module '@vitest/browser-preview/context' {
+  export interface Context {
+    custom?: boolean;
+  }
+}"#;
+
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
+
+    #[test]
+    fn test_rewrite_declare_module_vitest_browser_webdriverio_subpath() {
+        let content = r#"declare module '@vitest/browser-webdriverio/context' {
+  export interface Context {
+    custom?: boolean;
+  }
+}"#;
+
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
+
+    #[test]
+    fn test_rewrite_declare_module_complex_interface() {
         let content = r#"declare module 'vite' {
   interface UserConfig {
+    /**
+     * Options for vite-plugin-runtime-env
+     */
     runtimeEnv?: RuntimeEnvConfig;
+    /**
+     * Options for vite-plugin-runtime-html
+     */
+    runtimeHtml?: RuntimeHtmlConfig;
+  }
+
+  interface Plugin {
+    name: string;
+    configResolved?: (config: ResolvedConfig) => void;
   }
 }"#;
 
@@ -1440,57 +3226,69 @@ describe('app', () => {
             result.content,
             r#"declare module 'vite-plus' {
   interface UserConfig {
+    /**
+     * Options for vite-plugin-runtime-env
+     */
     runtimeEnv?: RuntimeEnvConfig;
+    /**
+     * Options for vite-plugin-runtime-html
+     */
+    runtimeHtml?: RuntimeHtmlConfig;
+  }
+
+  interface Plugin {
+    name: string;
+    configResolved?: (config: ResolvedConfig) => void;
   }
 }"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_vite_double_quotes() {
-        let content = r#"declare module "vite" {
-  interface UserConfig {
-    custom?: boolean;
-  }
-}"#;
+    fn test_rewrite_import_content_tsdown() {
+        let tsdown_config = r#"import { defineConfig } from 'tsdown';
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+export default defineConfig({
+  entry: 'src/index.ts',
+});"#;
+
+        let result = rewrite_import_content(tsdown_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module "vite-plus" {
-  interface UserConfig {
-    custom?: boolean;
-  }
-}"#
+            r#"import { defineConfig } from 'vite-plus/pack';
+
+export default defineConfig({
+  entry: 'src/index.ts',
+});"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest() {
-        let content = r#"declare module 'vitest' {
-  interface JestAssertion<T = any> {
-    toBeCustom(): void;
-  }
-}"#;
+    fn test_rewrite_import_content_tsdown_double_quotes() {
+        let tsdown_config = r#"import { defineConfig } from "tsdown";
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+export default defineConfig({
+  entry: "src/index.ts",
+});"#;
+
+        let result = rewrite_import_content(tsdown_config, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus/test' {
-  interface JestAssertion<T = any> {
-    toBeCustom(): void;
-  }
-}"#
+            r#"import { defineConfig } from "vite-plus/pack";
+
+export default defineConfig({
+  entry: "src/index.ts",
+});"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_config() {
-        let content = r#"declare module 'vitest/config' {
-  interface UserConfig {
-    test?: TestConfig;
+    fn test_rewrite_declare_module_tsdown() {
+        let content = r#"declare module 'tsdown' {
+  interface BuildConfig {
+    custom?: boolean;
   }
 }"#;
 
@@ -1498,18 +3296,18 @@ describe('app', () => {
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus' {
-  interface UserConfig {
-    test?: TestConfig;
+            r#"declare module 'vite-plus/pack' {
+  interface BuildConfig {
+    custom?: boolean;
   }
 }"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_vite_subpath() {
-        let content = r#"declare module 'vite/module-runner' {
-  export interface ModuleRunnerOptions {
+    fn test_rewrite_declare_module_tsdown_double_quotes() {
+        let content = r#"declare module "tsdown" {
+  interface BuildConfig {
     custom?: boolean;
   }
 }"#;
@@ -1518,8 +3316,8 @@ describe('app', () => {
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus/module-runner' {
-  export interface ModuleRunnerOptions {
+            r#"declare module "vite-plus/pack" {
+  interface BuildConfig {
     custom?: boolean;
   }
 }"#
@@ -1527,9 +3325,27 @@ describe('app', () => {
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_subpath() {
-        let content = r#"declare module 'vitest/node' {
-  export interface VitestOptions {
+    fn test_rewrite_import_content_tsdown_client() {
+        let content = r#"import 'tsdown/client';"#;
+
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"import 'vite-plus/pack/client';"#);
+    }
+
+    #[test]
+    fn test_rewrite_import_content_tsdown_client_double_quotes() {
+        let content = r#"import "tsdown/client";"#;
+
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"import "vite-plus/pack/client";"#);
+    }
+
+    #[test]
+    fn test_rewrite_declare_module_tsdown_client() {
+        let content = r#"declare module 'tsdown/client' {
+  interface ClientConfig {
     custom?: boolean;
   }
 }"#;
@@ -1538,8 +3354,8 @@ describe('app', () => {
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus/test/node' {
-  export interface VitestOptions {
+            r#"declare module 'vite-plus/pack/client' {
+  interface ClientConfig {
     custom?: boolean;
   }
 }"#
@@ -1547,9 +3363,9 @@ describe('app', () => {
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_browser() {
-        let content = r#"declare module '@vitest/browser' {
-  interface BrowserContext {
+    fn test_rewrite_declare_module_tsdown_client_double_quotes() {
+        let content = r#"declare module "tsdown/client" {
+  interface ClientConfig {
     custom?: boolean;
   }
 }"#;
@@ -1558,1366 +3374,1711 @@ describe('app', () => {
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus/test/browser' {
-  interface BrowserContext {
+            r#"declare module "vite-plus/pack/client" {
+  interface ClientConfig {
     custom?: boolean;
   }
 }"#
         );
     }
 
+    // ========================
+    // PeerDependencies Tests
+    // ========================
+
     #[test]
-    fn test_rewrite_declare_module_vitest_browser_subpath() {
-        let content = r#"declare module '@vitest/browser/context' {
-  export interface Context {
-    custom?: boolean;
-  }
-}"#;
+    fn test_skip_vite_when_peer_dependency() {
+        // When vite is a peerDependency, vite imports should NOT be rewritten
+        let content = r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+export default defineConfig({});"#;
+
+        let skip_packages =
+            SkipPackages { skip_vite: true, skip_vitest: false, skip_tsdown: false };
+
+        let result = rewrite_import_content(content, &skip_packages).unwrap();
         assert!(result.updated);
+        // vite import should NOT be rewritten, vitest import SHOULD be rewritten
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus/test/browser/context' {
-  export interface Context {
-    custom?: boolean;
-  }
-}"#
+            r#"import { defineConfig } from 'vite';
+import { describe } from 'vite-plus/test';
+
+export default defineConfig({});"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_browser_playwright() {
-        let content = r#"declare module '@vitest/browser-playwright' {
-  interface PlaywrightContext {
-    custom?: boolean;
-  }
-}"#;
+    fn test_skip_vitest_when_peer_dependency() {
+        // When vitest is a peerDependency, vitest imports should NOT be rewritten
+        let content = r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+export default defineConfig({});"#;
+
+        let skip_packages =
+            SkipPackages { skip_vite: false, skip_vitest: true, skip_tsdown: false };
+
+        let result = rewrite_import_content(content, &skip_packages).unwrap();
         assert!(result.updated);
+        // vite import SHOULD be rewritten, vitest import should NOT be rewritten
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus/test/browser-playwright' {
-  interface PlaywrightContext {
-    custom?: boolean;
-  }
-}"#
+            r#"import { defineConfig } from 'vite-plus';
+import { describe } from 'vitest';
+
+export default defineConfig({});"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_browser_preview() {
-        let content = r#"declare module '@vitest/browser-preview' {
-  interface PreviewContext {
-    custom?: boolean;
+    fn test_skip_all_when_all_peer_dependencies() {
+        // When all packages are peerDependencies, nothing should be rewritten
+        let content = r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
+import { build } from 'tsdown';
+
+export default defineConfig({});"#;
+
+        let skip_packages = SkipPackages { skip_vite: true, skip_vitest: true, skip_tsdown: true };
+
+        let result = rewrite_import_content(content, &skip_packages).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
+
+    #[test]
+    fn test_skip_packages_all_skipped() {
+        let skip_all = SkipPackages { skip_vite: true, skip_vitest: true, skip_tsdown: true };
+        assert!(skip_all.all_skipped());
+
+        let skip_some = SkipPackages { skip_vite: true, skip_vitest: false, skip_tsdown: true };
+        assert!(!skip_some.all_skipped());
+
+        let skip_none = SkipPackages::default();
+        assert!(!skip_none.all_skipped());
+    }
+
+    #[test]
+    fn test_get_skip_packages_from_package_json_with_vite_peer_dep() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // Create package.json with vite as peerDependency
+        let pkg_json = r#"{
+  "name": "my-vite-plugin",
+  "peerDependencies": {
+    "vite": "^5.0.0"
+  }
+}"#;
+        let package_json_path = temp.path().join("package.json");
+        fs::write(&package_json_path, pkg_json).unwrap();
+
+        let skip = get_skip_packages_from_package_json(&package_json_path);
+        assert!(skip.skip_vite);
+        assert!(!skip.skip_vitest);
+        assert!(!skip.skip_tsdown);
+    }
+
+    #[test]
+    fn test_get_skip_packages_from_package_json_with_all_peer_deps() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        let pkg_json = r#"{
+  "name": "my-plugin",
+  "peerDependencies": {
+    "vite": "^5.0.0",
+    "vitest": "^1.0.0",
+    "tsdown": "^1.0.0"
+  }
+}"#;
+        let package_json_path = temp.path().join("package.json");
+        fs::write(&package_json_path, pkg_json).unwrap();
+
+        let skip = get_skip_packages_from_package_json(&package_json_path);
+        assert!(skip.skip_vite);
+        assert!(skip.skip_vitest);
+        assert!(skip.skip_tsdown);
+        assert!(skip.all_skipped());
+    }
+
+    #[test]
+    fn test_get_skip_packages_from_package_json_with_vite_dependency() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // vite in dependencies should also skip rewriting
+        let pkg_json = r#"{
+  "name": "my-app",
+  "dependencies": {
+    "vite": "^5.0.0"
+  }
+}"#;
+        let package_json_path = temp.path().join("package.json");
+        fs::write(&package_json_path, pkg_json).unwrap();
+
+        let skip = get_skip_packages_from_package_json(&package_json_path);
+        assert!(skip.skip_vite); // NOW skips because vite is in dependencies
+        assert!(!skip.skip_vitest);
+        assert!(!skip.skip_tsdown);
+    }
+
+    #[test]
+    fn test_get_skip_packages_from_package_json_no_file() {
+        let temp = tempdir().unwrap();
+
+        // No package.json created - should return default (no skipping)
+        let package_json_path = temp.path().join("package.json");
+        let skip = get_skip_packages_from_package_json(&package_json_path);
+        assert!(!skip.skip_vite);
+        assert!(!skip.skip_vitest);
+        assert!(!skip.skip_tsdown);
+    }
+
+    #[test]
+    fn test_get_skip_packages_from_package_json_no_deps() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // Package with no dependencies at all
+        let pkg_json = r#"{
+  "name": "my-app"
+}"#;
+        let package_json_path = temp.path().join("package.json");
+        fs::write(&package_json_path, pkg_json).unwrap();
+
+        let skip = get_skip_packages_from_package_json(&package_json_path);
+        assert!(!skip.skip_vite);
+        assert!(!skip.skip_vitest);
+        assert!(!skip.skip_tsdown);
+    }
+
+    #[test]
+    fn test_get_skip_packages_mixed_peer_and_regular_deps() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // vite in dependencies, vitest in peerDependencies
+        let pkg_json = r#"{
+  "name": "my-package",
+  "dependencies": {
+    "vite": "^5.0.0"
+  },
+  "peerDependencies": {
+    "vitest": "^1.0.0"
   }
 }"#;
+        let package_json_path = temp.path().join("package.json");
+        fs::write(&package_json_path, pkg_json).unwrap();
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"declare module 'vite-plus/test/browser-preview' {
-  interface PreviewContext {
-    custom?: boolean;
-  }
-}"#
-        );
+        let skip = get_skip_packages_from_package_json(&package_json_path);
+        assert!(skip.skip_vite); // in dependencies
+        assert!(skip.skip_vitest); // in peerDependencies
+        assert!(!skip.skip_tsdown);
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_browser_webdriverio() {
-        let content = r#"declare module '@vitest/browser-webdriverio' {
-  interface WebDriverContext {
-    custom?: boolean;
+    fn test_rewrite_imports_in_directory_with_vite_dependency() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // Create package.json with vite as dependency (not peerDependency)
+        let pkg_json = r#"{
+  "name": "my-app",
+  "dependencies": {
+    "vite": "^5.0.0"
   }
 }"#;
+        fs::write(temp.path().join("package.json"), pkg_json).unwrap();
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
+        // Create src directory
+        fs::create_dir(temp.path().join("src")).unwrap();
+
+        // Create source file with vite and vitest imports
+        let original_content = r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
+
+export default defineConfig({});"#;
+        fs::write(temp.path().join("src/index.ts"), original_content).unwrap();
+
+        // Run the batch rewrite
+        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+
+        // File should be modified (vitest was rewritten)
+        assert_eq!(result.modified_files.len(), 1);
+        assert!(result.errors.is_empty());
+
+        // Verify vite import NOT rewritten (in dependencies), vitest IS rewritten
+        let content = fs::read_to_string(temp.path().join("src/index.ts")).unwrap();
         assert_eq!(
-            result.content,
-            r#"declare module 'vite-plus/test/browser-webdriverio' {
-  interface WebDriverContext {
-    custom?: boolean;
-  }
-}"#
+            content,
+            r#"import { defineConfig } from 'vite';
+import { describe } from 'vite-plus/test';
+
+export default defineConfig({});"#
         );
     }
 
     #[test]
-    fn test_rewrite_mixed_imports_and_declare_modules() {
-        let content = r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
+    fn test_rewrite_imports_in_directory_with_peer_deps() {
+        use std::fs;
 
-declare module 'vite' {
-  interface UserConfig {
-    custom?: boolean;
-  }
-}
+        let temp = tempdir().unwrap();
 
-declare module 'vitest' {
-  interface JestAssertion<T = any> {
-    toBeCustom(): void;
+        // Create package.json with vite as peerDependency
+        let pkg_json = r#"{
+  "name": "my-vite-plugin",
+  "peerDependencies": {
+    "vite": "^5.0.0"
   }
-}
+}"#;
+        fs::write(temp.path().join("package.json"), pkg_json).unwrap();
+
+        // Create src directory
+        fs::create_dir(temp.path().join("src")).unwrap();
+
+        // Create source file with vite and vitest imports
+        let original_content = r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
 
 export default defineConfig({});"#;
+        fs::write(temp.path().join("src/index.ts"), original_content).unwrap();
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"import { defineConfig } from 'vite-plus';
-import { describe } from 'vite-plus/test';
+        // Run the batch rewrite
+        let result = rewrite_imports_in_directory(temp.path()).unwrap();
 
-declare module 'vite-plus' {
-  interface UserConfig {
-    custom?: boolean;
-  }
-}
+        // File should be modified (vitest was rewritten)
+        assert_eq!(result.modified_files.len(), 1);
+        assert!(result.errors.is_empty());
 
-declare module 'vite-plus/test' {
-  interface JestAssertion<T = any> {
-    toBeCustom(): void;
-  }
-}
+        // Verify vite import NOT rewritten, vitest IS rewritten
+        let content = fs::read_to_string(temp.path().join("src/index.ts")).unwrap();
+        assert_eq!(
+            content,
+            r#"import { defineConfig } from 'vite';
+import { describe } from 'vite-plus/test';
 
 export default defineConfig({});"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_already_vite_plus() {
-        let content = r#"declare module 'vite-plus' {
-  interface UserConfig {
-    custom?: boolean;
+    fn test_rewrite_imports_skips_file_when_all_peer_deps() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // Create package.json with all packages as peerDependencies
+        let pkg_json = r#"{
+  "name": "my-plugin",
+  "peerDependencies": {
+    "vite": "^5.0.0",
+    "vitest": "^1.0.0",
+    "tsdown": "^1.0.0"
   }
 }"#;
+        fs::write(temp.path().join("package.json"), pkg_json).unwrap();
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+        // Create source file
+        let original_content = r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
+import { build } from 'tsdown';"#;
+        fs::write(temp.path().join("index.ts"), original_content).unwrap();
+
+        // Run the batch rewrite
+        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+
+        // File should be unchanged (all skipped)
+        assert!(result.modified_files.is_empty());
+        assert_eq!(result.unchanged_files.len(), 1);
+
+        // Verify content unchanged
+        let content = fs::read_to_string(temp.path().join("index.ts")).unwrap();
+        assert_eq!(content, original_content);
     }
 
     #[test]
-    fn test_rewrite_multiple_declare_modules() {
-        let content = r#"declare module 'vite' {
-  interface UserConfig {
-    custom?: boolean;
-  }
-}
+    fn test_find_nearest_package_json() {
+        use std::fs;
 
-declare module 'vite/module-runner' {
-  export interface ModuleRunnerOptions {
-    custom?: boolean;
-  }
-}
+        let temp = tempdir().unwrap();
 
-declare module 'vitest' {
-  interface JestAssertion<T = any> {
-    toBeCustom(): void;
-  }
-}
+        // Create monorepo structure
+        fs::create_dir_all(temp.path().join("packages/vite-plugin/src")).unwrap();
+        fs::create_dir_all(temp.path().join("packages/app/src")).unwrap();
 
-declare module '@vitest/browser' {
-  interface BrowserContext {
-    custom?: boolean;
-  }
-}"#;
+        // Root package.json (no peerDeps)
+        fs::write(temp.path().join("package.json"), r#"{"name": "monorepo"}"#).unwrap();
 
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"declare module 'vite-plus' {
-  interface UserConfig {
-    custom?: boolean;
-  }
-}
+        // vite-plugin package.json (has vite in peerDeps)
+        fs::write(
+            temp.path().join("packages/vite-plugin/package.json"),
+            r#"{"name": "vite-plugin", "peerDependencies": {"vite": "^5.0.0"}}"#,
+        )
+        .unwrap();
+
+        // app package.json (no peerDeps)
+        fs::write(temp.path().join("packages/app/package.json"), r#"{"name": "app"}"#).unwrap();
+
+        // Test finding package.json from vite-plugin/src/index.ts
+        let file_path = temp.path().join("packages/vite-plugin/src/index.ts");
+        let result = find_nearest_package_json(&file_path, temp.path());
+        assert_eq!(result, Some(temp.path().join("packages/vite-plugin/package.json")));
+
+        // Test finding package.json from app/src/index.ts
+        let file_path = temp.path().join("packages/app/src/index.ts");
+        let result = find_nearest_package_json(&file_path, temp.path());
+        assert_eq!(result, Some(temp.path().join("packages/app/package.json")));
+
+        // Test finding package.json from root level file
+        let file_path = temp.path().join("vite.config.ts");
+        let result = find_nearest_package_json(&file_path, temp.path());
+        assert_eq!(result, Some(temp.path().join("package.json")));
+    }
+
+    #[test]
+    fn test_rewrite_imports_monorepo_different_peer_deps() {
+        use std::fs;
+
+        let temp = tempdir().unwrap();
+
+        // Create monorepo structure
+        fs::create_dir_all(temp.path().join("packages/vite-plugin/src")).unwrap();
+        fs::create_dir_all(temp.path().join("packages/app/src")).unwrap();
+
+        // Root package.json (no peerDeps)
+        fs::write(temp.path().join("package.json"), r#"{"name": "monorepo"}"#).unwrap();
+
+        // vite-plugin package.json (has vite in peerDeps)
+        fs::write(
+            temp.path().join("packages/vite-plugin/package.json"),
+            r#"{"name": "vite-plugin", "peerDependencies": {"vite": "^5.0.0"}}"#,
+        )
+        .unwrap();
+
+        // app package.json (no peerDeps)
+        fs::write(temp.path().join("packages/app/package.json"), r#"{"name": "app"}"#).unwrap();
+
+        // vite-plugin source file with vite and vitest imports
+        fs::write(
+            temp.path().join("packages/vite-plugin/src/index.ts"),
+            r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
+export default defineConfig({});"#,
+        )
+        .unwrap();
+
+        // app source file with vite and vitest imports
+        fs::write(
+            temp.path().join("packages/app/src/index.ts"),
+            r#"import { defineConfig } from 'vite';
+import { describe } from 'vitest';
+export default defineConfig({});"#,
+        )
+        .unwrap();
 
-declare module 'vite-plus/module-runner' {
-  export interface ModuleRunnerOptions {
-    custom?: boolean;
-  }
-}
+        // Run the batch rewrite
+        let result = rewrite_imports_in_directory(temp.path()).unwrap();
 
-declare module 'vite-plus/test' {
-  interface JestAssertion<T = any> {
-    toBeCustom(): void;
-  }
-}
+        // Both files should be modified
+        assert_eq!(result.modified_files.len(), 2);
 
-declare module 'vite-plus/test/browser' {
-  interface BrowserContext {
-    custom?: boolean;
-  }
-}"#
+        // vite-plugin: vite NOT rewritten (has peerDep), vitest IS rewritten
+        let vite_plugin_content =
+            fs::read_to_string(temp.path().join("packages/vite-plugin/src/index.ts")).unwrap();
+        assert_eq!(
+            vite_plugin_content,
+            r#"import { defineConfig } from 'vite';
+import { describe } from 'vite-plus/test';
+export default defineConfig({});"#
+        );
+
+        // app: vite IS rewritten (no peerDep), vitest IS rewritten
+        let app_content =
+            fs::read_to_string(temp.path().join("packages/app/src/index.ts")).unwrap();
+        assert_eq!(
+            app_content,
+            r#"import { defineConfig } from 'vite-plus';
+import { describe } from 'vite-plus/test';
+export default defineConfig({});"#
         );
     }
 
-    #[test]
-    fn test_rewrite_declare_module_vitest_double_quotes() {
-        let content = r#"declare module "vitest" {
-  interface JestAssertion<T = any> {
-    toBeCustom(): void;
-  }
-}"#;
+    // ====================================
+    // CommonJS `require(...)` Rewriting Tests
+    // ====================================
+    //
+    // These tests cover the require-shape sibling rules that mirror the
+    // `import_statement` rewrites for `.cjs` / `.cts` and other CJS sources.
+    // The rules MUST only match a literal `require(...)` callee — not
+    // `my_require(...)`, not `require.cache[...]`, and not dynamic
+    // `import('...')` (the import_statement rules already cover ESM).
 
+    #[test]
+    fn test_rewrite_require_vite() {
+        let content = r#"const { defineConfig } = require('vite');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"declare module "vite-plus/test" {
-  interface JestAssertion<T = any> {
-    toBeCustom(): void;
-  }
-}"#
-        );
+        assert_eq!(result.content, r#"const { defineConfig } = require('vite-plus');"#);
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_browser_playwright_subpath() {
-        let content = r#"declare module '@vitest/browser-playwright/context' {
-  export interface Context {
-    custom?: boolean;
-  }
-}"#;
-
+    fn test_rewrite_require_vite_double_quotes() {
+        let content = r#"const { defineConfig } = require("vite");"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"declare module 'vite-plus/test/browser-playwright/context' {
-  export interface Context {
-    custom?: boolean;
-  }
-}"#
-        );
+        assert_eq!(result.content, r#"const { defineConfig } = require("vite-plus");"#);
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_browser_preview_subpath() {
-        let content = r#"declare module '@vitest/browser-preview/context' {
-  export interface Context {
-    custom?: boolean;
-  }
-}"#;
-
+    fn test_rewrite_require_vite_subpath() {
+        let content = r#"const { ModuleRunner } = require('vite/module-runner');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus/test/browser-preview/context' {
-  export interface Context {
-    custom?: boolean;
-  }
-}"#
+            r#"const { ModuleRunner } = require('vite-plus/module-runner');"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_vitest_browser_webdriverio_subpath() {
-        let content = r#"declare module '@vitest/browser-webdriverio/context' {
-  export interface Context {
-    custom?: boolean;
-  }
-}"#;
+    fn test_rewrite_require_vitest() {
+        let content = r#"const vi = require('vitest');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const vi = require('vite-plus/test');"#);
+    }
 
+    #[test]
+    fn test_rewrite_require_vitest_config() {
+        let content = r#"const { defineConfig } = require('vitest/config');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"declare module 'vite-plus/test/browser-webdriverio/context' {
-  export interface Context {
-    custom?: boolean;
-  }
-}"#
-        );
+        assert_eq!(result.content, r#"const { defineConfig } = require('vite-plus');"#);
     }
 
     #[test]
-    fn test_rewrite_declare_module_complex_interface() {
-        let content = r#"declare module 'vite' {
-  interface UserConfig {
-    /**
-     * Options for vite-plugin-runtime-env
-     */
-    runtimeEnv?: RuntimeEnvConfig;
-    /**
-     * Options for vite-plugin-runtime-html
-     */
-    runtimeHtml?: RuntimeHtmlConfig;
-  }
+    fn test_rewrite_require_vitest_subpath() {
+        let content = r#"const { startVitest } = require('vitest/node');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const { startVitest } = require('vite-plus/test/node');"#);
+    }
 
-  interface Plugin {
-    name: string;
-    configResolved?: (config: ResolvedConfig) => void;
-  }
-}"#;
+    #[test]
+    fn test_rewrite_require_vitest_browser() {
+        let content = r#"const x = require('@vitest/browser');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const x = require('vite-plus/test/browser');"#);
+    }
 
+    #[test]
+    fn test_rewrite_require_vitest_browser_context() {
+        let content = r#"const { context } = require('@vitest/browser/context');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus' {
-  interface UserConfig {
-    /**
-     * Options for vite-plugin-runtime-env
-     */
-    runtimeEnv?: RuntimeEnvConfig;
-    /**
-     * Options for vite-plugin-runtime-html
-     */
-    runtimeHtml?: RuntimeHtmlConfig;
-  }
-
-  interface Plugin {
-    name: string;
-    configResolved?: (config: ResolvedConfig) => void;
-  }
-}"#
+            r#"const { context } = require('vite-plus/test/browser/context');"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_tsdown() {
-        let tsdown_config = r#"import { defineConfig } from 'tsdown';
-
-export default defineConfig({
-  entry: 'src/index.ts',
-});"#;
+    fn test_rewrite_require_vitest_browser_flat_subpaths() {
+        // `@vitest/browser/{client,locators,matchers,utils}` get the `/browser/`
+        // segment stripped, mirroring the import_statement rule.
+        for (sub, expected) in [
+            ("client", "vite-plus/test/client"),
+            ("locators", "vite-plus/test/locators"),
+            ("matchers", "vite-plus/test/matchers"),
+            ("utils", "vite-plus/test/utils"),
+        ] {
+            let single = format!("const x = require('@vitest/browser/{sub}');");
+            let result = rewrite_import_content(&single, &SkipPackages::default()).unwrap();
+            assert!(result.updated, "single-quoted require @vitest/browser/{sub} should rewrite");
+            assert_eq!(result.content, format!("const x = require('{expected}');"));
+
+            let double = format!("const x = require(\"@vitest/browser/{sub}\");");
+            let result = rewrite_import_content(&double, &SkipPackages::default()).unwrap();
+            assert!(result.updated, "double-quoted require @vitest/browser/{sub} should rewrite");
+            assert_eq!(result.content, format!("const x = require(\"{expected}\");"));
+        }
+    }
 
-        let result = rewrite_import_content(tsdown_config, &SkipPackages::default()).unwrap();
+    #[test]
+    fn test_rewrite_require_vitest_browser_playwright() {
+        let content = r#"const x = require('@vitest/browser-playwright');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"import { defineConfig } from 'vite-plus/pack';
-
-export default defineConfig({
-  entry: 'src/index.ts',
-});"#
-        );
+        assert_eq!(result.content, r#"const x = require('vite-plus/test/browser-playwright');"#);
     }
 
     #[test]
-    fn test_rewrite_import_content_tsdown_double_quotes() {
-        let tsdown_config = r#"import { defineConfig } from "tsdown";
-
-export default defineConfig({
-  entry: "src/index.ts",
-});"#;
+    fn test_rewrite_require_vitest_browser_playwright_context() {
+        let content = r#"const x = require('@vitest/browser-playwright/context');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const x = require('vite-plus/test/browser/context');"#);
+    }
 
-        let result = rewrite_import_content(tsdown_config, &SkipPackages::default()).unwrap();
+    #[test]
+    fn test_rewrite_require_vitest_browser_playwright_provider() {
+        let content = r#"const x = require('@vitest/browser-playwright/provider');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"import { defineConfig } from "vite-plus/pack";
-
-export default defineConfig({
-  entry: "src/index.ts",
-});"#
+            r#"const x = require('vite-plus/test/browser/providers/playwright');"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_tsdown() {
-        let content = r#"declare module 'tsdown' {
-  interface BuildConfig {
-    custom?: boolean;
-  }
-}"#;
-
+    fn test_rewrite_require_vitest_browser_preview() {
+        let content = r#"const x = require('@vitest/browser-preview');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"declare module 'vite-plus/pack' {
-  interface BuildConfig {
-    custom?: boolean;
-  }
-}"#
-        );
+        assert_eq!(result.content, r#"const x = require('vite-plus/test/browser-preview');"#);
     }
 
     #[test]
-    fn test_rewrite_declare_module_tsdown_double_quotes() {
-        let content = r#"declare module "tsdown" {
-  interface BuildConfig {
-    custom?: boolean;
-  }
-}"#;
+    fn test_rewrite_require_vitest_browser_preview_context() {
+        let content = r#"const x = require('@vitest/browser-preview/context');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const x = require('vite-plus/test/browser/context');"#);
+    }
 
+    #[test]
+    fn test_rewrite_require_vitest_browser_preview_provider() {
+        let content = r#"const x = require('@vitest/browser-preview/provider');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module "vite-plus/pack" {
-  interface BuildConfig {
-    custom?: boolean;
-  }
-}"#
+            r#"const x = require('vite-plus/test/browser/providers/preview');"#
         );
     }
 
     #[test]
-    fn test_rewrite_import_content_tsdown_client() {
-        let content = r#"import 'tsdown/client';"#;
-
+    fn test_rewrite_require_vitest_browser_webdriverio() {
+        let content = r#"const x = require('@vitest/browser-webdriverio');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"import 'vite-plus/pack/client';"#);
+        assert_eq!(result.content, r#"const x = require('vite-plus/test/browser-webdriverio');"#);
     }
 
     #[test]
-    fn test_rewrite_import_content_tsdown_client_double_quotes() {
-        let content = r#"import "tsdown/client";"#;
-
+    fn test_rewrite_require_vitest_browser_webdriverio_context() {
+        let content = r#"const x = require('@vitest/browser-webdriverio/context');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"import "vite-plus/pack/client";"#);
+        assert_eq!(result.content, r#"const x = require('vite-plus/test/browser/context');"#);
     }
 
     #[test]
-    fn test_rewrite_declare_module_tsdown_client() {
-        let content = r#"declare module 'tsdown/client' {
-  interface ClientConfig {
-    custom?: boolean;
-  }
-}"#;
-
+    fn test_rewrite_require_vitest_browser_webdriverio_provider() {
+        let content = r#"const x = require('@vitest/browser-webdriverio/provider');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"declare module 'vite-plus/pack/client' {
-  interface ClientConfig {
-    custom?: boolean;
-  }
-}"#
+            r#"const x = require('vite-plus/test/browser/providers/webdriverio');"#
         );
     }
 
     #[test]
-    fn test_rewrite_declare_module_tsdown_client_double_quotes() {
-        let content = r#"declare module "tsdown/client" {
-  interface ClientConfig {
-    custom?: boolean;
-  }
-}"#;
-
+    fn test_rewrite_require_tsdown() {
+        let content = r#"const { defineConfig } = require('tsdown');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"declare module "vite-plus/pack/client" {
-  interface ClientConfig {
-    custom?: boolean;
-  }
-}"#
-        );
+        assert_eq!(result.content, r#"const { defineConfig } = require('vite-plus/pack');"#);
     }
 
-    // ========================
-    // PeerDependencies Tests
-    // ========================
-
     #[test]
-    fn test_skip_vite_when_peer_dependency() {
-        // When vite is a peerDependency, vite imports should NOT be rewritten
-        let content = r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
-
-export default defineConfig({});"#;
-
-        let skip_packages =
-            SkipPackages { skip_vite: true, skip_vitest: false, skip_tsdown: false };
+    fn test_rewrite_require_tsdown_client() {
+        let content = r#"require('tsdown/client');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"require('vite-plus/pack/client');"#);
+    }
 
-        let result = rewrite_import_content(content, &skip_packages).unwrap();
+    #[test]
+    fn test_rewrite_require_mixed_in_cjs_config() {
+        // Mirrors a realistic `vitest.config.cjs` containing both the config
+        // import and direct test specifier requires.
+        let content = r#"const { defineConfig } = require('vitest/config');
+const vi = require('vitest');
+const { context } = require('@vitest/browser/context');
+const { defineConfig: defineViteConfig } = require('vite');
+const { build } = require('tsdown');
+
+module.exports = defineConfig({});"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        // vite import should NOT be rewritten, vitest import SHOULD be rewritten
         assert_eq!(
             result.content,
-            r#"import { defineConfig } from 'vite';
-import { describe } from 'vite-plus/test';
+            r#"const { defineConfig } = require('vite-plus');
+const vi = require('vite-plus/test');
+const { context } = require('vite-plus/test/browser/context');
+const { defineConfig: defineViteConfig } = require('vite-plus');
+const { build } = require('vite-plus/pack');
 
-export default defineConfig({});"#
+module.exports = defineConfig({});"#
         );
     }
 
+    // -- Negative tests: require-shape rules must NOT match these --
+
     #[test]
-    fn test_skip_vitest_when_peer_dependency() {
-        // When vitest is a peerDependency, vitest imports should NOT be rewritten
-        let content = r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
+    fn test_rewrite_require_does_not_match_custom_require_function() {
+        // `my_require('vitest')` is NOT a literal `require` callee, so the
+        // require-shape rules must leave it alone. (The string `'vitest'` is
+        // also outside an import_statement / module / require call, so the
+        // import-shape rules can't see it either.)
+        let content = r#"const x = my_require('vitest');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-export default defineConfig({});"#;
+    #[test]
+    fn test_rewrite_require_does_not_match_require_cache_index() {
+        // `require.cache['vitest']` is a member access + index — there is no
+        // `require(...)` call here, so the require-shape rules must not match.
+        let content = r#"const x = require.cache['vitest'];"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
+
+    #[test]
+    fn test_rewrite_require_does_not_match_require_resolve() {
+        // `require.resolve('vitest')` is a method call on `require`, not a
+        // bare `require(...)` call — the rule constrains the callee to the
+        // literal identifier `require`, so this is left alone.
+        let content = r#"const x = require.resolve('vitest');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
+    #[test]
+    fn test_rewrite_require_respects_skip_vitest_peer_dependency() {
+        // When vitest is in peerDependencies, the require-shape rule must
+        // also be skipped (parity with the import-shape rule).
+        let content = r#"const vi = require('vitest');
+const { defineConfig } = require('vite');"#;
         let skip_packages =
             SkipPackages { skip_vite: false, skip_vitest: true, skip_tsdown: false };
-
         let result = rewrite_import_content(content, &skip_packages).unwrap();
         assert!(result.updated);
-        // vite import SHOULD be rewritten, vitest import should NOT be rewritten
+        // vitest require is NOT rewritten; vite require IS rewritten.
         assert_eq!(
             result.content,
-            r#"import { defineConfig } from 'vite-plus';
-import { describe } from 'vitest';
-
-export default defineConfig({});"#
+            r#"const vi = require('vitest');
+const { defineConfig } = require('vite-plus');"#
         );
     }
 
-    #[test]
-    fn test_skip_all_when_all_peer_dependencies() {
-        // When all packages are peerDependencies, nothing should be rewritten
-        let content = r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
-import { build } from 'tsdown';
-
-export default defineConfig({});"#;
-
-        let skip_packages = SkipPackages { skip_vite: true, skip_vitest: true, skip_tsdown: true };
+    // ====================================
+    // Reference Types Rewriting Tests
+    // ====================================
 
-        let result = rewrite_import_content(content, &skip_packages).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+    #[test]
+    fn test_rewrite_reference_types_vite_client() {
+        let content = r#"/// <reference types="vite/client" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus/client" />"#);
     }
 
     #[test]
-    fn test_skip_packages_all_skipped() {
-        let skip_all = SkipPackages { skip_vite: true, skip_vitest: true, skip_tsdown: true };
-        assert!(skip_all.all_skipped());
-
-        let skip_some = SkipPackages { skip_vite: true, skip_vitest: false, skip_tsdown: true };
-        assert!(!skip_some.all_skipped());
-
-        let skip_none = SkipPackages::default();
-        assert!(!skip_none.all_skipped());
+    fn test_rewrite_reference_types_vite_client_single_quotes() {
+        let content = r#"/// <reference types='vite/client' />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types='vite-plus/client' />"#);
     }
 
     #[test]
-    fn test_get_skip_packages_from_package_json_with_vite_peer_dep() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
-
-        // Create package.json with vite as peerDependency
-        let pkg_json = r#"{
-  "name": "my-vite-plugin",
-  "peerDependencies": {
-    "vite": "^5.0.0"
-  }
-}"#;
-        let package_json_path = temp.path().join("package.json");
-        fs::write(&package_json_path, pkg_json).unwrap();
-
-        let skip = get_skip_packages_from_package_json(&package_json_path);
-        assert!(skip.skip_vite);
-        assert!(!skip.skip_vitest);
-        assert!(!skip.skip_tsdown);
+    fn test_rewrite_reference_types_bare_vite() {
+        let content = r#"/// <reference types="vite" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus" />"#);
     }
 
     #[test]
-    fn test_get_skip_packages_from_package_json_with_all_peer_deps() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
-
-        let pkg_json = r#"{
-  "name": "my-plugin",
-  "peerDependencies": {
-    "vite": "^5.0.0",
-    "vitest": "^1.0.0",
-    "tsdown": "^1.0.0"
-  }
-}"#;
-        let package_json_path = temp.path().join("package.json");
-        fs::write(&package_json_path, pkg_json).unwrap();
-
-        let skip = get_skip_packages_from_package_json(&package_json_path);
-        assert!(skip.skip_vite);
-        assert!(skip.skip_vitest);
-        assert!(skip.skip_tsdown);
-        assert!(skip.all_skipped());
+    fn test_rewrite_reference_types_bare_vitest() {
+        let content = r#"/// <reference types="vitest" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus/test" />"#);
     }
 
     #[test]
-    fn test_get_skip_packages_from_package_json_with_vite_dependency() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
-
-        // vite in dependencies should also skip rewriting
-        let pkg_json = r#"{
-  "name": "my-app",
-  "dependencies": {
-    "vite": "^5.0.0"
-  }
-}"#;
-        let package_json_path = temp.path().join("package.json");
-        fs::write(&package_json_path, pkg_json).unwrap();
-
-        let skip = get_skip_packages_from_package_json(&package_json_path);
-        assert!(skip.skip_vite); // NOW skips because vite is in dependencies
-        assert!(!skip.skip_vitest);
-        assert!(!skip.skip_tsdown);
+    fn test_rewrite_reference_types_vitest_globals() {
+        let content = r#"/// <reference types="vitest/globals" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus/test/globals" />"#);
     }
 
     #[test]
-    fn test_get_skip_packages_from_package_json_no_file() {
-        let temp = tempdir().unwrap();
-
-        // No package.json created - should return default (no skipping)
-        let package_json_path = temp.path().join("package.json");
-        let skip = get_skip_packages_from_package_json(&package_json_path);
-        assert!(!skip.skip_vite);
-        assert!(!skip.skip_vitest);
-        assert!(!skip.skip_tsdown);
+    fn test_rewrite_reference_types_vitest_config() {
+        let content = r#"/// <reference types="vitest/config" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus" />"#);
     }
 
     #[test]
-    fn test_get_skip_packages_from_package_json_no_deps() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
-
-        // Package with no dependencies at all
-        let pkg_json = r#"{
-  "name": "my-app"
-}"#;
-        let package_json_path = temp.path().join("package.json");
-        fs::write(&package_json_path, pkg_json).unwrap();
-
-        let skip = get_skip_packages_from_package_json(&package_json_path);
-        assert!(!skip.skip_vite);
-        assert!(!skip.skip_vitest);
-        assert!(!skip.skip_tsdown);
+    fn test_rewrite_reference_types_vitest_browser() {
+        let content = r#"/// <reference types="vitest/browser" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus/test/browser" />"#);
     }
 
     #[test]
-    fn test_get_skip_packages_mixed_peer_and_regular_deps() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
-
-        // vite in dependencies, vitest in peerDependencies
-        let pkg_json = r#"{
-  "name": "my-package",
-  "dependencies": {
-    "vite": "^5.0.0"
-  },
-  "peerDependencies": {
-    "vitest": "^1.0.0"
-  }
-}"#;
-        let package_json_path = temp.path().join("package.json");
-        fs::write(&package_json_path, pkg_json).unwrap();
-
-        let skip = get_skip_packages_from_package_json(&package_json_path);
-        assert!(skip.skip_vite); // in dependencies
-        assert!(skip.skip_vitest); // in peerDependencies
-        assert!(!skip.skip_tsdown);
+    fn test_rewrite_reference_types_vitest_scoped_browser_flat_subpaths() {
+        // `@vitest/browser/{client,locators,matchers,utils}` are exposed at the
+        // *bare* `vite-plus/test/{name}` surface — the `/browser/` segment is stripped.
+        for (sub, expected) in [
+            ("client", "vite-plus/test/client"),
+            ("locators", "vite-plus/test/locators"),
+            ("matchers", "vite-plus/test/matchers"),
+            ("utils", "vite-plus/test/utils"),
+        ] {
+            let content = format!(r#"/// <reference types="@vitest/browser/{sub}" />"#);
+            let result = rewrite_import_content(&content, &SkipPackages::default()).unwrap();
+            assert!(result.updated, "@vitest/browser/{sub} reference should be rewritten");
+            assert_eq!(result.content, format!(r#"/// <reference types="{expected}" />"#));
+        }
     }
 
     #[test]
-    fn test_rewrite_imports_in_directory_with_vite_dependency() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
-
-        // Create package.json with vite as dependency (not peerDependency)
-        let pkg_json = r#"{
-  "name": "my-app",
-  "dependencies": {
-    "vite": "^5.0.0"
-  }
-}"#;
-        fs::write(temp.path().join("package.json"), pkg_json).unwrap();
-
-        // Create src directory
-        fs::create_dir(temp.path().join("src")).unwrap();
-
-        // Create source file with vite and vitest imports
-        let original_content = r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
-
-export default defineConfig({});"#;
-        fs::write(temp.path().join("src/index.ts"), original_content).unwrap();
-
-        // Run the batch rewrite
-        let result = rewrite_imports_in_directory(temp.path()).unwrap();
-
-        // File should be modified (vitest was rewritten)
-        assert_eq!(result.modified_files.len(), 1);
-        assert!(result.errors.is_empty());
+    fn test_rewrite_reference_types_vitest_scoped_browser_context() {
+        let content = r#"/// <reference types="@vitest/browser/context" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus/test/browser/context" />"#);
+    }
 
-        // Verify vite import NOT rewritten (in dependencies), vitest IS rewritten
-        let content = fs::read_to_string(temp.path().join("src/index.ts")).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_vitest_scoped_browser_playwright() {
+        let content = r#"/// <reference types="@vitest/browser/providers/playwright" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
         assert_eq!(
-            content,
-            r#"import { defineConfig } from 'vite';
-import { describe } from 'vite-plus/test';
+            result.content,
+            r#"/// <reference types="vite-plus/test/browser/providers/playwright" />"#
+        );
+    }
 
-export default defineConfig({});"#
+    #[test]
+    fn test_rewrite_reference_types_vitest_scoped_browser_playwright_pkg() {
+        let content = r#"/// <reference types="@vitest/browser-playwright" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"/// <reference types="vite-plus/test/browser-playwright" />"#
         );
     }
 
     #[test]
-    fn test_rewrite_imports_in_directory_with_peer_deps() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
+    fn test_rewrite_reference_types_vitest_scoped_browser_webdriverio() {
+        let content = r#"/// <reference types="@vitest/browser/providers/webdriverio" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"/// <reference types="vite-plus/test/browser/providers/webdriverio" />"#
+        );
+    }
 
-        // Create package.json with vite as peerDependency
-        let pkg_json = r#"{
-  "name": "my-vite-plugin",
-  "peerDependencies": {
-    "vite": "^5.0.0"
-  }
-}"#;
-        fs::write(temp.path().join("package.json"), pkg_json).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_vitest_scoped_provider_context() {
+        // `@vitest/browser-{provider}/context` references map onto the shared
+        // `vite-plus/test/browser/context` export (the provider segment is dropped).
+        for provider in ["playwright", "preview", "webdriverio"] {
+            let content =
+                format!(r#"/// <reference types="@vitest/browser-{provider}/context" />"#);
+            let result = rewrite_import_content(&content, &SkipPackages::default()).unwrap();
+            assert!(result.updated, "@vitest/browser-{provider}/context should be rewritten");
+            assert_eq!(
+                result.content,
+                r#"/// <reference types="vite-plus/test/browser/context" />"#
+            );
+        }
+    }
 
-        // Create src directory
-        fs::create_dir(temp.path().join("src")).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_vitest_scoped_provider_entry() {
+        // `@vitest/browser-{provider}/provider` references map to the provider
+        // entry point under `vite-plus/test/browser/providers/{provider}`.
+        for provider in ["playwright", "preview", "webdriverio"] {
+            let content =
+                format!(r#"/// <reference types="@vitest/browser-{provider}/provider" />"#);
+            let result = rewrite_import_content(&content, &SkipPackages::default()).unwrap();
+            assert!(result.updated, "@vitest/browser-{provider}/provider should be rewritten");
+            assert_eq!(
+                result.content,
+                format!(r#"/// <reference types="vite-plus/test/browser/providers/{provider}" />"#)
+            );
+        }
+    }
 
-        // Create source file with vite and vitest imports
-        let original_content = r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
+    #[test]
+    fn test_rewrite_reference_types_tsdown_client_rewritten() {
+        // tsdown/client should be rewritten to vite-plus/pack/client
+        let content = r#"/// <reference types="tsdown/client" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus/pack/client" />"#);
+    }
 
-export default defineConfig({});"#;
-        fs::write(temp.path().join("src/index.ts"), original_content).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_vitest_scoped_not_matching() {
+        // Non-enumerated @vitest/* packages should NOT be rewritten
+        let content = r#"/// <reference types="@vitest/coverage-v8" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // Run the batch rewrite
-        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_inside_template_literal_not_rewritten() {
+        // Reference-like content inside template literals should NOT be rewritten.
+        // The preamble ends at the first non-comment line (`const`), so nothing is processed.
+        let content = r#"const template = `
+/// <reference types="vite/client" />
+`;"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // File should be modified (vitest was rewritten)
-        assert_eq!(result.modified_files.len(), 1);
-        assert!(result.errors.is_empty());
+    #[test]
+    fn test_rewrite_reference_types_preamble_only() {
+        // Only references in the preamble (before first statement) should be rewritten
+        let content = r#"/// <reference types="vite/client" />
+// A regular comment
 
-        // Verify vite import NOT rewritten, vitest IS rewritten
-        let content = fs::read_to_string(temp.path().join("src/index.ts")).unwrap();
+const x = 1;
+/// <reference types="vitest" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        // First reference (preamble) is rewritten; last one (after code) is not
         assert_eq!(
-            content,
-            r#"import { defineConfig } from 'vite';
-import { describe } from 'vite-plus/test';
+            result.content,
+            r#"/// <reference types="vite-plus/client" />
+// A regular comment
 
-export default defineConfig({});"#
+const x = 1;
+/// <reference types="vitest" />"#
         );
     }
 
     #[test]
-    fn test_rewrite_imports_skips_file_when_all_peer_deps() {
-        use std::fs;
-
-        let temp = tempdir().unwrap();
-
-        // Create package.json with all packages as peerDependencies
-        let pkg_json = r#"{
-  "name": "my-plugin",
-  "peerDependencies": {
-    "vite": "^5.0.0",
-    "vitest": "^1.0.0",
-    "tsdown": "^1.0.0"
-  }
-}"#;
-        fs::write(temp.path().join("package.json"), pkg_json).unwrap();
+    fn test_rewrite_reference_types_after_block_comment() {
+        // Block comments (/* ... */) should not end the preamble scan
+        let content = "/* License: MIT */\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "/* License: MIT */\n/// <reference types=\"vite-plus/client\" />\n"
+        );
+    }
 
-        // Create source file
-        let original_content = r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
-import { build } from 'tsdown';"#;
-        fs::write(temp.path().join("index.ts"), original_content).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_after_multiline_block_comment() {
+        // Multi-line block comments should be skipped entirely
+        let content =
+            "/*\n * License header\n * Copyright 2024\n */\n/// <reference types=\"vitest\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "/*\n * License header\n * Copyright 2024\n */\n/// <reference types=\"vite-plus/test\" />\n"
+        );
+    }
 
-        // Run the batch rewrite
-        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_block_comment_with_trailing_code() {
+        // A single-line block comment followed by code should end the preamble
+        let content = "/* banner */ const x = 1;\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // File should be unchanged (all skipped)
-        assert!(result.modified_files.is_empty());
-        assert_eq!(result.unchanged_files.len(), 1);
+    #[test]
+    fn test_rewrite_reference_types_block_comment_with_trailing_comment() {
+        // A block comment followed by a line comment is still preamble
+        let content = "/* banner */ // generated\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "/* banner */ // generated\n/// <reference types=\"vite-plus/client\" />\n"
+        );
+    }
 
-        // Verify content unchanged
-        let content = fs::read_to_string(temp.path().join("index.ts")).unwrap();
-        assert_eq!(content, original_content);
+    #[test]
+    fn test_rewrite_reference_types_multiline_block_comment_closes_into_code() {
+        // Multi-line block comment closing line has code after */ — end of preamble
+        let content = "/*\n * License\n */ const x = 1;\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_find_nearest_package_json() {
-        use std::fs;
+    fn test_rewrite_reference_types_multiline_block_comment_closes_into_comment() {
+        // Multi-line block comment closing line has only a comment after */ — still preamble
+        let content = "/*\n * License\n */ // end\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "/*\n * License\n */ // end\n/// <reference types=\"vite-plus/client\" />\n"
+        );
+    }
 
-        let temp = tempdir().unwrap();
+    #[test]
+    fn test_rewrite_reference_types_block_close_into_new_block_comment() {
+        // `/* a */ /* b` closes first comment then opens a new multi-line one
+        let content = "/* a */ /* b\n * still going */\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "/* a */ /* b\n * still going */\n/// <reference types=\"vite-plus/client\" />\n"
+        );
+    }
 
-        // Create monorepo structure
-        fs::create_dir_all(temp.path().join("packages/vite-plugin/src")).unwrap();
-        fs::create_dir_all(temp.path().join("packages/app/src")).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_multiple_inline_block_comments_then_code() {
+        // `/* a */ /* b */ const x = 1;` — code after two closed block comments ends preamble
+        let content = "/* a */ /* b */ const x = 1;\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // Root package.json (no peerDeps)
-        fs::write(temp.path().join("package.json"), r#"{"name": "monorepo"}"#).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_multiple_inline_block_comments_no_code() {
+        // `/* a */ /* b */` — only block comments, no trailing code, preamble continues
+        let content = "/* a */ /* b */\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "/* a */ /* b */\n/// <reference types=\"vite-plus/client\" />\n"
+        );
+    }
 
-        // vite-plugin package.json (has vite in peerDeps)
-        fs::write(
-            temp.path().join("packages/vite-plugin/package.json"),
-            r#"{"name": "vite-plugin", "peerDependencies": {"vite": "^5.0.0"}}"#,
-        )
-        .unwrap();
+    #[test]
+    fn test_rewrite_reference_types_vitest_browser_providers_playwright() {
+        // @vitest/browser/providers/playwright → vite-plus/test/browser/providers/playwright
+        let content = r#"/// <reference types="@vitest/browser/providers/playwright" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"/// <reference types="vite-plus/test/browser/providers/playwright" />"#
+        );
+    }
 
-        // app package.json (no peerDeps)
-        fs::write(temp.path().join("packages/app/package.json"), r#"{"name": "app"}"#).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_crlf() {
+        // CRLF line endings should be preserved
+        let content =
+            "/// <reference types=\"vite/client\" />\r\n/// <reference types=\"vitest\" />\r\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "/// <reference types=\"vite-plus/client\" />\r\n/// <reference types=\"vite-plus/test\" />\r\n"
+        );
+    }
 
-        // Test finding package.json from vite-plugin/src/index.ts
-        let file_path = temp.path().join("packages/vite-plugin/src/index.ts");
-        let result = find_nearest_package_json(&file_path, temp.path());
-        assert_eq!(result, Some(temp.path().join("packages/vite-plugin/package.json")));
+    #[test]
+    fn test_rewrite_reference_types_crlf_with_block_comment() {
+        // CRLF + block comment header
+        let content =
+            "/* License */\r\n/// <reference types=\"vite/client\" />\r\nconst x = 1;\r\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "/* License */\r\n/// <reference types=\"vite-plus/client\" />\r\nconst x = 1;\r\n"
+        );
+    }
 
-        // Test finding package.json from app/src/index.ts
-        let file_path = temp.path().join("packages/app/src/index.ts");
-        let result = find_nearest_package_json(&file_path, temp.path());
-        assert_eq!(result, Some(temp.path().join("packages/app/package.json")));
+    #[test]
+    fn test_rewrite_reference_types_no_space_after_slashes() {
+        // TypeScript accepts ///<reference without a space
+        let content = r#"///<reference types="vite/client" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"///<reference types="vite-plus/client" />"#);
+    }
 
-        // Test finding package.json from root level file
-        let file_path = temp.path().join("vite.config.ts");
-        let result = find_nearest_package_json(&file_path, temp.path());
-        assert_eq!(result, Some(temp.path().join("package.json")));
+    #[test]
+    fn test_rewrite_reference_types_tab_after_slashes() {
+        // TypeScript accepts ///\t<reference with a tab
+        let content = "///\t<reference types=\"vite/client\" />";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, "///\t<reference types=\"vite-plus/client\" />");
     }
 
     #[test]
-    fn test_rewrite_imports_monorepo_different_peer_deps() {
-        use std::fs;
+    fn test_rewrite_reference_types_after_shebang() {
+        // Shebang lines should not end the preamble scan
+        let content = "#!/usr/bin/env node\n/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            "#!/usr/bin/env node\n/// <reference types=\"vite-plus/client\" />\n"
+        );
+    }
 
-        let temp = tempdir().unwrap();
+    #[test]
+    fn test_rewrite_reference_types_after_bom() {
+        // UTF-8 BOM should not end the preamble scan; BOM is stripped during rewrite
+        let content = "\u{feff}/// <reference types=\"vite/client\" />\n";
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, "/// <reference types=\"vite-plus/client\" />\n");
+    }
 
-        // Create monorepo structure
-        fs::create_dir_all(temp.path().join("packages/vite-plugin/src")).unwrap();
-        fs::create_dir_all(temp.path().join("packages/app/src")).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_bare_tsdown() {
+        let content = r#"/// <reference types="tsdown" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"/// <reference types="vite-plus/pack" />"#);
+    }
 
-        // Root package.json (no peerDeps)
-        fs::write(temp.path().join("package.json"), r#"{"name": "monorepo"}"#).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_already_migrated() {
+        let content = r#"/// <reference types="vite-plus/client" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // vite-plugin package.json (has vite in peerDeps)
-        fs::write(
-            temp.path().join("packages/vite-plugin/package.json"),
-            r#"{"name": "vite-plugin", "peerDependencies": {"vite": "^5.0.0"}}"#,
-        )
-        .unwrap();
+    #[test]
+    fn test_rewrite_reference_types_preserves_non_matching() {
+        let content = r#"/// <reference types="node" />
+/// <reference lib="es2015" />
+/// <reference path="./types.d.ts" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
-        // app package.json (no peerDeps)
-        fs::write(temp.path().join("packages/app/package.json"), r#"{"name": "app"}"#).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_with_leading_whitespace() {
+        let content = r#"  /// <reference types="vite/client" />"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"  /// <reference types="vite-plus/client" />"#);
+    }
 
-        // vite-plugin source file with vite and vitest imports
-        fs::write(
-            temp.path().join("packages/vite-plugin/src/index.ts"),
-            r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
-export default defineConfig({});"#,
-        )
-        .unwrap();
+    #[test]
+    fn test_rewrite_reference_types_env_d_ts_style() {
+        let content = r#"/// <reference types="vite/client" />
+/// <reference types="vitest" />
+/// <reference types="vitest/globals" />
+/// <reference types="vitest/config" />
+/// <reference types="@vitest/browser/context" />"#;
 
-        // app source file with vite and vitest imports
-        fs::write(
-            temp.path().join("packages/app/src/index.ts"),
-            r#"import { defineConfig } from 'vite';
-import { describe } from 'vitest';
-export default defineConfig({});"#,
-        )
-        .unwrap();
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"/// <reference types="vite-plus/client" />
+/// <reference types="vite-plus/test" />
+/// <reference types="vite-plus/test/globals" />
+/// <reference types="vite-plus" />
+/// <reference types="vite-plus/test/browser/context" />"#
+        );
+    }
 
-        // Run the batch rewrite
-        let result = rewrite_imports_in_directory(temp.path()).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_mixed_with_imports() {
+        let content = r#"/// <reference types="vite/client" />
+import { defineConfig } from 'vite';
 
-        // Both files should be modified
-        assert_eq!(result.modified_files.len(), 2);
+export default defineConfig({});"#;
 
-        // vite-plugin: vite NOT rewritten (has peerDep), vitest IS rewritten
-        let vite_plugin_content =
-            fs::read_to_string(temp.path().join("packages/vite-plugin/src/index.ts")).unwrap();
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
         assert_eq!(
-            vite_plugin_content,
-            r#"import { defineConfig } from 'vite';
-import { describe } from 'vite-plus/test';
+            result.content,
+            r#"/// <reference types="vite-plus/client" />
+import { defineConfig } from 'vite-plus';
+
 export default defineConfig({});"#
         );
+    }
 
-        // app: vite IS rewritten (no peerDep), vitest IS rewritten
-        let app_content =
-            fs::read_to_string(temp.path().join("packages/app/src/index.ts")).unwrap();
+    #[test]
+    fn test_rewrite_reference_types_skip_vite() {
+        let content = r#"/// <reference types="vite/client" />
+/// <reference types="vitest" />"#;
+
+        let skip_packages =
+            SkipPackages { skip_vite: true, skip_vitest: false, skip_tsdown: false };
+        let result = rewrite_import_content(content, &skip_packages).unwrap();
+        assert!(result.updated);
         assert_eq!(
-            app_content,
-            r#"import { defineConfig } from 'vite-plus';
-import { describe } from 'vite-plus/test';
-export default defineConfig({});"#
+            result.content,
+            r#"/// <reference types="vite/client" />
+/// <reference types="vite-plus/test" />"#
         );
     }
 
-    // ====================================
-    // Reference Types Rewriting Tests
-    // ====================================
-
     #[test]
-    fn test_rewrite_reference_types_vite_client() {
-        let content = r#"/// <reference types="vite/client" />"#;
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+    fn test_rewrite_reference_types_skip_vitest() {
+        let content = r#"/// <reference types="vite/client" />
+/// <reference types="vitest" />
+/// <reference types="@vitest/browser/matchers" />"#;
+
+        let skip_packages =
+            SkipPackages { skip_vite: false, skip_vitest: true, skip_tsdown: false };
+        let result = rewrite_import_content(content, &skip_packages).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus/client" />"#);
+        assert_eq!(
+            result.content,
+            r#"/// <reference types="vite-plus/client" />
+/// <reference types="vitest" />
+/// <reference types="@vitest/browser/matchers" />"#
+        );
     }
 
     #[test]
-    fn test_rewrite_reference_types_vite_client_single_quotes() {
-        let content = r#"/// <reference types='vite/client' />"#;
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+    fn test_rewrite_reference_types_skip_tsdown() {
+        let content = r#"/// <reference types="tsdown/client" />
+/// <reference types="vite/client" />"#;
+
+        let skip_packages =
+            SkipPackages { skip_vite: false, skip_vitest: false, skip_tsdown: true };
+        let result = rewrite_import_content(content, &skip_packages).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types='vite-plus/client' />"#);
+        assert_eq!(
+            result.content,
+            r#"/// <reference types="tsdown/client" />
+/// <reference types="vite-plus/client" />"#
+        );
     }
 
     #[test]
-    fn test_rewrite_reference_types_bare_vite() {
-        let content = r#"/// <reference types="vite" />"#;
+    fn test_rewrite_reference_types_skip_all() {
+        let content = r#"/// <reference types="vite/client" />
+/// <reference types="vitest" />
+/// <reference types="tsdown/client" />"#;
+
+        let skip_packages = SkipPackages { skip_vite: true, skip_vitest: true, skip_tsdown: true };
+        let result = rewrite_import_content(content, &skip_packages).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
+
+    // ====================================
+    // Re-export (`export ... from '...'`) Rewriting Tests
+    // ====================================
+    //
+    // These mirror the `import_statement` rewrites for the `export_statement`
+    // tree-sitter kind, which covers `export { x } from 'mod'`,
+    // `export * from 'mod'`, `export * as ns from 'mod'`, and
+    // `export type { x } from 'mod'`. Without these sibling rules, projects
+    // that re-export from `@vitest/browser-playwright` (or the other browser
+    // provider packages that `rewritePackageJson()` removes from package.json)
+    // would silently end up with `ERR_PACKAGE_PATH_NOT_EXPORTED` at runtime.
+
+    #[test]
+    fn test_rewrite_export_named_from_vite() {
+        let content = r#"export { defineConfig } from 'vite';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus" />"#);
+        assert_eq!(result.content, r#"export { defineConfig } from 'vite-plus';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_bare_vitest() {
-        let content = r#"/// <reference types="vitest" />"#;
+    fn test_rewrite_export_named_from_tsdown() {
+        let content = r#"export { defineConfig } from 'tsdown';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus/test" />"#);
+        assert_eq!(result.content, r#"export { defineConfig } from 'vite-plus/pack';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_globals() {
-        let content = r#"/// <reference types="vitest/globals" />"#;
+    fn test_rewrite_export_named_from_vitest_browser_context() {
+        // `export { page } from '@vitest/browser/context'` — the regression
+        // case called out in the chatgpt-codex review.
+        let content = r#"export { page } from '@vitest/browser/context';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus/test/globals" />"#);
+        assert_eq!(result.content, r#"export { page } from 'vite-plus/test/browser/context';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_config() {
-        let content = r#"/// <reference types="vitest/config" />"#;
+    fn test_rewrite_export_star_from_vitest_browser_playwright() {
+        // `export * from '@vitest/browser-playwright'` — the package is dropped
+        // from package.json by `rewritePackageJson()`, so it MUST be rewritten
+        // to avoid `ERR_PACKAGE_PATH_NOT_EXPORTED` under strict pnpm/PnP.
+        let content = r#"export * from '@vitest/browser-playwright';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus" />"#);
+        assert_eq!(result.content, r#"export * from 'vite-plus/test/browser-playwright';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_browser() {
-        let content = r#"/// <reference types="vitest/browser" />"#;
+    fn test_rewrite_export_star_as_ns_from_vitest() {
+        // `export * as ns from 'vitest'` (namespace re-export).
+        let content = r#"export * as vi from 'vitest';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus/test/browser" />"#);
+        assert_eq!(result.content, r#"export * as vi from 'vite-plus/test';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_scoped_browser_matchers_not_rewritten() {
-        // @vitest/browser/matchers is NOT exported by vite-plus — should not be rewritten
-        let content = r#"/// <reference types="@vitest/browser/matchers" />"#;
+    fn test_rewrite_export_type_from_vitest_config() {
+        // `export type { Config } from 'vitest/config'` — type-only re-export.
+        // `vitest/config` collapses to bare `vite-plus` (matches the
+        // import-shape rule).
+        let content = r#"export type { Config } from 'vitest/config';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+        assert!(result.updated);
+        assert_eq!(result.content, r#"export type { Config } from 'vite-plus';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_scoped_browser_context() {
-        let content = r#"/// <reference types="@vitest/browser/context" />"#;
+    fn test_rewrite_export_named_from_vitest_browser_preview() {
+        let content = r#"export { preview } from "@vitest/browser-preview";"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus/test/browser/context" />"#);
+        assert_eq!(result.content, r#"export { preview } from "vite-plus/test/browser-preview";"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_scoped_browser_playwright() {
-        let content = r#"/// <reference types="@vitest/browser/providers/playwright" />"#;
+    fn test_rewrite_export_star_from_vitest_browser_webdriverio_provider() {
+        // `@vitest/browser-{provider}/provider` maps onto the shared
+        // `vite-plus/test/browser/providers/{provider}` entry point.
+        let content = r#"export * from '@vitest/browser-webdriverio/provider';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            r#"/// <reference types="vite-plus/test/browser/providers/playwright" />"#
+            r#"export * from 'vite-plus/test/browser/providers/webdriverio';"#
         );
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_scoped_browser_playwright_pkg() {
-        let content = r#"/// <reference types="@vitest/browser-playwright" />"#;
+    fn test_rewrite_export_named_from_vite_subpath() {
+        let content = r#"export { ModuleRunner } from 'vite/module-runner';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="vite-plus/test/browser-playwright" />"#
-        );
+        assert_eq!(result.content, r#"export { ModuleRunner } from 'vite-plus/module-runner';"#);
+    }
+
+    #[test]
+    fn test_rewrite_export_value_with_vitest_string_literal_not_rewritten() {
+        // A plain value declaration whose initializer is the string `'vitest'`
+        // is NOT a re-export — the string literal is outside any
+        // import_statement / export_statement / require call and must be left
+        // alone.
+        let content = r#"export const foo = 'vitest';"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
+    // =========================================
+    // Dynamic import() and TS import-type tests
+    // =========================================
+
     #[test]
-    fn test_rewrite_reference_types_vitest_scoped_browser_webdriverio() {
-        let content = r#"/// <reference types="@vitest/browser/providers/webdriverio" />"#;
+    fn test_rewrite_dynamic_import_vite() {
+        let content = r#"const m = await import('vite');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="vite-plus/test/browser/providers/webdriverio" />"#
-        );
+        assert_eq!(result.content, r#"const m = await import('vite-plus');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_tsdown_client_rewritten() {
-        // tsdown/client should be rewritten to vite-plus/pack/client
-        let content = r#"/// <reference types="tsdown/client" />"#;
+    fn test_rewrite_dynamic_import_vite_subpath() {
+        let content = r#"const m = await import("vite/module-runner");"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus/pack/client" />"#);
+        assert_eq!(result.content, r#"const m = await import("vite-plus/module-runner");"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_scoped_not_matching() {
-        // Non-enumerated @vitest/* packages should NOT be rewritten
-        let content = r#"/// <reference types="@vitest/coverage-v8" />"#;
+    fn test_rewrite_dynamic_import_vitest() {
+        let content = r#"const m = await import('vitest');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const m = await import('vite-plus/test');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_inside_template_literal_not_rewritten() {
-        // Reference-like content inside template literals should NOT be rewritten.
-        // The preamble ends at the first non-comment line (`const`), so nothing is processed.
-        let content = r#"const template = `
-/// <reference types="vite/client" />
-`;"#;
+    fn test_rewrite_dynamic_import_vitest_config() {
+        let content = r#"const m = await import('vitest/config');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const m = await import('vite-plus');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_preamble_only() {
-        // Only references in the preamble (before first statement) should be rewritten
-        let content = r#"/// <reference types="vite/client" />
-// A regular comment
-
-const x = 1;
-/// <reference types="vitest" />"#;
+    fn test_rewrite_dynamic_import_vitest_subpath() {
+        let content = r#"const m = await import('vitest/node');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        // First reference (preamble) is rewritten; last one (after code) is not
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="vite-plus/client" />
-// A regular comment
-
-const x = 1;
-/// <reference types="vitest" />"#
-        );
+        assert_eq!(result.content, r#"const m = await import('vite-plus/test/node');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_after_block_comment() {
-        // Block comments (/* ... */) should not end the preamble scan
-        let content = "/* License: MIT */\n/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_dynamic_import_vitest_browser() {
+        let content = r#"const provider = await import('@vitest/browser');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            "/* License: MIT */\n/// <reference types=\"vite-plus/client\" />\n"
-        );
+        assert_eq!(result.content, r#"const provider = await import('vite-plus/test/browser');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_after_multiline_block_comment() {
-        // Multi-line block comments should be skipped entirely
-        let content =
-            "/*\n * License header\n * Copyright 2024\n */\n/// <reference types=\"vitest\" />\n";
+    fn test_rewrite_dynamic_import_vitest_browser_context() {
+        let content = r#"const ctx = await import('@vitest/browser/context');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            "/*\n * License header\n * Copyright 2024\n */\n/// <reference types=\"vite-plus/test\" />\n"
+            r#"const ctx = await import('vite-plus/test/browser/context');"#
         );
     }
 
     #[test]
-    fn test_rewrite_reference_types_block_comment_with_trailing_code() {
-        // A single-line block comment followed by code should end the preamble
-        let content = "/* banner */ const x = 1;\n/// <reference types=\"vite/client\" />\n";
-        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+    fn test_rewrite_dynamic_import_vitest_browser_flat_subpath() {
+        // `@vitest/browser/{client,locators,matchers,utils}` strips the
+        // `/browser/` segment, matching the static-import rule.
+        for (sub, expected) in [
+            ("client", "vite-plus/test/client"),
+            ("locators", "vite-plus/test/locators"),
+            ("matchers", "vite-plus/test/matchers"),
+            ("utils", "vite-plus/test/utils"),
+        ] {
+            let content = format!("const x = await import('@vitest/browser/{sub}');");
+            let result = rewrite_import_content(&content, &SkipPackages::default()).unwrap();
+            assert!(result.updated, "dynamic import of @vitest/browser/{sub} should be rewritten");
+            assert_eq!(result.content, format!("const x = await import('{expected}');"));
+        }
     }
 
     #[test]
-    fn test_rewrite_reference_types_block_comment_with_trailing_comment() {
-        // A block comment followed by a line comment is still preamble
-        let content = "/* banner */ // generated\n/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_dynamic_import_vitest_browser_playwright() {
+        let content = r#"const p = await import('@vitest/browser-playwright');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            "/* banner */ // generated\n/// <reference types=\"vite-plus/client\" />\n"
+            r#"const p = await import('vite-plus/test/browser-playwright');"#
         );
     }
 
     #[test]
-    fn test_rewrite_reference_types_multiline_block_comment_closes_into_code() {
-        // Multi-line block comment closing line has code after */ — end of preamble
-        let content = "/*\n * License\n */ const x = 1;\n/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_dynamic_import_vitest_browser_playwright_context() {
+        let content = r#"const p = await import('@vitest/browser-playwright/context');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const p = await import('vite-plus/test/browser/context');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_multiline_block_comment_closes_into_comment() {
-        // Multi-line block comment closing line has only a comment after */ — still preamble
-        let content = "/*\n * License\n */ // end\n/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_dynamic_import_vitest_browser_playwright_provider() {
+        let content = r#"const p = await import('@vitest/browser-playwright/provider');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            "/*\n * License\n */ // end\n/// <reference types=\"vite-plus/client\" />\n"
+            r#"const p = await import('vite-plus/test/browser/providers/playwright');"#
         );
     }
 
     #[test]
-    fn test_rewrite_reference_types_block_close_into_new_block_comment() {
-        // `/* a */ /* b` closes first comment then opens a new multi-line one
-        let content = "/* a */ /* b\n * still going */\n/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_dynamic_import_vitest_browser_preview_provider() {
+        let content = r#"const p = await import('@vitest/browser-preview/provider');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            "/* a */ /* b\n * still going */\n/// <reference types=\"vite-plus/client\" />\n"
+            r#"const p = await import('vite-plus/test/browser/providers/preview');"#
         );
     }
 
     #[test]
-    fn test_rewrite_reference_types_multiple_inline_block_comments_then_code() {
-        // `/* a */ /* b */ const x = 1;` — code after two closed block comments ends preamble
-        let content = "/* a */ /* b */ const x = 1;\n/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_dynamic_import_vitest_browser_webdriverio_provider() {
+        let content = r#"const p = await import('@vitest/browser-webdriverio/provider');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+        assert!(result.updated);
+        assert_eq!(
+            result.content,
+            r#"const p = await import('vite-plus/test/browser/providers/webdriverio');"#
+        );
     }
 
     #[test]
-    fn test_rewrite_reference_types_multiple_inline_block_comments_no_code() {
-        // `/* a */ /* b */` — only block comments, no trailing code, preamble continues
-        let content = "/* a */ /* b */\n/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_dynamic_import_tsdown() {
+        let content = r#"const m = await import('tsdown');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            "/* a */ /* b */\n/// <reference types=\"vite-plus/client\" />\n"
-        );
+        assert_eq!(result.content, r#"const m = await import('vite-plus/pack');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_vitest_browser_providers_playwright() {
-        // @vitest/browser/providers/playwright → vite-plus/test/browser/providers/playwright
-        let content = r#"/// <reference types="@vitest/browser/providers/playwright" />"#;
+    fn test_rewrite_dynamic_import_tsdown_client() {
+        let content = r#"const m = await import('tsdown/client');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="vite-plus/test/browser/providers/playwright" />"#
-        );
+        assert_eq!(result.content, r#"const m = await import('vite-plus/pack/client');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_crlf() {
-        // CRLF line endings should be preserved
-        let content =
-            "/// <reference types=\"vite/client\" />\r\n/// <reference types=\"vitest\" />\r\n";
+    fn test_rewrite_ts_import_type_vitest_browser_context() {
+        // TS type-position `typeof import('@vitest/browser/context')` is also
+        // a `call_expression` with the special `import` token as callee, so
+        // the same rule covers it.
+        let content = r#"type C = typeof import('@vitest/browser/context');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            "/// <reference types=\"vite-plus/client\" />\r\n/// <reference types=\"vite-plus/test\" />\r\n"
-        );
+        assert_eq!(result.content, r#"type C = typeof import('vite-plus/test/browser/context');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_crlf_with_block_comment() {
-        // CRLF + block comment header
-        let content =
-            "/* License */\r\n/// <reference types=\"vite/client\" />\r\nconst x = 1;\r\n";
+    fn test_rewrite_ts_import_type_vitest_browser_playwright() {
+        let content = r#"type P = typeof import('@vitest/browser-playwright');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            "/* License */\r\n/// <reference types=\"vite-plus/client\" />\r\nconst x = 1;\r\n"
+            r#"type P = typeof import('vite-plus/test/browser-playwright');"#
         );
     }
 
     #[test]
-    fn test_rewrite_reference_types_no_space_after_slashes() {
-        // TypeScript accepts ///<reference without a space
-        let content = r#"///<reference types="vite/client" />"#;
+    fn test_rewrite_dynamic_import_does_not_touch_string_literal_args() {
+        // A string literal that *contains* an `import('@vitest/browser')`-looking
+        // payload must NOT be rewritten — it is not a real dynamic import.
+        let content = r#"const x = "import('@vitest/browser')";"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(result.content, r#"///<reference types="vite-plus/client" />"#);
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_reference_types_tab_after_slashes() {
-        // TypeScript accepts ///\t<reference with a tab
-        let content = "///\t<reference types=\"vite/client\" />";
+    fn test_rewrite_dynamic_import_does_not_touch_member_call_named_import() {
+        // A property-access call like `obj.import('@vitest/browser')` is a
+        // user-defined function, not a dynamic import expression. Its callee
+        // node-kind is `member_expression`, not `import`, so the rule must
+        // not match.
+        let content = r#"const p = obj.import('@vitest/browser');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(result.content, "///\t<reference types=\"vite-plus/client\" />");
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_reference_types_after_shebang() {
-        // Shebang lines should not end the preamble scan
-        let content = "#!/usr/bin/env node\n/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_dynamic_import_mixed_with_static_imports() {
+        // Verifies a mixed file with static, dynamic, and TS-type imports.
+        let content = r#"import { describe } from 'vitest';
+const browser = await import('@vitest/browser');
+type C = typeof import('@vitest/browser/context');
+const provider = await import('@vitest/browser-playwright');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
         assert_eq!(
             result.content,
-            "#!/usr/bin/env node\n/// <reference types=\"vite-plus/client\" />\n"
+            r#"import { describe } from 'vite-plus/test';
+const browser = await import('vite-plus/test/browser');
+type C = typeof import('vite-plus/test/browser/context');
+const provider = await import('vite-plus/test/browser-playwright');"#
         );
     }
 
+    // `vitest/package.json` is a metadata-access pattern (typically used to
+    // read the vitest version). Rewriting it to `vite-plus/test/package.json`
+    // would fail at runtime with `ERR_PACKAGE_PATH_NOT_EXPORTED` because
+    // `syncTestPackageExports()` deliberately skips the upstream
+    // `./package.json` export. The catch-all `vitest/*` subpath rules MUST
+    // leave it alone so the original specifier still resolves through the
+    // transitively-installed `vitest` dependency.
+
     #[test]
-    fn test_rewrite_reference_types_after_bom() {
-        // UTF-8 BOM should not end the preamble scan; BOM is stripped during rewrite
-        let content = "\u{feff}/// <reference types=\"vite/client\" />\n";
+    fn test_rewrite_import_content_vitest_package_json_static_import() {
+        let content = r#"import pkg from 'vitest/package.json';
+
+console.log(pkg.version);"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(result.content, "/// <reference types=\"vite-plus/client\" />\n");
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_reference_types_bare_tsdown() {
-        let content = r#"/// <reference types="tsdown" />"#;
+    fn test_rewrite_import_content_vitest_package_json_static_import_double_quotes() {
+        let content = r#"import pkg from "vitest/package.json";
+
+console.log(pkg.version);"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(result.content, r#"/// <reference types="vite-plus/pack" />"#);
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_reference_types_already_migrated() {
-        let content = r#"/// <reference types="vite-plus/client" />"#;
+    fn test_rewrite_import_content_vitest_package_json_require() {
+        let content = r#"const pkg = require('vitest/package.json');
+
+console.log(pkg.version);"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(!result.updated);
         assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_reference_types_preserves_non_matching() {
-        let content = r#"/// <reference types="node" />
-/// <reference lib="es2015" />
-/// <reference path="./types.d.ts" />"#;
+    fn test_rewrite_import_content_vitest_package_json_dynamic_import() {
+        let content = r#"const pkg = await import('vitest/package.json');
+
+console.log(pkg.version);"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(!result.updated);
         assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_reference_types_with_leading_whitespace() {
-        let content = r#"  /// <reference types="vite/client" />"#;
+    fn test_rewrite_import_content_vitest_package_json_typeof_import() {
+        let content = r#"type Pkg = typeof import('vitest/package.json');"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
-        assert!(result.updated);
-        assert_eq!(result.content, r#"  /// <reference types="vite-plus/client" />"#);
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
     }
 
     #[test]
-    fn test_rewrite_reference_types_env_d_ts_style() {
-        let content = r#"/// <reference types="vite/client" />
-/// <reference types="vitest" />
-/// <reference types="vitest/globals" />
-/// <reference types="vitest/config" />
-/// <reference types="@vitest/browser/context" />"#;
+    fn test_rewrite_import_content_vitest_package_json_export_from() {
+        let content = r#"export { version } from 'vitest/package.json';"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(!result.updated);
+        assert_eq!(result.content, content);
+    }
 
+    #[test]
+    fn test_rewrite_import_content_vitest_config_still_rewritten() {
+        // Sanity check: the `package.json` exclusion must not interfere with
+        // the existing `vitest/config` → `vite-plus` rule (which produces a
+        // bare `vite-plus` target, NOT `vite-plus/test/config`).
+        let content = r#"import 'vitest/config';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="vite-plus/client" />
-/// <reference types="vite-plus/test" />
-/// <reference types="vite-plus/test/globals" />
-/// <reference types="vite-plus" />
-/// <reference types="vite-plus/test/browser/context" />"#
-        );
+        assert_eq!(result.content, r#"import 'vite-plus';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_mixed_with_imports() {
-        let content = r#"/// <reference types="vite/client" />
-import { defineConfig } from 'vite';
+    fn test_rewrite_import_content_vitest_package_json_like_suffix_still_rewritten() {
+        // Sanity check: only the exact `vitest/package.json` subpath is
+        // skipped. A subpath that merely starts with `package.json` (e.g.,
+        // `package.json.js`) MUST still be rewritten by the catch-all rule.
+        let content = r#"import 'vitest/package.json.js';"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"import 'vite-plus/test/package.json.js';"#);
+    }
 
-export default defineConfig({});"#;
+    // The `rewrite-vitest-subpath-*` rules now explicitly exclude `vitest/config`
+    // via `not: regex: ^['"]vitest/(package\.json|config)['"]$` to make the intent
+    // robust against rule-reordering or ast-grep conflict-resolution changes.
+    // These tests verify the exclusion holds for all 4 import shapes and that
+    // a similar-but-distinct path like `vitest/config-extra` is NOT excluded.
 
+    #[test]
+    fn test_rewrite_import_content_vitest_config_export_not_subpath_rewritten() {
+        // `export ... from 'vitest/config'` must collapse to `vite-plus` (bare),
+        // NOT `vite-plus/test/config`. The subpath rule's `not` exclusion prevents
+        // the wrong fix from firing.
+        let content = r#"export type { Config } from 'vitest/config';"#;
         let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="vite-plus/client" />
-import { defineConfig } from 'vite-plus';
-
-export default defineConfig({});"#
-        );
+        assert_eq!(result.content, r#"export type { Config } from 'vite-plus';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_skip_vite() {
-        let content = r#"/// <reference types="vite/client" />
-/// <reference types="vitest" />"#;
-
-        let skip_packages =
-            SkipPackages { skip_vite: true, skip_vitest: false, skip_tsdown: false };
-        let result = rewrite_import_content(content, &skip_packages).unwrap();
+    fn test_rewrite_import_content_vitest_config_require_not_subpath_rewritten() {
+        // `require('vitest/config')` must collapse to `vite-plus` (bare),
+        // NOT `vite-plus/test/config`.
+        let content = r#"const { defineConfig } = require('vitest/config');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="vite/client" />
-/// <reference types="vite-plus/test" />"#
-        );
+        assert_eq!(result.content, r#"const { defineConfig } = require('vite-plus');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_skip_vitest() {
-        let content = r#"/// <reference types="vite/client" />
-/// <reference types="vitest" />
-/// <reference types="@vitest/browser/matchers" />"#;
-
-        let skip_packages =
-            SkipPackages { skip_vite: false, skip_vitest: true, skip_tsdown: false };
-        let result = rewrite_import_content(content, &skip_packages).unwrap();
+    fn test_rewrite_import_content_vitest_config_dynamic_import_not_subpath_rewritten() {
+        // `import('vitest/config')` must collapse to `vite-plus` (bare),
+        // NOT `vite-plus/test/config`.
+        let content = r#"const m = await import('vitest/config');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="vite-plus/client" />
-/// <reference types="vitest" />
-/// <reference types="@vitest/browser/matchers" />"#
-        );
+        assert_eq!(result.content, r#"const m = await import('vite-plus');"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_skip_tsdown() {
-        let content = r#"/// <reference types="tsdown/client" />
-/// <reference types="vite/client" />"#;
+    fn test_rewrite_import_content_vitest_config_extra_subpath_static_import() {
+        // `vitest/config-extra` is NOT `vitest/config` — the subpath exclusion
+        // must not be too broad. It should still be rewritten by the catch-all
+        // subpath rule to `vite-plus/test/config-extra`.
+        let content = r#"import { something } from 'vitest/config-extra';"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"import { something } from 'vite-plus/test/config-extra';"#);
+    }
 
-        let skip_packages =
-            SkipPackages { skip_vite: false, skip_vitest: false, skip_tsdown: true };
-        let result = rewrite_import_content(content, &skip_packages).unwrap();
+    #[test]
+    fn test_rewrite_import_content_vitest_config_extra_subpath_export() {
+        // Same sanity check for export_statement shape.
+        let content = r#"export { something } from 'vitest/config-extra';"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
         assert!(result.updated);
-        assert_eq!(
-            result.content,
-            r#"/// <reference types="tsdown/client" />
-/// <reference types="vite-plus/client" />"#
-        );
+        assert_eq!(result.content, r#"export { something } from 'vite-plus/test/config-extra';"#);
     }
 
     #[test]
-    fn test_rewrite_reference_types_skip_all() {
-        let content = r#"/// <reference types="vite/client" />
-/// <reference types="vitest" />
-/// <reference types="tsdown/client" />"#;
+    fn test_rewrite_import_content_vitest_config_extra_subpath_require() {
+        // Same sanity check for require() shape.
+        let content = r#"const m = require('vitest/config-extra');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const m = require('vite-plus/test/config-extra');"#);
+    }
 
-        let skip_packages = SkipPackages { skip_vite: true, skip_vitest: true, skip_tsdown: true };
-        let result = rewrite_import_content(content, &skip_packages).unwrap();
-        assert!(!result.updated);
-        assert_eq!(result.content, content);
+    #[test]
+    fn test_rewrite_import_content_vitest_config_extra_subpath_dynamic_import() {
+        // Same sanity check for dynamic import() shape.
+        let content = r#"const m = await import('vitest/config-extra');"#;
+        let result = rewrite_import_content(content, &SkipPackages::default()).unwrap();
+        assert!(result.updated);
+        assert_eq!(result.content, r#"const m = await import('vite-plus/test/config-extra');"#);
     }
 }
diff --git a/docs/guide/migrate.md b/docs/guide/migrate.md
index 5ce59a447d..d5dbc0282f 100644
--- a/docs/guide/migrate.md
+++ b/docs/guide/migrate.md
@@ -75,8 +75,8 @@ Migrate this project to Vite+. Vite+ replaces the current split tooling around r
 After the migration:
 
 - Confirm `vite` imports were rewritten to `vite-plus` where needed
-- Confirm `vitest` imports were rewritten to `vite-plus/test` where needed
-- On pnpm, keep the `vite` / `vitest` entries that `vp migrate` aliased to the Vite+ packages so the workspace override stays effective; with other package managers you can remove them once those rewrites are confirmed
+- Confirm `vitest` imports were rewritten to `vite-plus/test` (and `@vitest/browser*` to `vite-plus/test/browser*`) where needed
+- Remove old `vite`, `vitest`, and `@vitest/browser*` dependencies only after those rewrites are confirmed — `vite-plus` ships them as direct deps
 - Move remaining tool-specific config into the appropriate blocks in `vite.config.ts`
 
 Command mapping to keep in mind:
@@ -96,20 +96,30 @@ Summarize the migration at the end and report any manual follow-up still require
 
 ### Vitest
 
-Vitest is automatically migrated through `vp migrate`. If you are migrating manually, you have to update all the imports to `vite-plus/test` instead:
+Vitest is automatically migrated through `vp migrate`. `vite-plus` re-exports upstream `vitest@4.x` under `vite-plus/test*`, so for node-mode tests a single `vite-plus` install is enough — you no longer need to install `vitest` directly.
+
+Browser mode is more nuanced. `vite-plus` bundles the base browser runtime (`@vitest/browser`) and the preview provider (`@vitest/browser-preview`), but the **Playwright** and **WebdriverIO** providers stay opt-in: `@vitest/browser-playwright` (with its `playwright` peer) and `@vitest/browser-webdriverio` (with its `webdriverio` peer) are **not** shipped with `vite-plus`, so non-browser projects never pull them in. `vp migrate` detects the provider you actually use and adds it — pinned to the bundled vitest version — together with its framework. If you migrate manually and use one of these providers, install the provider package and its framework yourself so `vite-plus/test/browser-playwright` / `vite-plus/test/browser-webdriverio` can resolve.
+
+If you are migrating manually, update all the imports to `vite-plus/test*` instead:
 
 ```ts
 // before
+import { defineConfig } from 'vitest/config';
 import { describe, expect, it, vi } from 'vitest';
+import { playwright } from '@vitest/browser-playwright';
 
 const { page } = await import('@vitest/browser/context');
 
 // after
+import { defineConfig } from 'vite-plus';
 import { describe, expect, it, vi } from 'vite-plus/test';
+import { playwright } from 'vite-plus/test/browser-playwright';
 
 const { page } = await import('vite-plus/test/browser/context');
 ```
 
+`declare module 'vitest'` / `declare module '@vitest/browser*'` augmentations are intentionally **not** rewritten — `vite-plus/test*` is a thin re-export of upstream `vitest*`, so type augmentations have to target the upstream module identity to merge correctly. Leave those `declare module` statements pointing at `'vitest'` / `'@vitest/browser*'`.
+
 ### tsdown
 
 If your project uses a `tsdown.config.ts`, move its options into the `pack` block in `vite.config.ts`:
diff --git a/docs/guide/upgrade.md b/docs/guide/upgrade.md
index 2902b33af6..fe0a4997b8 100644
--- a/docs/guide/upgrade.md
+++ b/docs/guide/upgrade.md
@@ -40,21 +40,38 @@ You can also use `vp add vite-plus@latest` if you want to move the dependency ex
 
 ### Updating Aliased Packages
 
-Vite+ sets up npm aliases for its core packages during installation:
+Vite+ sets up an npm alias for its core package during installation:
 
 - `vite` is aliased to `npm:@voidzero-dev/vite-plus-core@latest`
-- `vitest` is aliased to `npm:@voidzero-dev/vite-plus-test@latest`
 
-`vp update vite-plus` does not re-resolve these aliases in the lockfile. To fully upgrade, update them separately:
+`vp update vite-plus` does not re-resolve this alias in the lockfile. To fully upgrade, update it separately:
 
 ```bash
-vp update @voidzero-dev/vite-plus-core @voidzero-dev/vite-plus-test
+vp update @voidzero-dev/vite-plus-core
 ```
 
 Or update everything at once:
 
 ```bash
-vp update vite-plus @voidzero-dev/vite-plus-core @voidzero-dev/vite-plus-test
+vp update vite-plus @voidzero-dev/vite-plus-core
 ```
 
 You can verify with `vp outdated` that no Vite+ packages remain outdated.
+
+### Updating the Vitest Pin
+
+If you migrated with `vp migrate`, your project pins `vitest` to an exact version so the whole project shares a single Vitest copy with the bundled `vp test` runner. The pin lives in your package manager's override block:
+
+- **npm / Bun:** a `vitest` entry under `overrides` in `package.json`
+- **Yarn:** a `vitest` entry under `resolutions` in `package.json`
+- **pnpm:** a `vitest` entry under `overrides` in `pnpm-workspace.yaml` — unless your `package.json` already had a `pnpm` field, in which case it lives under `pnpm.overrides` in `package.json` instead (pnpm ignores `pnpm-workspace.yaml` overrides when `package.json` defines `pnpm.overrides`)
+
+A Vite+ release can bump the bundled Vitest. Because that pin also applies to `vite-plus`'s own `vitest` dependency, an out-of-date pin keeps installing the previous runner even after you upgrade `vite-plus` — splitting Vitest's internals (mocks, `expect`, runner state) between the pinned copy and the one `vp test` loads.
+
+After upgrading `vite-plus`, re-pin `vitest` to the version Vite+ now bundles. Check that version with:
+
+```bash
+vp --version
+```
+
+Then set the `vitest` override to that exact version, or rerun `vp migrate` to update the pin for you.
diff --git a/ecosystem-ci/patch-project.ts b/ecosystem-ci/patch-project.ts
index 2226dbe353..86fec24eef 100644
--- a/ecosystem-ci/patch-project.ts
+++ b/ecosystem-ci/patch-project.ts
@@ -2,12 +2,10 @@ import { execSync } from 'node:child_process';
 import { readFile, writeFile } from 'node:fs/promises';
 import { join } from 'node:path';
 
-import cliPkg from '../packages/cli/package.json' with { type: 'json' };
+import { VITEST_VERSION } from '../packages/cli/src/utils/constants.ts';
 import { ecosystemCiDir, tgzDir } from './paths.ts';
 import repos from './repo.json' with { type: 'json' };
 
-const vpVersion = cliPkg.version;
-
 const projects = Object.keys(repos);
 
 const project = process.argv[2];
@@ -21,7 +19,9 @@ const repoRoot = join(ecosystemCiDir, project);
 const repoConfig = repos[project as keyof typeof repos];
 const directory = 'directory' in repoConfig ? repoConfig.directory : undefined;
 const cwd = directory ? join(repoRoot, directory) : repoRoot;
-const vitePlusTgz = `file:${tgzDir}/vite-plus-${vpVersion}.tgz`;
+// The e2e build job pins packages/cli to 0.0.0 before `pnpm pack`, so the
+// artifact is always vite-plus-0.0.0.tgz regardless of the committed version.
+const vitePlusTgz = `file:${tgzDir}/vite-plus-0.0.0.tgz`;
 // run vp migrate
 const cli = process.env.VP_CLI_BIN ?? 'vp';
 
@@ -45,6 +45,19 @@ if (project === 'vinext') {
     throw new Error(`vinext patch: \`minimumReleaseAge:\` not found in ${workspacePath}`);
   }
   await writeFile(workspacePath, patched, 'utf-8');
+
+  // The single in-process `integration` project runs serially and its ISR
+  // revalidation test sits right at the 30s ceiling under CI load (observed
+  // 26.8s on green main runs, 30.0s here) — a borderline timeout, not a real
+  // regression (the vitest runner is byte-identical across this bump). Give it
+  // headroom so the ecosystem run isn't flaky.
+  const viteConfigPath = join(repoRoot, 'vite.config.ts');
+  const viteConfig = await readFile(viteConfigPath, 'utf-8');
+  const patchedConfig = viteConfig.replace('testTimeout: 30000', 'testTimeout: 60000');
+  if (patchedConfig === viteConfig) {
+    throw new Error(`vinext patch: \`testTimeout: 30000\` not found in ${viteConfigPath}`);
+  }
+  await writeFile(viteConfigPath, patchedConfig, 'utf-8');
 }
 
 if (project === 'dify') {
@@ -66,6 +79,33 @@ if (project === 'dify') {
 // vp migrate runs full dependency rewriting instead of skipping.
 const forceFreshMigration = 'forceFreshMigration' in repoConfig && repoConfig.forceFreshMigration;
 
+// Bun is uniquely strict about vitest's `peer vite ^6 || ^7 || ^8` resolution
+// (https://github.com/oven-sh/bun/issues/8406): it checks both the override
+// target's package name and version. Point bun-based projects at the
+// vite-7.99.0 alias tgz (a copy of core renamed to "vite" with a satisfying
+// version); pnpm/npm/yarn must keep pointing at the real core tgz, otherwise
+// they trip a registry lookup for "vite@<version>" when a workspace
+// sub-package and the override both reference the same vite-named alias.
+const isBunProject = project === 'bun-vite-template';
+const viteOverrideTgz = isBunProject ? `vite-7.99.0.tgz` : `voidzero-dev-vite-plus-core-0.0.0.tgz`;
+
+// Mirror VITE_PLUS_OVERRIDE_PACKAGES: pin `vitest` only. The `@vitest/*` family
+// are exact deps of `vitest`, so a single `vitest` override cascades them.
+//
+// Coverage providers are intentionally NOT in the shipped override map (the
+// product leaves them user-owned; the runtime guard fail-fasts on a skew). But
+// this rig FORCE-INSTALLS the locally built vitest, and many ecosystem projects
+// pin an older `@vitest/coverage-*` in their lockfile. Without alignment, the
+// forced runner (4.1.9) skews from the project's pinned provider and the guard
+// aborts `vp test --coverage` — testing an incoherent combo no real install has.
+// Pin the providers here so the E2E coverage step runs against a consistent
+// runner+provider pair, exactly as a user who followed the guard's advice would.
+const vitestOverrides = {
+  vitest: VITEST_VERSION,
+  '@vitest/coverage-v8': VITEST_VERSION,
+  '@vitest/coverage-istanbul': VITEST_VERSION,
+};
+
 execSync(`${cli} migrate --no-agent --no-interactive`, {
   cwd,
   stdio: 'inherit',
@@ -73,10 +113,9 @@ execSync(`${cli} migrate --no-agent --no-interactive`, {
     ...process.env,
     ...(forceFreshMigration ? { VP_FORCE_MIGRATE: '1' } : {}),
     VP_OVERRIDE_PACKAGES: JSON.stringify({
-      vite: `file:${tgzDir}/voidzero-dev-vite-plus-core-${vpVersion}.tgz`,
-      vitest: `file:${tgzDir}/voidzero-dev-vite-plus-test-${vpVersion}.tgz`,
-      '@voidzero-dev/vite-plus-core': `file:${tgzDir}/voidzero-dev-vite-plus-core-${vpVersion}.tgz`,
-      '@voidzero-dev/vite-plus-test': `file:${tgzDir}/voidzero-dev-vite-plus-test-${vpVersion}.tgz`,
+      vite: `file:${tgzDir}/${viteOverrideTgz}`,
+      '@voidzero-dev/vite-plus-core': `file:${tgzDir}/voidzero-dev-vite-plus-core-0.0.0.tgz`,
+      ...vitestOverrides,
     }),
     VP_VERSION: vitePlusTgz,
   },
diff --git a/ecosystem-ci/verify-install.ts b/ecosystem-ci/verify-install.ts
index 1b13c774bc..212b741122 100644
--- a/ecosystem-ci/verify-install.ts
+++ b/ecosystem-ci/verify-install.ts
@@ -2,11 +2,13 @@ import { readFileSync } from 'node:fs';
 import { createRequire } from 'node:module';
 import path from 'node:path';
 
-import cliPkg from '../packages/cli/package.json' with { type: 'json' };
-
 const require = createRequire(`${process.cwd()}/`);
 
-const expectedVersion = cliPkg.version;
+// The ecosystem-ci pack step pins packages/cli to 0.0.0 before `pnpm pack`, so
+// a correctly installed local build always reports 0.0.0 — never the published
+// registry version (which `patch-project.ts` likewise references as a fixed
+// `vite-plus-0.0.0.tgz`).
+const expectedVersion = '0.0.0';
 
 try {
   const pkgPath = require.resolve('vite-plus/package.json');
diff --git a/packages/cli/BUNDLING.md b/packages/cli/BUNDLING.md
index fa9ef3d792..b4f23f1b2a 100644
--- a/packages/cli/BUNDLING.md
+++ b/packages/cli/BUNDLING.md
@@ -1,6 +1,6 @@
 # CLI Package Build Architecture
 
-This document explains how `vite-plus` is built and how it re-exports from both the core and test packages to serve as a drop-in replacement for `vite`.
+This document explains how `vite-plus` is built and how it re-exports from `@voidzero-dev/vite-plus-core` (bundled vite/rolldown/tsdown) and from upstream `vitest` to serve as a drop-in replacement for `vite`.
 
 ## Overview
 
@@ -9,9 +9,9 @@ The CLI package uses a **4-step build process**:
 1. **tsdown Build** - Bundle all CLI entry points via tsdown
 2. **NAPI Binding Build** - Compile Rust code to native Node.js bindings
 3. **Core Package Export Sync** - Re-export `@voidzero-dev/vite-plus-core` under `./client`, `./types/*`, etc.
-4. **Test Package Export Sync** - Re-export `@voidzero-dev/vite-plus-test` under `./test/*`
+4. **Test Package Export Sync** - Re-export upstream `vitest` under `./test/*`
 
-This architecture allows users to import everything from a single package (`vite-plus`) as a drop-in replacement for `vite`, without needing to know about the separate core and test packages.
+This architecture allows users to import everything from a single package (`vite-plus`) as a drop-in replacement for `vite`, without needing to know about the separate `@voidzero-dev/vite-plus-core` bundle or `vitest`.
 
 ## Build Steps
 
@@ -24,7 +24,7 @@ Bundles all CLI entry points using tsdown (configured in `tsdown.config.ts`). Th
 - Public API entries: `bin`, `index`, `define-config`, `fmt`, `lint`, `pack`, `pack-bin`
 - Global command entries: `create`, `migrate`, `version`, `config`, `mcp`, `staged`
 - All third-party dependencies are inlined at build time
-- Only packages that must be resolved at runtime stay external (NAPI binding, `@voidzero-dev/vite-plus-core`, `@voidzero-dev/vite-plus-test`, `oxfmt`, `oxlint`)
+- Only packages that must be resolved at runtime stay external (NAPI binding, `@voidzero-dev/vite-plus-core`, `vitest`, `oxfmt`, `oxlint`)
 - Code splitting creates shared chunks for code used by multiple entries
 - DTS (`.d.ts`) files are generated for all entries
 
@@ -90,15 +90,22 @@ export type * from '@voidzero-dev/vite-plus-core/types/importMeta.d.ts';
 
 ### Step 4: Test Package Export Sync (`syncTestPackageExports`)
 
-Reads the test package's exports and creates shim files that re-export everything under `./test/*`:
+Reads vitest's exports plus the three `@vitest/browser-*` provider packages and creates shim files that re-export everything under `./test/*`:
 
 ```typescript
-// For each test package export like "./browser-playwright"
-// Creates a shim file: dist/test/browser-playwright.js
-export * from '@voidzero-dev/vite-plus-test/browser-playwright';
+// For each vitest export like "./node"
+// Creates a shim file: dist/test/node.js
+export * from 'vitest/node';
+
+// For each @vitest/browser-* provider, two shim surfaces are projected:
+//   dist/test/browser-playwright.js          (matches old wrapper path)
+//   dist/test/browser/providers/playwright.js (alias path)
+export * from '@vitest/browser-playwright';
 ```
 
-**Input**: `../test/package.json` exports
+Provider `.d.ts` shims are NOT bare re-exports — see the [Provider Type Identity](#why-provider-dts-shims-are-inlined) note below.
+
+**Input**: resolved `vitest/package.json` exports plus each `@vitest/browser-*` package's exports (all resolved via `createRequire`)
 **Output**: `dist/test/*.js`, `dist/test/*.d.ts`, updated `package.json` exports
 
 ---
@@ -336,28 +343,49 @@ This allows TypeScript to pick up types like `import.meta.hot`, CSS module types
 
 ### Why Shim Files?
 
-Instead of copying the actual dist files from the test package, we create thin shim files that re-export from `@voidzero-dev/vite-plus-test`. This approach:
+Instead of copying vitest's dist files, we create thin shim files that re-export from `vitest`. This approach:
 
-1. **Keeps packages in sync** - No need to rebuild CLI when test package changes
+1. **Keeps packages in sync** - No need to rebuild CLI when vitest is upgraded
 2. **Reduces duplication** - No file copying, just re-exports
-3. **Preserves module resolution** - Node.js resolves to the actual test package
+3. **Preserves module resolution** - Node.js resolves to the actual installed vitest
 
 ### Export Mapping (Test)
 
-All test package exports are mapped under `./test/*`:
+Every entry under vitest's own `exports` is shimmed under `./test/*` (wildcard exports and `./package.json` are skipped). The shim is purely a re-export — `vite-plus/test` and friends are aliases for the matching subpath of upstream `vitest`. Examples:
+
+| Vitest Export      | CLI Package Export         |
+| ------------------ | -------------------------- |
+| `vitest`           | `vite-plus/test`           |
+| `vitest/browser`   | `vite-plus/test/browser`   |
+| `vitest/node`      | `vite-plus/test/node`      |
+| `vitest/config`    | `vite-plus/test/config`    |
+| `vitest/reporters` | `vite-plus/test/reporters` |
+
+The full set is regenerated on every build from the upstream vitest `package.json`, so the exact list tracks vitest itself.
+
+In addition to vitest's own exports, the three `@vitest/browser-*` provider packages are projected under two parallel surfaces so existing user code keeps resolving after the deleted `@voidzero-dev/vite-plus-test` wrapper:
+
+| Provider Package              | CLI Package Exports                                                                  |
+| ----------------------------- | ------------------------------------------------------------------------------------ |
+| `@vitest/browser-playwright`  | `vite-plus/test/browser-playwright`, `vite-plus/test/browser/providers/playwright`   |
+| `@vitest/browser-preview`     | `vite-plus/test/browser-preview`, `vite-plus/test/browser/providers/preview`         |
+| `@vitest/browser-webdriverio` | `vite-plus/test/browser-webdriverio`, `vite-plus/test/browser/providers/webdriverio` |
+
+Each provider's own subpaths (e.g. `./context`) are mirrored under both alias prefixes.
+
+> **Note — webdriverio and playwright are opt-in.** `@vitest/browser` (base) and `@vitest/browser-preview` stay bundled **runtime dependencies** of `vite-plus` (and are stripped from users' manifests during migration) because neither carries a heavy non-optional peer. `@vitest/browser-webdriverio` and `@vitest/browser-playwright` are now vite-plus **devDependencies + optional peerDependencies** — each is kept as a devDependency so build-time shim generation can still emit the `./test/browser-webdriverio*` / `./test/browser-playwright*` exports (the export/shim surfaces above are unchanged), but neither is a bundled runtime dep. They are optional peers because each drags a non-optional framework peer (`webdriverio` / `playwright`) that non-browser consumers must not be forced to install. Users targeting a provider instead **keep** it in their **own** dependencies via `vp migrate` (pinned to the bundled vitest version, with its framework peer ensured), so their rewritten `vite-plus/test/browser-webdriverio` / `vite-plus/test/browser-playwright` imports resolve.
+
+#### Why provider d.ts shims are inlined
+
+Provider `.d.ts` shims are NOT plain `export * from '@vitest/browser-playwright'` re-exports — they inline the upstream `.d.ts` content with `vitest/node` / `vitest/browser` / `@vitest/browser*` bare specifiers rewritten to relative paths inside `dist/test/`. The two private shims `dist/test/_at-vitest-browser.d.ts` and `dist/test/_at-vitest-browser/context.d.ts` re-export `@vitest/browser`/`@vitest/browser/context` and are referenced from those rewrites.
 
-| Test Package Export                               | CLI Package Export                  |
-| ------------------------------------------------- | ----------------------------------- |
-| `@voidzero-dev/vite-plus-test`                    | `vite-plus/test`                    |
-| `@voidzero-dev/vite-plus-test/browser`            | `vite-plus/test/browser`            |
-| `@voidzero-dev/vite-plus-test/browser-playwright` | `vite-plus/test/browser-playwright` |
-| `@voidzero-dev/vite-plus-test/plugins/runner`     | `vite-plus/test/plugins/runner`     |
+This avoids a pnpm-edge type-identity split: when the upstream `.d.ts` is loaded by reference (`export * from '@vitest/browser-playwright'`), TypeScript resolves its internal `import { BrowserProvider } from 'vitest/node'` through the provider package's own pnpm-edge, which can be a different vitest copy than the one a user's `vite.config.ts` sees through `vite-plus`. The mismatch produces two structurally identical but nominally distinct `BrowserProvider` types, so `provider: playwright()` fails the user's typecheck. Rewriting the specifiers routes every type import through vite-plus's own subpath shims, guaranteeing a single vitest identity across the user's whole config.
 
 ### Conditional Export Handling
 
 The sync handles complex conditional exports with `import`/`require`/`node`/`types` conditions.
 
-**Test package's main export** (`"."`):
+**Vitest's main export** (`"."`):
 
 ```json
 ".": {
@@ -390,23 +418,23 @@ For each condition, appropriate shim files are created:
 
 ### Shim File Contents
 
-**ESM shim** (`dist/test/browser-playwright.js`):
+**ESM shim** (`dist/test/browser.js`):
 
 ```javascript
-export * from '@voidzero-dev/vite-plus-test/browser-playwright';
+export * from 'vitest/browser';
 ```
 
 **CJS shim** (`dist/test/index.cjs`):
 
 ```javascript
-module.exports = require('@voidzero-dev/vite-plus-test');
+module.exports = require('vitest');
 ```
 
-**Type shim** (`dist/test/browser-playwright.d.ts`):
+**Type shim** (`dist/test/browser.d.ts`):
 
 ```typescript
-import '@voidzero-dev/vite-plus-test/browser-playwright';
-export * from '@voidzero-dev/vite-plus-test/browser-playwright';
+import 'vitest/browser';
+export * from 'vitest/browser';
 ```
 
 Note: Type shims include a side-effect import to preserve module augmentations (e.g., `toMatchSnapshot` on the `Assertion` interface).
@@ -499,8 +527,8 @@ See `package.json` for the complete list of exports.
 // Core package name for Vite compatibility exports
 const CORE_PACKAGE_NAME = '@voidzero-dev/vite-plus-core';
 
-// Test package name for re-exports
-const TEST_PACKAGE_NAME = '@voidzero-dev/vite-plus-test';
+// Test package name for re-exports (vitest itself, not a bundled wrapper)
+const TEST_PACKAGE_NAME = 'vitest';
 ```
 
 ### Package.json Exports Management
@@ -539,7 +567,7 @@ All non-`./test*` exports are manually maintained in `package.json`. These fall
 
 All `./test*` exports are fully managed by `syncTestPackageExports()`. The build script:
 
-1. Reads `packages/test/package.json` exports
+1. Reads vitest's `package.json` exports (resolved via `createRequire`)
 2. Creates shim files in `dist/test/`
 3. Removes old `./test*` exports from `package.json`
 4. Merges in newly generated test exports
diff --git a/packages/cli/README.md b/packages/cli/README.md
index 8c3fcd71b0..ff1ede0783 100644
--- a/packages/cli/README.md
+++ b/packages/cli/README.md
@@ -187,12 +187,12 @@ If you are manually migrating a project to Vite+, install these dev dependencies
 npm install -D vite-plus @voidzero-dev/vite-plus-core@latest
 ```
 
-You need to add overrides to your package manager for `vite` and `vitest` so that other packages depending on Vite and Vitest will use the Vite+ versions:
+You need to add overrides to your package manager so that other packages resolve the Vite+ versions: alias `vite` to `@voidzero-dev/vite-plus-core`, and pin `vitest` to the version Vite+ bundles (run `vp --version`) so the whole project shares a single Vitest copy with `vp test`. Without the `vitest` pin, a dependency or workspace package can pull a different Vitest than the bundled runner, splitting Vitest's internals (mocks, `expect`, runner state):
 
 ```json
 "overrides": {
   "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-  "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+  "vitest": "4.1.9"
 }
 ```
 
@@ -201,7 +201,7 @@ If you are using `pnpm`, add this to your `pnpm-workspace.yaml`:
 ```yaml
 overrides:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: 4.1.9
 ```
 
 Or, if you are using Yarn:
@@ -209,7 +209,7 @@ Or, if you are using Yarn:
 ```json
 "resolutions": {
   "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-  "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+  "vitest": "4.1.9"
 }
 ```
 
diff --git a/packages/cli/build.ts b/packages/cli/build.ts
index cd395f3b2f..fee85b977e 100644
--- a/packages/cli/build.ts
+++ b/packages/cli/build.ts
@@ -5,7 +5,7 @@
  * 1. buildWithTsdown() - Bundles all CLI entry points via tsdown
  * 2. buildNapiBinding() - Builds the native Rust binding via NAPI
  * 3. syncCorePackageExports() - Creates shim files to re-export from @voidzero-dev/vite-plus-core
- * 4. syncTestPackageExports() - Creates shim files to re-export from @voidzero-dev/vite-plus-test
+ * 4. syncTestPackageExports() - Creates shim files to re-export from vitest
  * 5. syncVersionsExport() - Generates ./versions module with bundled tool versions
  * 6. copyBundledDocs() - Copies docs into docs/ for bundled package access
  * 7. syncReadmeFromRoot() - Keeps package README in sync
@@ -21,6 +21,7 @@
 import { execSync } from 'node:child_process';
 import { existsSync, readdirSync, statSync } from 'node:fs';
 import { copyFile, cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
+import { createRequire } from 'node:module';
 import { dirname, join, relative } from 'node:path';
 import { fileURLToPath } from 'node:url';
 import { parseArgs } from 'node:util';
@@ -30,12 +31,76 @@ import { format } from 'oxfmt';
 
 import { generateLicenseFile } from '../../scripts/generate-license.js';
 import corePkg from '../core/package.json' with { type: 'json' };
-import testPkg from '../test/package.json' with { type: 'json' };
 
 const projectDir = dirname(fileURLToPath(import.meta.url));
-const TEST_PACKAGE_NAME = '@voidzero-dev/vite-plus-test';
+const TEST_PACKAGE_NAME = 'vitest';
 const CORE_PACKAGE_NAME = '@voidzero-dev/vite-plus-core';
 
+// Browser providers projected under ./test/* and ./test/browser/providers/* so the
+// public surface matches what the deleted `@voidzero-dev/vite-plus-test` wrapper exposed.
+// Each entry maps the upstream package name to the short provider name used in the
+// `./test/browser/providers/<short>` alias path.
+const BROWSER_PROVIDER_PACKAGES: ReadonlyArray<{ pkg: string; short: string }> = [
+  { pkg: '@vitest/browser-playwright', short: 'playwright' },
+  { pkg: '@vitest/browser-preview', short: 'preview' },
+  { pkg: '@vitest/browser-webdriverio', short: 'webdriverio' },
+];
+
+// Plugin shim entries: each `@vitest/*` package/subpath projected under
+// `./test/plugins/<name>` to restore the surface that the removed
+// `@voidzero-dev/vite-plus-test` wrapper previously exposed.
+const PLUGIN_SHIM_ENTRIES: ReadonlyArray<readonly [importSpecifier: string, pluginName: string]> = [
+  ['@vitest/runner', 'runner'],
+  ['@vitest/runner/utils', 'runner-utils'],
+  ['@vitest/runner/types', 'runner-types'],
+  ['@vitest/utils', 'utils'],
+  ['@vitest/utils/source-map', 'utils-source-map'],
+  ['@vitest/utils/source-map/node', 'utils-source-map-node'],
+  ['@vitest/utils/error', 'utils-error'],
+  ['@vitest/utils/helpers', 'utils-helpers'],
+  ['@vitest/utils/display', 'utils-display'],
+  ['@vitest/utils/timers', 'utils-timers'],
+  ['@vitest/utils/offset', 'utils-offset'],
+  ['@vitest/utils/resolver', 'utils-resolver'],
+  ['@vitest/utils/serialize', 'utils-serialize'],
+  ['@vitest/utils/constants', 'utils-constants'],
+  ['@vitest/utils/diff', 'utils-diff'],
+  ['@vitest/spy', 'spy'],
+  ['@vitest/expect', 'expect'],
+  ['@vitest/snapshot', 'snapshot'],
+  ['@vitest/snapshot/environment', 'snapshot-environment'],
+  ['@vitest/snapshot/manager', 'snapshot-manager'],
+  ['@vitest/mocker', 'mocker'],
+  ['@vitest/mocker/node', 'mocker-node'],
+  ['@vitest/mocker/browser', 'mocker-browser'],
+  ['@vitest/mocker/redirect', 'mocker-redirect'],
+  ['@vitest/mocker/transforms', 'mocker-transforms'],
+  ['@vitest/mocker/automock', 'mocker-automock'],
+  ['@vitest/mocker/register', 'mocker-register'],
+  ['@vitest/pretty-format', 'pretty-format'],
+  ['@vitest/browser', 'browser'],
+  ['@vitest/browser/context', 'browser-context'],
+  ['@vitest/browser/client', 'browser-client'],
+  ['@vitest/browser/locators', 'browser-locators'],
+  ['@vitest/browser-playwright', 'browser-playwright'],
+  ['@vitest/browser-webdriverio', 'browser-webdriverio'],
+  ['@vitest/browser-preview', 'browser-preview'],
+];
+
+/**
+ * Vitest-related bare specifiers that appear in `@vitest/browser-*` d.ts files
+ * and the sub-path under `dist/test/` whose shim re-exports the same module.
+ * Longer prefixes are listed first so substring matches don't shadow them
+ * (e.g. `vitest/internal/browser` before `vitest/browser`).
+ */
+const VITEST_TYPE_SPECIFIER_REWRITES: ReadonlyArray<readonly [string, string]> = [
+  ['@vitest/browser/context', '_at-vitest-browser/context'],
+  ['@vitest/browser', '_at-vitest-browser'],
+  ['vitest/internal/browser', 'internal/browser'],
+  ['vitest/browser', 'browser'],
+  ['vitest/node', 'node'],
+];
+
 const {
   values: { ['skip-native']: skipNative, ['skip-ts']: skipTs },
 } = parseArgs({
@@ -275,16 +340,21 @@ async function syncTypesDir(srcDir: string, destDir: string, relativePath: strin
 }
 
 /**
- * Sync exports from @voidzero-dev/vite-plus-test to vite-plus
+ * Sync exports from vitest to vite-plus
  *
- * This function reads the test package's exports and creates shim files that
+ * This function reads vitest's package.json exports and creates shim files that
  * re-export everything under the ./test/* subpath. This allows users to import
- * from vite-plus/test/* instead of @voidzero-dev/vite-plus-test/*.
+ * from vite-plus/test/* instead of vitest/*.
  */
 async function syncTestPackageExports() {
   console.log('\nSyncing test package exports...');
 
-  const testPkgPath = join(projectDir, '../test/package.json');
+  // Resolve vitest's package.json via Node's resolver so we always read the
+  // currently installed copy — packages/test/ no longer exists.
+  const require = createRequire(import.meta.url);
+  const testPkgPath = require.resolve(`${TEST_PACKAGE_NAME}/package.json`, {
+    paths: [projectDir],
+  });
   const cliPkgPath = join(projectDir, 'package.json');
   const testDistDir = join(projectDir, 'dist/test');
 
@@ -306,21 +376,225 @@ async function syncTestPackageExports() {
 
     // Convert ./foo to ./test/foo, . to ./test
     const cliExportPath = exportPath === '.' ? './test' : `./test${exportPath.slice(1)}`;
+    const shimBaseName = exportPath === '.' ? 'index' : exportPath.slice(2);
+    const importSpecifier =
+      exportPath === '.' ? TEST_PACKAGE_NAME : `${TEST_PACKAGE_NAME}${exportPath.slice(1)}`;
 
     // Create shim files and build export entry
-    const shimExport = await createShimForExport(exportPath, exportValue, testDistDir);
+    const shimExport = await createShimForExport(
+      shimBaseName,
+      exportValue,
+      importSpecifier,
+      testDistDir,
+    );
     if (shimExport) {
       generatedExports[cliExportPath] = shimExport;
       console.log(`  Created ${cliExportPath}`);
     }
   }
 
+  // Private shims for `@vitest/browser` and `@vitest/browser/context`. These
+  // are referenced as relative paths from the inlined browser-provider d.ts
+  // shims so that `@vitest/browser` resolves through vite-plus's own pnpm-edge
+  // (same one that owns the `vitest` direct dep) — preventing the two-vitest
+  // type-identity split that breaks user `provider: playwright()` typechecks.
+  await writePrivateAtVitestBrowserShims(testDistDir);
+
+  // Mirror upstream @vitest/browser-* provider packages under ./test/<provider> and
+  // ./test/browser/providers/<short>. Existing vite-plus user code imports from these
+  // paths (e.g., `vite-plus/test/browser-playwright`) and must keep resolving after
+  // the bundled `@voidzero-dev/vite-plus-test` wrapper was removed.
+  for (const { pkg, short } of BROWSER_PROVIDER_PACKAGES) {
+    let providerPkgPath: string;
+    try {
+      providerPkgPath = require.resolve(`${pkg}/package.json`, { paths: [projectDir] });
+    } catch (err) {
+      console.warn(`  Skipping ${pkg} — not installed: ${(err as Error).message}`);
+      continue;
+    }
+    const providerPkg = JSON.parse(await readFile(providerPkgPath, 'utf-8'));
+    const providerPkgRoot = dirname(providerPkgPath);
+    const providerExports = (providerPkg.exports ?? {}) as Record<string, unknown>;
+
+    for (const [providerExportPath, providerExportValue] of Object.entries(providerExports)) {
+      if (providerExportPath === './package.json' || providerExportPath.includes('*')) {
+        continue;
+      }
+
+      const providerSubPath = providerExportPath === '.' ? '' : providerExportPath.slice(1);
+      // Two CLI surfaces that map to the same provider shim:
+      //   ./test/<pkgShortName>           → e.g. ./test/browser-playwright
+      //   ./test/browser/providers/<short> → e.g. ./test/browser/providers/playwright
+      const pkgShortName = pkg.startsWith('@vitest/') ? pkg.slice('@vitest/'.length) : pkg;
+      const surfaces = [
+        {
+          cliPath: `./test/${pkgShortName}${providerSubPath}`,
+          baseName: `${pkgShortName}${providerSubPath}`,
+        },
+        {
+          cliPath: `./test/browser/providers/${short}${providerSubPath}`,
+          baseName: `browser/providers/${short}${providerSubPath}`,
+        },
+      ];
+      const importSpecifier =
+        providerExportPath === '.' ? pkg : `${pkg}${providerExportPath.slice(1)}`;
+
+      for (const { cliPath, baseName } of surfaces) {
+        const shimBaseName = baseName.replace(/^\//, '');
+        const shimExport = await createShimForExport(
+          shimBaseName,
+          providerExportValue,
+          importSpecifier,
+          testDistDir,
+          { providerPkgRoot },
+        );
+        if (shimExport) {
+          // Upstream `@vitest/browser-<provider>/context` is types-only and just
+          // re-exports from `@vitest/browser/context`. To make the migrated
+          // `vite-plus/test/browser-<provider>/context` import resolvable at
+          // runtime (Node ESM resolution requires `default`/`import`), emit a
+          // JS shim that re-exports from `@vitest/browser/context` and amend
+          // the export entry.
+          if (providerExportPath === './context') {
+            await ensureContextRuntimeShim(shimBaseName, testDistDir, shimExport);
+          }
+          generatedExports[cliPath] = shimExport;
+          console.log(`  Created ${cliPath}`);
+        }
+      }
+    }
+  }
+
+  // Emit `./test/browser/context` — vitest's exports map only covers `./browser`
+  // (which becomes `vite-plus/test/browser`), but the migration rewrites
+  // `@vitest/browser/context` → `vite-plus/test/browser/context`. Without this
+  // entry Node throws ERR_PACKAGE_PATH_NOT_EXPORTED at runtime.
+  generatedExports['./test/browser/context'] = await createBrowserContextExport(testDistDir);
+  console.log('  Created ./test/browser/context');
+
+  // Bare `./test/<subpath>` shims for the bundled `@vitest/browser` surfaces
+  // the old `@voidzero-dev/vite-plus-test` wrapper used to expose:
+  //   ./test/client, ./test/context, ./test/locators, ./test/matchers, ./test/utils
+  // `oxlint-plugin.ts` autofixes `@vitest/browser/client` →
+  // `vite-plus/test/client` and `@vitest/browser/locators` →
+  // `vite-plus/test/locators`, so the runtime targets MUST resolve.
+  const bareBrowserShims = await createBareBrowserShims(require, testDistDir);
+  for (const [cliPath, exportValue] of Object.entries(bareBrowserShims)) {
+    generatedExports[cliPath] = exportValue;
+    console.log(`  Created ${cliPath}`);
+  }
+
+  // Emit `./test/browser-compat` — used when downstream consumers point
+  // `@vitest/browser` at vite-plus via a pnpm/yarn override. The shim
+  // re-exports the four symbols vitest's browser plugin checks for to
+  // identify a compatible browser provider package.
+  generatedExports['./test/browser-compat'] = await createBrowserCompatExport(testDistDir);
+  console.log('  Created ./test/browser-compat');
+
+  for (const [importSpecifier, pluginName] of PLUGIN_SHIM_ENTRIES) {
+    const shimExport = await createShimForExport(
+      `plugins/${pluginName}`,
+      `${pluginName}.js`,
+      importSpecifier,
+      testDistDir,
+    );
+    if (shimExport) {
+      generatedExports[`./test/plugins/${pluginName}`] = shimExport;
+      console.log(`  Created ./test/plugins/${pluginName}`);
+    }
+  }
+
   // Update CLI package.json
   await updateCliPackageJson(cliPkgPath, generatedExports);
 
   console.log(`\nSynced ${Object.keys(generatedExports).length} exports from test package`);
 }
 
+/**
+ * `@vitest/browser` exports a handful of subpaths (`./client`, `./context`,
+ * `./locators`, `./matchers`, `./utils`) that the deleted vite-plus-test
+ * wrapper surfaced as bare `./test/<subpath>` entries. Without these, code
+ * that imports `vite-plus/test/client` (and friends) — including code
+ * produced by `vp lint --fix` via the autofix rule in
+ * `packages/cli/src/oxlint-plugin.ts` — fails with
+ * `ERR_PACKAGE_PATH_NOT_EXPORTED`.
+ *
+ * `./matchers` and `./utils` resolve to a `dummy.js` upstream (types-only
+ * entrypoints) and we mirror that — `createShimForExport` is happy with the
+ * empty default file because it still creates a valid shim that just
+ * re-exports nothing at runtime; type imports continue to resolve.
+ */
+async function createBareBrowserShims(
+  require: NodeRequire,
+  testDistDir: string,
+): Promise<Record<string, ExportValue>> {
+  const result: Record<string, ExportValue> = {};
+  let browserPkgPath: string;
+  try {
+    browserPkgPath = require.resolve('@vitest/browser/package.json', { paths: [projectDir] });
+  } catch (err) {
+    console.warn(
+      `  Skipping bare browser shims — @vitest/browser not installed: ${(err as Error).message}`,
+    );
+    return result;
+  }
+  const browserPkg = JSON.parse(await readFile(browserPkgPath, 'utf-8'));
+  const browserPkgRoot = dirname(browserPkgPath);
+  const browserExports = (browserPkg.exports ?? {}) as Record<string, unknown>;
+
+  const bareSubpaths = ['./client', './context', './locators', './matchers', './utils'] as const;
+  for (const sub of bareSubpaths) {
+    const exportValue = browserExports[sub];
+    if (!exportValue) {
+      continue;
+    }
+    const subName = sub.slice(2);
+    const cliPath = `./test/${subName}`;
+    const shimBaseName = subName;
+    const importSpecifier = `@vitest/browser${sub.slice(1)}`;
+    const shimExport = await createShimForExport(
+      shimBaseName,
+      exportValue,
+      importSpecifier,
+      testDistDir,
+      { providerPkgRoot: browserPkgRoot },
+    );
+    if (shimExport) {
+      result[cliPath] = shimExport;
+    }
+  }
+  return result;
+}
+
+/**
+ * Browser-compat shim — preserves the `./test/browser-compat` surface from
+ * the deleted wrapper. Re-exports the four symbols vitest's own browser
+ * plugin spotchecks for when treating a package as a browser provider
+ * override target.
+ */
+async function createBrowserCompatExport(testDistDir: string): Promise<ExportValue> {
+  const dir = testDistDir;
+  await mkdir(dir, { recursive: true });
+  const symbols = [
+    'asLocator',
+    'defineBrowserCommand',
+    'defineBrowserProvider',
+    'parseKeyDef',
+    'resolveScreenshotPath',
+  ];
+  const jsPath = join(dir, 'browser-compat.js');
+  const dtsPath = join(dir, 'browser-compat.d.ts');
+  await writeFile(jsPath, `export { ${symbols.join(', ')} } from '@vitest/browser';\n`);
+  await writeFile(
+    dtsPath,
+    `import '@vitest/browser';\nexport { ${symbols.join(', ')} } from '@vitest/browser';\n`,
+  );
+  return {
+    types: './dist/test/browser-compat.d.ts',
+    default: './dist/test/browser-compat.js',
+  };
+}
+
 /**
  * Read version from a dependency's package.json in node_modules.
  * Uses readFile because these packages don't export ./package.json.
@@ -347,8 +621,8 @@ async function readDepVersion(packageName: string): Promise<string | null> {
  * Generate ./versions export module with bundled tool versions.
  *
  * Collects versions from:
- * - core/test package.json bundledVersions (vite, rolldown, tsdown, vitest)
- * - CLI dependency package.json (oxlint, oxfmt, oxlint-tsgolint)
+ * - core package.json bundledVersions (vite, rolldown, tsdown)
+ * - CLI dependency package.json (oxlint, oxfmt, oxlint-tsgolint, vitest)
  *
  * Generates dist/versions.js and dist/versions.d.ts with inlined constants.
  */
@@ -356,15 +630,14 @@ async function syncVersionsExport() {
   console.log('\nSyncing versions export...');
   const distDir = join(projectDir, 'dist');
 
-  // Collect versions from bundledVersions (core + test)
+  // Collect bundled versions from the core package
   const versions: Record<string, string> = {
     ...(corePkg as Record<string, any>).bundledVersions,
-    ...(testPkg as Record<string, any>).bundledVersions,
   };
 
-  // Collect versions from CLI dependencies (oxlint, oxfmt, oxlint-tsgolint)
-  // These don't export ./package.json, so we read from node_modules directly
-  const depTools = ['oxlint', 'oxfmt', 'oxlint-tsgolint'] as const;
+  // Read versions from CLI dependencies' installed package.json files
+  // (these packages don't export ./package.json, so node_modules is the source of truth)
+  const depTools = ['oxlint', 'oxfmt', 'oxlint-tsgolint', 'vitest'] as const;
   for (const name of depTools) {
     const version = await readDepVersion(name);
     if (version) {
@@ -465,19 +738,189 @@ type ExportValue =
     };
 
 /**
- * Create shim file(s) for a single export and return the export entry for package.json
+ * Write private shims at `dist/test/_at-vitest-browser{.d.ts,/context.d.ts}`
+ * that re-export the `@vitest/browser` package. These are referenced by the
+ * inlined browser-provider d.ts shims via relative paths so all of
+ * `@vitest/browser`, `vitest/node`, etc. resolve through vite-plus's own
+ * pnpm-edge — the same edge that owns vite-plus's `vitest` direct dep.
+ * The underscore prefix marks them as private; they are not surfaced in the
+ * package.json `exports` map (TS resolves the relative paths directly).
+ */
+async function writePrivateAtVitestBrowserShims(testDistDir: string): Promise<void> {
+  await mkdir(join(testDistDir, '_at-vitest-browser'), { recursive: true });
+  await writeFile(
+    join(testDistDir, '_at-vitest-browser.d.ts'),
+    `import '@vitest/browser';\nexport * from '@vitest/browser';\n`,
+  );
+  await writeFile(
+    join(testDistDir, '_at-vitest-browser/context.d.ts'),
+    `import '@vitest/browser/context';\nexport * from '@vitest/browser/context';\n`,
+  );
+}
+
+/**
+ * Write a JS shim for a provider-`/context` export and amend the export entry
+ * with a runtime target.
+ *
+ * Upstream `@vitest/browser-<provider>/context` is declared types-only (its
+ * `context.d.ts` simply re-exports from `@vitest/browser/context`). After the
+ * migration rewrites `@vitest/browser-<provider>/context` →
+ * `vite-plus/test/browser-<provider>/context`, Node ESM resolution fails with
+ * ERR_PACKAGE_PATH_NOT_EXPORTED unless the export entry has a `default`/`import`
+ * target. We re-export from `@vitest/browser/context` so the bundled
+ * `@vitest/browser` (vite-plus's own pnpm-edge) is reached at runtime.
+ */
+async function ensureContextRuntimeShim(
+  shimBaseName: string,
+  testDistDir: string,
+  shimExport: ExportValue,
+): Promise<void> {
+  if (typeof shimExport !== 'object' || shimExport === null) {
+    return;
+  }
+  const entry = shimExport as Record<string, unknown>;
+  if (entry.default || entry.import) {
+    return;
+  }
+  const jsRelPath = `./dist/test/${shimBaseName}.js`;
+  const jsAbsPath = join(testDistDir, `${shimBaseName}.js`);
+  await mkdir(dirname(jsAbsPath), { recursive: true });
+  await writeFile(jsAbsPath, `export * from '@vitest/browser/context';\n`);
+  entry.default = jsRelPath;
+}
+
+/**
+ * Build the `./test/browser/context` export entry and write its JS/d.ts shims.
+ *
+ * Vitest's package.json only exposes `./browser` (mapped to `./test/browser`).
+ * The migration rewrites `@vitest/browser/context` →
+ * `vite-plus/test/browser/context`, so we add this path with both runtime and
+ * type targets that re-export from `@vitest/browser/context`.
+ */
+async function createBrowserContextExport(testDistDir: string): Promise<ExportValue> {
+  const dir = join(testDistDir, 'browser');
+  await mkdir(dir, { recursive: true });
+  await writeFile(join(dir, 'context.js'), `export * from '@vitest/browser/context';\n`);
+  await writeFile(
+    join(dir, 'context.d.ts'),
+    `import '@vitest/browser/context';\nexport * from '@vitest/browser/context';\n`,
+  );
+  return {
+    types: './dist/test/browser/context.d.ts',
+    default: './dist/test/browser/context.js',
+  };
+}
+
+/**
+ * Inline-copy a browser-provider's upstream `.d.ts` file into `outDtsPath` and
+ * rewrite vitest-related bare specifiers to relative paths inside the
+ * vite-plus test shim tree. See `VITEST_TYPE_SPECIFIER_REWRITES` and
+ * `writePrivateAtVitestBrowserShims` for the rationale.
+ *
+ * Specifiers that are user peer dependencies (`playwright`, `webdriverio`,
+ * `tinyrainbow`, etc.) and the `@vitest/browser-*` self-import are left bare.
+ */
+async function writeInlinedProviderDts(
+  outDtsPath: string,
+  upstreamDtsPath: string,
+  testDistDir: string,
+): Promise<void> {
+  const upstream = await readFile(upstreamDtsPath, 'utf-8');
+  const outDir = dirname(outDtsPath);
+  // Resolve to the file basename appended to the relative dir, never to the
+  // bare directory. `relative('dist/test/x/', 'dist/test/y')` returns `'../y'`
+  // but `relative('dist/test/x/', 'dist/test/y/')` returns `'..'` — TS would
+  // then look for `dist/test/y/index.d.ts` instead of `dist/test/y.d.ts`. We
+  // always emit `<relDir>/<basename>` so the basename lookup hits the file.
+  const relToShim = (sub: string): string => {
+    const r = relative(outDir, testDistDir).replaceAll('\\', '/');
+    const prefix = r === '' ? '.' : r.startsWith('.') ? r : `./${r}`;
+    return `${prefix}/${sub}`;
+  };
+  let result = upstream;
+  for (const [bare, sub] of VITEST_TYPE_SPECIFIER_REWRITES) {
+    const escaped = bare.replaceAll('/', '\\/');
+    const pattern = new RegExp(`(['"])${escaped}\\1`, 'g');
+    result = result.replaceAll(pattern, `'${relToShim(sub)}'`);
+  }
+  await mkdir(outDir, { recursive: true });
+  await writeFile(outDtsPath, result);
+}
+
+/**
+ * Resolve the upstream `.d.ts` path for a given export value. Returns null
+ * when the export does not declare a types file (runtime-only exports).
+ */
+function resolveUpstreamDtsPath(
+  providerPkgRoot: string,
+  exportValue: unknown,
+  condition: 'types' | 'require-types' = 'types',
+): string | null {
+  if (typeof exportValue === 'string') {
+    return exportValue.endsWith('.d.ts') || exportValue.endsWith('.d.cts')
+      ? join(providerPkgRoot, exportValue)
+      : null;
+  }
+  if (typeof exportValue !== 'object' || exportValue === null) {
+    return null;
+  }
+  const value = exportValue as Record<string, unknown>;
+  if (condition === 'types') {
+    if (typeof value.types === 'string') {
+      return join(providerPkgRoot, value.types);
+    }
+    if (typeof value.import === 'object' && value.import !== null) {
+      const types = (value.import as Record<string, unknown>).types;
+      if (typeof types === 'string') {
+        return join(providerPkgRoot, types);
+      }
+    }
+  } else {
+    if (typeof value.require === 'object' && value.require !== null) {
+      const types = (value.require as Record<string, unknown>).types;
+      if (typeof types === 'string') {
+        return join(providerPkgRoot, types);
+      }
+    }
+  }
+  return null;
+}
+
+async function writeShimDts(
+  outDtsPath: string,
+  importSpecifier: string,
+  upstreamDtsPath: string | null,
+  testDistDir: string,
+): Promise<void> {
+  if (upstreamDtsPath) {
+    await writeInlinedProviderDts(outDtsPath, upstreamDtsPath, testDistDir);
+    return;
+  }
+  // Include side-effect import to preserve module augmentations (e.g., toMatchSnapshot on Assertion)
+  await writeFile(
+    outDtsPath,
+    `import '${importSpecifier}';\nexport * from '${importSpecifier}';\n`,
+  );
+}
+
+/**
+ * Create shim file(s) for a single export and return the export entry for package.json.
+ *
+ * @param shimBaseName Path under dist/test/ (e.g. 'index', 'config', 'browser-playwright/context').
+ * @param exportValue  The upstream package's export value for this entry.
+ * @param testImportSpecifier The bare import specifier the shim should re-export from
+ *   (e.g. 'vitest', 'vitest/node', '@vitest/browser-playwright').
+ * @param distDir      Output dist/test directory.
+ * @param opts         Optional shim context. Pass `providerPkgRoot` for browser-provider
+ *                     packages to inline-copy their upstream d.ts content with specifier rewrites.
  */
 async function createShimForExport(
-  exportPath: string,
+  shimBaseName: string,
   exportValue: unknown,
+  testImportSpecifier: string,
   distDir: string,
+  opts: { providerPkgRoot?: string } = {},
 ): Promise<ExportValue | null> {
-  // Determine the import specifier for the test package
-  const testImportSpecifier =
-    exportPath === '.' ? TEST_PACKAGE_NAME : `${TEST_PACKAGE_NAME}${exportPath.slice(1)}`;
-
-  // Convert export path to file path: ./foo/bar -> foo/bar, . -> index
-  const shimBaseName = exportPath === '.' ? 'index' : exportPath.slice(2);
   const shimDir = join(distDir, dirname(shimBaseName));
   await mkdir(shimDir, { recursive: true });
 
@@ -490,11 +933,10 @@ async function createShimForExport(
     // Check if it's a type-only export
     if (exportValue.endsWith('.d.ts')) {
       const dtsPath = join(shimDirForFile, `${baseFileName}.d.ts`);
-      // Include side-effect import to preserve module augmentations (e.g., toMatchSnapshot on Assertion)
-      await writeFile(
-        dtsPath,
-        `import '${testImportSpecifier}';\nexport * from '${testImportSpecifier}';\n`,
-      );
+      const upstream = opts.providerPkgRoot
+        ? resolveUpstreamDtsPath(opts.providerPkgRoot, exportValue, 'types')
+        : null;
+      await writeShimDts(dtsPath, testImportSpecifier, upstream, distDir);
       return { types: `./dist/test/${shimBaseName}.d.ts` };
     }
 
@@ -514,6 +956,8 @@ async function createShimForExport(
         shimDirForFile,
         baseFileName,
         shimBaseName,
+        distDir,
+        opts,
       );
     }
 
@@ -522,11 +966,10 @@ async function createShimForExport(
 
     if (value.types && typeof value.types === 'string') {
       const dtsPath = join(shimDirForFile, `${baseFileName}.d.ts`);
-      // Include side-effect import to preserve module augmentations (e.g., toMatchSnapshot on Assertion)
-      await writeFile(
-        dtsPath,
-        `import '${testImportSpecifier}';\nexport * from '${testImportSpecifier}';\n`,
-      );
+      const upstream = opts.providerPkgRoot
+        ? resolveUpstreamDtsPath(opts.providerPkgRoot, value, 'types')
+        : null;
+      await writeShimDts(dtsPath, testImportSpecifier, upstream, distDir);
       (result as Record<string, string>).types = `./dist/test/${shimBaseName}.d.ts`;
     }
 
@@ -549,6 +992,11 @@ async function createShimForExport(
  *   { import: { types, node, default }, require: { types, default } }
  * And flat structures like:
  *   { types, require, default }
+ *
+ * Insertion order matters: Node.js package-exports conditions are order-sensitive.
+ * For dual-condition entries, `require` MUST come before `default` so that
+ * `require('vite-plus/test/config')` resolves to the `.cjs` shim instead of
+ * matching the catch-all `default` (which would point at the ESM file).
  */
 async function createConditionalShim(
   value: Record<string, unknown>,
@@ -556,25 +1004,23 @@ async function createConditionalShim(
   shimDir: string,
   baseFileName: string,
   shimBaseName: string,
+  distDir: string,
+  opts: { providerPkgRoot?: string } = {},
 ): Promise<ExportValue> {
-  const result: ExportValue = {};
+  // Build entries as an array of tuples so we control insertion order explicitly.
+  // Final order for flat entries: types, import (if present), require, default.
+  // `require` MUST come before `default` — `default` matches everything, so
+  // putting it first makes the `require` branch unreachable for CJS consumers.
+  const entries: Array<[string, ExportValue]> = [];
 
   // Handle top-level types (flat structure like { types, require, default })
   if (value.types && typeof value.types === 'string' && !value.import) {
     const dtsPath = join(shimDir, `${baseFileName}.d.ts`);
-    // Include side-effect import to preserve module augmentations (e.g., toMatchSnapshot on Assertion)
-    await writeFile(
-      dtsPath,
-      `import '${testImportSpecifier}';\nexport * from '${testImportSpecifier}';\n`,
-    );
-    (result as Record<string, string>).types = `./dist/test/${shimBaseName}.d.ts`;
-  }
-
-  // Handle top-level default (flat structure, only when no import condition)
-  if (value.default && typeof value.default === 'string' && !value.import) {
-    const jsPath = join(shimDir, `${baseFileName}.js`);
-    await writeFile(jsPath, `export * from '${testImportSpecifier}';\n`);
-    (result as Record<string, string>).default = `./dist/test/${shimBaseName}.js`;
+    const upstream = opts.providerPkgRoot
+      ? resolveUpstreamDtsPath(opts.providerPkgRoot, value, 'types')
+      : null;
+    await writeShimDts(dtsPath, testImportSpecifier, upstream, distDir);
+    entries.push(['types', `./dist/test/${shimBaseName}.d.ts`]);
   }
 
   // Handle import condition
@@ -584,17 +1030,16 @@ async function createConditionalShim(
     if (typeof importValue === 'string') {
       const jsPath = join(shimDir, `${baseFileName}.js`);
       await writeFile(jsPath, `export * from '${testImportSpecifier}';\n`);
-      (result as Record<string, unknown>).import = `./dist/test/${shimBaseName}.js`;
+      entries.push(['import', `./dist/test/${shimBaseName}.js`]);
     } else if (typeof importValue === 'object' && importValue !== null) {
       const importResult: Record<string, string> = {};
 
       if (importValue.types && typeof importValue.types === 'string') {
         const dtsPath = join(shimDir, `${baseFileName}.d.ts`);
-        // Include side-effect import to preserve module augmentations (e.g., toMatchSnapshot on Assertion)
-        await writeFile(
-          dtsPath,
-          `import '${testImportSpecifier}';\nexport * from '${testImportSpecifier}';\n`,
-        );
+        const upstream = opts.providerPkgRoot
+          ? resolveUpstreamDtsPath(opts.providerPkgRoot, value, 'types')
+          : null;
+        await writeShimDts(dtsPath, testImportSpecifier, upstream, distDir);
         importResult.types = `./dist/test/${shimBaseName}.d.ts`;
       }
 
@@ -609,28 +1054,28 @@ async function createConditionalShim(
         importResult.default = `./dist/test/${shimBaseName}.js`;
       }
 
-      result.import = importResult;
+      entries.push(['import', importResult]);
     }
   }
 
-  // Handle require condition
+  // Handle require condition — emitted BEFORE `default` so CJS resolution
+  // picks the `.cjs` shim instead of the catch-all `default` entry.
   if (value.require) {
     const requireValue = value.require as Record<string, unknown>;
 
     if (typeof requireValue === 'string') {
       const cjsPath = join(shimDir, `${baseFileName}.cjs`);
       await writeFile(cjsPath, `module.exports = require('${testImportSpecifier}');\n`);
-      result.require = `./dist/test/${shimBaseName}.cjs`;
+      entries.push(['require', `./dist/test/${shimBaseName}.cjs`]);
     } else if (typeof requireValue === 'object' && requireValue !== null) {
       const requireResult: Record<string, string> = {};
 
       if (requireValue.types && typeof requireValue.types === 'string') {
         const dctsPath = join(shimDir, `${baseFileName}.d.cts`);
-        // Include side-effect import to preserve module augmentations (e.g., toMatchSnapshot on Assertion)
-        await writeFile(
-          dctsPath,
-          `import '${testImportSpecifier}';\nexport * from '${testImportSpecifier}';\n`,
-        );
+        const upstream = opts.providerPkgRoot
+          ? resolveUpstreamDtsPath(opts.providerPkgRoot, value, 'require-types')
+          : null;
+        await writeShimDts(dctsPath, testImportSpecifier, upstream, distDir);
         requireResult.types = `./dist/test/${shimBaseName}.d.cts`;
       }
 
@@ -640,11 +1085,20 @@ async function createConditionalShim(
         requireResult.default = `./dist/test/${shimBaseName}.cjs`;
       }
 
-      result.require = requireResult;
+      entries.push(['require', requireResult]);
     }
   }
 
-  return result;
+  // Handle top-level default (flat structure, only when no import condition).
+  // Emitted LAST among siblings so `require` (and any specific condition)
+  // wins resolution against the catch-all `default`.
+  if (value.default && typeof value.default === 'string' && !value.import) {
+    const jsPath = join(shimDir, `${baseFileName}.js`);
+    await writeFile(jsPath, `export * from '${testImportSpecifier}';\n`);
+    entries.push(['default', `./dist/test/${shimBaseName}.js`]);
+  }
+
+  return Object.fromEntries(entries) as ExportValue;
 }
 
 /**
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 89c10c0501..adac40b119 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -96,7 +96,6 @@
     "./test": {
       "import": {
         "types": "./dist/test/index.d.ts",
-        "node": "./dist/test/index.js",
         "default": "./dist/test/index.js"
       },
       "require": {
@@ -148,8 +147,8 @@
     },
     "./test/config": {
       "types": "./dist/test/config.d.ts",
-      "default": "./dist/test/config.js",
-      "require": "./dist/test/config.cjs"
+      "require": "./dist/test/config.cjs",
+      "default": "./dist/test/config.js"
     },
     "./test/coverage": {
       "types": "./dist/test/coverage.d.ts",
@@ -171,8 +170,57 @@
       "types": "./dist/test/worker.d.ts",
       "default": "./dist/test/worker.js"
     },
-    "./test/browser-compat": {
-      "default": "./dist/test/browser-compat.js"
+    "./test/browser-playwright": {
+      "types": "./dist/test/browser-playwright.d.ts",
+      "default": "./dist/test/browser-playwright.js"
+    },
+    "./test/browser/providers/playwright": {
+      "types": "./dist/test/browser/providers/playwright.d.ts",
+      "default": "./dist/test/browser/providers/playwright.js"
+    },
+    "./test/browser-playwright/context": {
+      "types": "./dist/test/browser-playwright/context.d.ts",
+      "default": "./dist/test/browser-playwright/context.js"
+    },
+    "./test/browser/providers/playwright/context": {
+      "types": "./dist/test/browser/providers/playwright/context.d.ts",
+      "default": "./dist/test/browser/providers/playwright/context.js"
+    },
+    "./test/browser-preview": {
+      "types": "./dist/test/browser-preview.d.ts",
+      "default": "./dist/test/browser-preview.js"
+    },
+    "./test/browser/providers/preview": {
+      "types": "./dist/test/browser/providers/preview.d.ts",
+      "default": "./dist/test/browser/providers/preview.js"
+    },
+    "./test/browser-preview/context": {
+      "types": "./dist/test/browser-preview/context.d.ts",
+      "default": "./dist/test/browser-preview/context.js"
+    },
+    "./test/browser/providers/preview/context": {
+      "types": "./dist/test/browser/providers/preview/context.d.ts",
+      "default": "./dist/test/browser/providers/preview/context.js"
+    },
+    "./test/browser-webdriverio": {
+      "types": "./dist/test/browser-webdriverio.d.ts",
+      "default": "./dist/test/browser-webdriverio.js"
+    },
+    "./test/browser/providers/webdriverio": {
+      "types": "./dist/test/browser/providers/webdriverio.d.ts",
+      "default": "./dist/test/browser/providers/webdriverio.js"
+    },
+    "./test/browser-webdriverio/context": {
+      "types": "./dist/test/browser-webdriverio/context.d.ts",
+      "default": "./dist/test/browser-webdriverio/context.js"
+    },
+    "./test/browser/providers/webdriverio/context": {
+      "types": "./dist/test/browser/providers/webdriverio/context.d.ts",
+      "default": "./dist/test/browser/providers/webdriverio/context.js"
+    },
+    "./test/browser/context": {
+      "types": "./dist/test/browser/context.d.ts",
+      "default": "./dist/test/browser/context.js"
     },
     "./test/client": {
       "default": "./dist/test/client.js"
@@ -181,42 +229,20 @@
       "types": "./dist/test/context.d.ts",
       "default": "./dist/test/context.js"
     },
-    "./test/browser/context": {
-      "types": "./dist/test/browser/context.d.ts",
-      "default": "./dist/test/browser/context.js"
-    },
     "./test/locators": {
+      "types": "./dist/test/locators.d.ts",
       "default": "./dist/test/locators.js"
     },
     "./test/matchers": {
+      "types": "./dist/test/matchers.d.ts",
       "default": "./dist/test/matchers.js"
     },
     "./test/utils": {
       "default": "./dist/test/utils.js"
     },
-    "./test/browser-playwright": {
-      "types": "./dist/test/browser-playwright.d.ts",
-      "default": "./dist/test/browser-playwright.js"
-    },
-    "./test/browser-webdriverio": {
-      "types": "./dist/test/browser-webdriverio.d.ts",
-      "default": "./dist/test/browser-webdriverio.js"
-    },
-    "./test/browser-preview": {
-      "types": "./dist/test/browser-preview.d.ts",
-      "default": "./dist/test/browser-preview.js"
-    },
-    "./test/browser/providers/playwright": {
-      "types": "./dist/test/browser/providers/playwright.d.ts",
-      "default": "./dist/test/browser/providers/playwright.js"
-    },
-    "./test/browser/providers/webdriverio": {
-      "types": "./dist/test/browser/providers/webdriverio.d.ts",
-      "default": "./dist/test/browser/providers/webdriverio.js"
-    },
-    "./test/browser/providers/preview": {
-      "types": "./dist/test/browser/providers/preview.d.ts",
-      "default": "./dist/test/browser/providers/preview.js"
+    "./test/browser-compat": {
+      "types": "./dist/test/browser-compat.d.ts",
+      "default": "./dist/test/browser-compat.js"
     },
     "./test/plugins/runner": {
       "default": "./dist/test/plugins/runner.js"
@@ -248,9 +274,6 @@
     "./test/plugins/utils-timers": {
       "default": "./dist/test/plugins/utils-timers.js"
     },
-    "./test/plugins/utils-highlight": {
-      "default": "./dist/test/plugins/utils-highlight.js"
-    },
     "./test/plugins/utils-offset": {
       "default": "./dist/test/plugins/utils-offset.js"
     },
@@ -340,11 +363,20 @@
   "dependencies": {
     "@oxc-project/types": "catalog:",
     "@oxlint/plugins": "catalog:",
+    "@vitest/browser": "catalog:",
+    "@vitest/browser-preview": "catalog:",
+    "@vitest/expect": "catalog:",
+    "@vitest/mocker": "catalog:",
+    "@vitest/pretty-format": "catalog:",
+    "@vitest/runner": "catalog:",
+    "@vitest/snapshot": "catalog:",
+    "@vitest/spy": "catalog:",
+    "@vitest/utils": "catalog:",
     "@voidzero-dev/vite-plus-core": "workspace:*",
-    "@voidzero-dev/vite-plus-test": "workspace:*",
     "oxfmt": "catalog:",
     "oxlint": "catalog:",
-    "oxlint-tsgolint": "catalog:"
+    "oxlint-tsgolint": "catalog:",
+    "vitest": "catalog:"
   },
   "devDependencies": {
     "@napi-rs/cli": "catalog:",
@@ -353,6 +385,8 @@
     "@types/cross-spawn": "catalog:",
     "@types/semver": "catalog:",
     "@types/validate-npm-package-name": "catalog:",
+    "@vitest/browser-playwright": "catalog:",
+    "@vitest/browser-webdriverio": "catalog:",
     "@voidzero-dev/vite-plus-prompts": "workspace:*",
     "@voidzero-dev/vite-plus-tools": "workspace:*",
     "bingo": "catalog:",
@@ -375,6 +409,18 @@
     "yaml": "catalog:",
     "zod": "catalog:"
   },
+  "peerDependencies": {
+    "@vitest/browser-playwright": "catalog:",
+    "@vitest/browser-webdriverio": "catalog:"
+  },
+  "peerDependenciesMeta": {
+    "@vitest/browser-playwright": {
+      "optional": true
+    },
+    "@vitest/browser-webdriverio": {
+      "optional": true
+    }
+  },
   "napi": {
     "binaryName": "vite-plus",
     "packageName": "@voidzero-dev/vite-plus",
diff --git a/packages/cli/publish-native-addons.ts b/packages/cli/publish-native-addons.ts
index ed9c5c51f2..b9dfb3dd35 100644
--- a/packages/cli/publish-native-addons.ts
+++ b/packages/cli/publish-native-addons.ts
@@ -53,12 +53,6 @@ if (existsSync(rustCliArtifactsDir)) {
   }
 }
 
-// Build test package — versions are already bumped on main by prepare_release.yml.
-execSync('pnpm --filter=@voidzero-dev/vite-plus-test build', {
-  cwd: repoRoot,
-  stdio: 'inherit',
-});
-
 // Create npm directories for NAPI bindings
 await cli.createNpmDirs({
   cwd: currentDir,
diff --git a/packages/cli/snap-tests-global/command-install-auto-create-package-json/snap.txt b/packages/cli/snap-tests-global/command-install-auto-create-package-json/snap.txt
index 539887a21f..ccc942b871 100644
--- a/packages/cli/snap-tests-global/command-install-auto-create-package-json/snap.txt
+++ b/packages/cli/snap-tests-global/command-install-auto-create-package-json/snap.txt
@@ -14,7 +14,6 @@ no package.json
 }
 
 > vp add testnpm2 -D && cat package.json # should add package to auto-created package.json
-✓ Lockfile passes supply-chain policies (verified <variable>ms ago)
 Packages: +<variable>
 +
 Progress: resolved <variable>, reused <variable>, downloaded <variable>, added <variable>, done
diff --git a/packages/cli/snap-tests-global/create-from-bun-monorepo-subdir/package.json b/packages/cli/snap-tests-global/create-from-bun-monorepo-subdir/package.json
index 488755aac1..0047e9e07d 100644
--- a/packages/cli/snap-tests-global/create-from-bun-monorepo-subdir/package.json
+++ b/packages/cli/snap-tests-global/create-from-bun-monorepo-subdir/package.json
@@ -10,7 +10,7 @@
     ],
     "catalog": {
       "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-      "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
+      "vitest": "^4.0.0",
       "vite-plus": "latest"
     }
   },
diff --git a/packages/cli/snap-tests-global/create-from-bun-monorepo-subdir/snap.txt b/packages/cli/snap-tests-global/create-from-bun-monorepo-subdir/snap.txt
index 510ea84d45..afb64636d5 100644
--- a/packages/cli/snap-tests-global/create-from-bun-monorepo-subdir/snap.txt
+++ b/packages/cli/snap-tests-global/create-from-bun-monorepo-subdir/snap.txt
@@ -26,7 +26,7 @@ Created at packages/vite-plus-library
     ],
     "catalog": {
       "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-      "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
+      "vitest": "^4.0.0",
       "vite-plus": "latest"
     }
   },
diff --git a/packages/cli/snap-tests-global/migration-add-git-hooks/snap.txt b/packages/cli/snap-tests-global/migration-add-git-hooks/snap.txt
index 41af05c3ec..3ecc5a9256 100644
--- a/packages/cli/snap-tests-global/migration-add-git-hooks/snap.txt
+++ b/packages/cli/snap-tests-global/migration-add-git-hooks/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-already-vite-plus/snap.txt b/packages/cli/snap-tests-global/migration-already-vite-plus/snap.txt
index 868ba69b18..aa0b62ec9d 100644
--- a/packages/cli/snap-tests-global/migration-already-vite-plus/snap.txt
+++ b/packages/cli/snap-tests-global/migration-already-vite-plus/snap.txt
@@ -1,6 +1,7 @@
-> vp migrate --no-interactive # should detect existing vite-plus and exit without setup defaults
-This project is already using Vite+! Happy coding!
-
+> vp migrate --no-interactive # legacy wrapper-override project: rewrites the stale vitest wrapper override to bundled vitest and completes the missing @vitest/* family pins, no hooks/agent setup defaults
+◇ Migrated . to Vite+
+• Node <semver>  npm <semver>
+• Package manager settings configured
 
 > vp migrate --no-interactive --hooks --agent agents # explicit setup should still update existing vite-plus project
 ◇ Updated .
@@ -15,7 +16,7 @@ This project is already using Vite+! Happy coding!
   },
   "overrides": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vitest": "<semver>"
   },
   "devEngines": {
     "packageManager": {
diff --git a/packages/cli/snap-tests-global/migration-already-vite-plus/steps.json b/packages/cli/snap-tests-global/migration-already-vite-plus/steps.json
index 4321aadfe0..85bc820818 100644
--- a/packages/cli/snap-tests-global/migration-already-vite-plus/steps.json
+++ b/packages/cli/snap-tests-global/migration-already-vite-plus/steps.json
@@ -1,6 +1,6 @@
 {
   "commands": [
-    "vp migrate --no-interactive # should detect existing vite-plus and exit without setup defaults",
+    "vp migrate --no-interactive # legacy wrapper-override project: rewrites the stale vitest wrapper override to bundled vitest and completes the missing @vitest/* family pins, no hooks/agent setup defaults",
     "vp migrate --no-interactive --hooks --agent agents # explicit setup should still update existing vite-plus project",
     "cat package.json # prepare script should be configured for vp config",
     "test -f AGENTS.md # explicit agent instructions should be written",
diff --git a/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt b/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt
index 312d5927f3..1192b20cdd 100644
--- a/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt
+++ b/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt
@@ -57,7 +57,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt b/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt
index 5fe49316c0..5d31e92cc2 100644
--- a/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt
+++ b/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt
@@ -60,7 +60,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt b/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt
index e26f94cecb..377a73d062 100644
--- a/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt
+++ b/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-composed-husky-custom-dir/snap.txt b/packages/cli/snap-tests-global/migration-composed-husky-custom-dir/snap.txt
index 13435e190f..060b656fab 100644
--- a/packages/cli/snap-tests-global/migration-composed-husky-custom-dir/snap.txt
+++ b/packages/cli/snap-tests-global/migration-composed-husky-custom-dir/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-composed-husky-prepare/snap.txt b/packages/cli/snap-tests-global/migration-composed-husky-prepare/snap.txt
index 102046cb59..62670a4322 100644
--- a/packages/cli/snap-tests-global/migration-composed-husky-prepare/snap.txt
+++ b/packages/cli/snap-tests-global/migration-composed-husky-prepare/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt
index 63e82d65e9..1739bfda66 100644
--- a/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt
+++ b/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt
index 21a7cb5060..46060a3184 100644
--- a/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt
+++ b/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt b/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt
index 0889d48721..dbeea0338d 100644
--- a/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt
+++ b/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-eslint-npx-wrapper/snap.txt b/packages/cli/snap-tests-global/migration-eslint-npx-wrapper/snap.txt
index e15d79dd1c..eae3f8790e 100644
--- a/packages/cli/snap-tests-global/migration-eslint-npx-wrapper/snap.txt
+++ b/packages/cli/snap-tests-global/migration-eslint-npx-wrapper/snap.txt
@@ -32,7 +32,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-eslint/snap.txt b/packages/cli/snap-tests-global/migration-eslint/snap.txt
index 0eebe7a0ee..fea606b7f3 100644
--- a/packages/cli/snap-tests-global/migration-eslint/snap.txt
+++ b/packages/cli/snap-tests-global/migration-eslint/snap.txt
@@ -30,7 +30,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt
index 8259f6bc78..d2c1c68a13 100644
--- a/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt
+++ b/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-existing-husky-v8-hooks/snap.txt b/packages/cli/snap-tests-global/migration-existing-husky-v8-hooks/snap.txt
index 998eb30281..5d26bc1549 100644
--- a/packages/cli/snap-tests-global/migration-existing-husky-v8-hooks/snap.txt
+++ b/packages/cli/snap-tests-global/migration-existing-husky-v8-hooks/snap.txt
@@ -30,7 +30,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-existing-husky-v8-multi-hooks/snap.txt b/packages/cli/snap-tests-global/migration-existing-husky-v8-multi-hooks/snap.txt
index fd5a5b547a..d848a1259c 100644
--- a/packages/cli/snap-tests-global/migration-existing-husky-v8-multi-hooks/snap.txt
+++ b/packages/cli/snap-tests-global/migration-existing-husky-v8-multi-hooks/snap.txt
@@ -30,7 +30,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-existing-husky/snap.txt b/packages/cli/snap-tests-global/migration-existing-husky/snap.txt
index 4fe839f50c..cb5a7637e8 100644
--- a/packages/cli/snap-tests-global/migration-existing-husky/snap.txt
+++ b/packages/cli/snap-tests-global/migration-existing-husky/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt b/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt
index 39372d494b..940fa1c0aa 100644
--- a/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt
+++ b/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt
index 1af81eb934..e6c009ca2b 100644
--- a/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt
+++ b/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-existing-prepare-script/snap.txt b/packages/cli/snap-tests-global/migration-existing-prepare-script/snap.txt
index 79a98435ee..5a898b0f28 100644
--- a/packages/cli/snap-tests-global/migration-existing-prepare-script/snap.txt
+++ b/packages/cli/snap-tests-global/migration-existing-prepare-script/snap.txt
@@ -28,7 +28,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt b/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt
index 4d4968b06c..16087a6ade 100644
--- a/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt
+++ b/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt
@@ -52,7 +52,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt b/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt
index 635777ab9d..547d4c1772 100644
--- a/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt
+++ b/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt
@@ -54,7 +54,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-from-vitest-config/snap.txt b/packages/cli/snap-tests-global/migration-from-vitest-config/snap.txt
index 596fb1b7f5..9a6c718500 100644
--- a/packages/cli/snap-tests-global/migration-from-vitest-config/snap.txt
+++ b/packages/cli/snap-tests-global/migration-from-vitest-config/snap.txt
@@ -8,7 +8,7 @@ import { join } from 'node:path';
 
 import { foo } from '@foo/vite-plugin-foo';
 import { playwright } from 'vite-plus/test/browser-playwright';
-import { server } from 'vite-plus/test/browser-playwright/context';
+import { server } from 'vite-plus/test/browser/context';
 import { preview } from 'vite-plus/test/browser-preview';
 import { webdriverio } from 'vite-plus/test/browser-webdriverio';
 import { userEvent } from 'vite-plus/test/browser/context';
@@ -40,8 +40,11 @@ export default defineConfig({
     "prepare": "vp config"
   },
   "devDependencies": {
+    "@vitest/browser-playwright": "<semver>",
     "vite": "catalog:",
     "vitest": "catalog:",
+    "@vitest/browser-webdriverio": "<semver>",
+    "webdriverio": "*",
     "playwright": "*",
     "vite-plus": "catalog:"
   },
@@ -57,8 +60,11 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
+allowBuilds:
+  edgedriver: true
+  geckodriver: true
 overrides:
   vite: 'catalog:'
   vitest: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-from-vitest-files/snap.txt b/packages/cli/snap-tests-global/migration-from-vitest-files/snap.txt
index 3c309fbe4f..a72ade1a4e 100644
--- a/packages/cli/snap-tests-global/migration-from-vitest-files/snap.txt
+++ b/packages/cli/snap-tests-global/migration-from-vitest-files/snap.txt
@@ -15,6 +15,7 @@
     "prepare": "vp config"
   },
   "devDependencies": {
+    "@vitest/browser-playwright": "<semver>",
     "vite": "catalog:",
     "vitest": "catalog:",
     "playwright": "*",
@@ -32,7 +33,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
@@ -46,7 +47,7 @@ peerDependencyRules:
     vitest: '*'
 
 > cat test/hello.ts # check test/hello.ts
-import { server } from 'vite-plus/test/browser-playwright/context';
+import { server } from 'vite-plus/test/browser/context';
 import { test, describe, expect, it } from 'vite-plus/test';
 
 const { readFile } = server.commands;
diff --git a/packages/cli/snap-tests-global/migration-hooks-skip-on-existing-hookspath/snap.txt b/packages/cli/snap-tests-global/migration-hooks-skip-on-existing-hookspath/snap.txt
index 6e36c11985..3a30efa064 100644
--- a/packages/cli/snap-tests-global/migration-hooks-skip-on-existing-hookspath/snap.txt
+++ b/packages/cli/snap-tests-global/migration-hooks-skip-on-existing-hookspath/snap.txt
@@ -30,7 +30,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-husky-catalog-version/snap.txt b/packages/cli/snap-tests-global/migration-husky-catalog-version/snap.txt
index 0fcbd13c57..72ce13481b 100644
--- a/packages/cli/snap-tests-global/migration-husky-catalog-version/snap.txt
+++ b/packages/cli/snap-tests-global/migration-husky-catalog-version/snap.txt
@@ -35,7 +35,7 @@ catalog:
   husky: ^9.1.7
   lint-staged: ^16.2.6
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/snap.txt b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/snap.txt
index a5e24d28ce..efd29b79ed 100644
--- a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/snap.txt
+++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/snap.txt b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/snap.txt
index 9b9bb748b8..40ab64b83a 100644
--- a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/snap.txt
+++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/snap.txt
@@ -29,7 +29,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-husky-or-prepare/snap.txt b/packages/cli/snap-tests-global/migration-husky-or-prepare/snap.txt
index c9ba844284..2e91e7579e 100644
--- a/packages/cli/snap-tests-global/migration-husky-or-prepare/snap.txt
+++ b/packages/cli/snap-tests-global/migration-husky-or-prepare/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-husky-semicolon-prepare/snap.txt b/packages/cli/snap-tests-global/migration-husky-semicolon-prepare/snap.txt
index 9660233cbf..3b7b394d0c 100644
--- a/packages/cli/snap-tests-global/migration-husky-semicolon-prepare/snap.txt
+++ b/packages/cli/snap-tests-global/migration-husky-semicolon-prepare/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-husky-v8-preserves-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-husky-v8-preserves-lint-staged/snap.txt
index eb08f0c8f0..20e45a6e33 100644
--- a/packages/cli/snap-tests-global/migration-husky-v8-preserves-lint-staged/snap.txt
+++ b/packages/cli/snap-tests-global/migration-husky-v8-preserves-lint-staged/snap.txt
@@ -33,7 +33,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-lazy-plugins-await/snap.txt b/packages/cli/snap-tests-global/migration-lazy-plugins-await/snap.txt
index 44777909af..95a4844c9a 100644
--- a/packages/cli/snap-tests-global/migration-lazy-plugins-await/snap.txt
+++ b/packages/cli/snap-tests-global/migration-lazy-plugins-await/snap.txt
@@ -36,7 +36,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt b/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt
index 641ea5522d..bfa1bb00f7 100644
--- a/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt
+++ b/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt
@@ -28,7 +28,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-lint-staged-merge-fail/snap.txt b/packages/cli/snap-tests-global/migration-lint-staged-merge-fail/snap.txt
index c997b0d867..ecdad850c3 100644
--- a/packages/cli/snap-tests-global/migration-lint-staged-merge-fail/snap.txt
+++ b/packages/cli/snap-tests-global/migration-lint-staged-merge-fail/snap.txt
@@ -36,7 +36,7 @@ Please add staged config to vite.config.ts manually, see https://viteplus.dev/gu
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-lint-staged-ts-config/snap.txt b/packages/cli/snap-tests-global/migration-lint-staged-ts-config/snap.txt
index 1f401bd846..287d37346a 100644
--- a/packages/cli/snap-tests-global/migration-lint-staged-ts-config/snap.txt
+++ b/packages/cli/snap-tests-global/migration-lint-staged-ts-config/snap.txt
@@ -31,7 +31,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt b/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt
index 26d3da551f..af8f1dd1f5 100644
--- a/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt
+++ b/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt
@@ -100,7 +100,7 @@ Documentation: https://viteplus.dev/guide/migrate
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-lintstagedrc-merge-fail/snap.txt b/packages/cli/snap-tests-global/migration-lintstagedrc-merge-fail/snap.txt
index b4eff71f6f..9b0e74b5d9 100644
--- a/packages/cli/snap-tests-global/migration-lintstagedrc-merge-fail/snap.txt
+++ b/packages/cli/snap-tests-global/migration-lintstagedrc-merge-fail/snap.txt
@@ -33,7 +33,7 @@ Please add staged config to vite.config.ts manually, see https://viteplus.dev/gu
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-lintstagedrc-not-support/snap.txt b/packages/cli/snap-tests-global/migration-lintstagedrc-not-support/snap.txt
index 42d5acbb1b..5d9403d2b3 100644
--- a/packages/cli/snap-tests-global/migration-lintstagedrc-not-support/snap.txt
+++ b/packages/cli/snap-tests-global/migration-lintstagedrc-not-support/snap.txt
@@ -44,7 +44,7 @@ export default {
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt b/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt
index eaccf39880..ba09d0e639 100644
--- a/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt
+++ b/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt
@@ -28,7 +28,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt b/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt
index 0936ac3162..38385db3b2 100644
--- a/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt
+++ b/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt
@@ -58,7 +58,7 @@ export default {
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt b/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt
index 6f359ec29f..d63ee649df 100644
--- a/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt
+++ b/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt
@@ -74,6 +74,7 @@ export default defineConfig({
   },
   "devDependencies": {
     "@vitejs/plugin-react": "^4.2.0",
+    "@vitest/browser-playwright": "<semver>",
     "vite": "catalog:",
     "vitest": "catalog:",
     "playwright": "*",
@@ -91,7 +92,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-monorepo-bun/package.json b/packages/cli/snap-tests-global/migration-monorepo-bun/package.json
index 622a598eda..330320b33b 100644
--- a/packages/cli/snap-tests-global/migration-monorepo-bun/package.json
+++ b/packages/cli/snap-tests-global/migration-monorepo-bun/package.json
@@ -7,7 +7,7 @@
     ],
     "catalog": {
       "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-      "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
+      "vitest": "^4.0.0",
       "vite-plus": "latest"
     }
   },
diff --git a/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt
index dc534d27fd..8a36eae8d9 100644
--- a/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt
+++ b/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt
@@ -45,7 +45,7 @@ export default defineConfig({
     ],
     "catalog": {
       "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-      "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
+      "vitest": "<semver>",
       "vite-plus": "latest"
     }
   },
diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt
index c9f110dec7..b32ff43659 100644
--- a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt
+++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt
@@ -39,7 +39,7 @@ packages:
 
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 
 overrides:
diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt
index abdd0a009f..4181032dc4 100644
--- a/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt
+++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt
@@ -83,7 +83,7 @@ catalog:
   testnpm2: ^1.0.0
   # test comment here to check if the comment is preserved
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 
 minimumReleaseAge: 1440
@@ -106,6 +106,8 @@ minimumReleaseAgeExclude:
   - '@oxlint-tsgolint/*'
   - oxfmt
   - '@oxfmt/*'
+  - vitest
+  - '@vitest/*'
 
 > cat packages/app/package.json # check app package.json
 {
diff --git a/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/package.json b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/package.json
new file mode 100644
index 0000000000..e247e48f3f
--- /dev/null
+++ b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "migration-monorepo-root-vitest-adjacent",
+  "scripts": {
+    "test": "vitest"
+  },
+  "devDependencies": {
+    "vitest-browser-svelte": "^2.1.0"
+  }
+}
diff --git a/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/packages/lib/package.json b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/packages/lib/package.json
new file mode 100644
index 0000000000..1a42355dac
--- /dev/null
+++ b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/packages/lib/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "@repo/lib",
+  "version": "0.0.0"
+}
diff --git a/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/pnpm-workspace.yaml b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/pnpm-workspace.yaml
new file mode 100644
index 0000000000..924b55f42e
--- /dev/null
+++ b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+packages:
+  - packages/*
diff --git a/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/snap.txt
new file mode 100644
index 0000000000..a4e42ec1f2
--- /dev/null
+++ b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/snap.txt
@@ -0,0 +1,25 @@
+> vp migrate --no-interactive # root with a vitest-adjacent dep but no direct vitest still gets a direct vitest pin
+◇ Migrated . to Vite+
+• Node <semver>  pnpm <semver>
+• 2 config updates applied
+
+> cat package.json # vitest pinned at the root even though vite-plus is injected first
+{
+  "name": "migration-monorepo-root-vitest-adjacent",
+  "scripts": {
+    "test": "vp test",
+    "prepare": "vp config"
+  },
+  "devDependencies": {
+    "vitest-browser-svelte": "^2.1.0",
+    "vite-plus": "catalog:",
+    "vitest": "catalog:"
+  },
+  "devEngines": {
+    "packageManager": {
+      "name": "pnpm",
+      "version": "<semver>",
+      "onFail": "download"
+    }
+  }
+}
diff --git a/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/steps.json b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/steps.json
new file mode 100644
index 0000000000..f3d77e97e1
--- /dev/null
+++ b/packages/cli/snap-tests-global/migration-monorepo-root-vitest-adjacent/steps.json
@@ -0,0 +1,6 @@
+{
+  "commands": [
+    "vp migrate --no-interactive # root with a vitest-adjacent dep but no direct vitest still gets a direct vitest pin",
+    "cat package.json # vitest pinned at the root even though vite-plus is injected first"
+  ]
+}
diff --git a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt
index d29b5d8f55..6bd15e4900 100644
--- a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt
+++ b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt
@@ -66,15 +66,18 @@ export default defineConfig({
   "packageManager": "yarn@<semver>",
   "resolutions": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vitest": "<semver>"
   }
 }
 
 > cat .yarnrc.yml # check .yarnrc.yml
 nodeLinker: node-modules
+npmPreapprovedPackages:
+  - vitest
+  - '@vitest/*'
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 
 > cat packages/app/package.json # check app package.json
diff --git a/packages/cli/snap-tests-global/migration-no-git-repo/snap.txt b/packages/cli/snap-tests-global/migration-no-git-repo/snap.txt
index d75196d32f..b7f357bd28 100644
--- a/packages/cli/snap-tests-global/migration-no-git-repo/snap.txt
+++ b/packages/cli/snap-tests-global/migration-no-git-repo/snap.txt
@@ -25,7 +25,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-no-hooks-with-husky/snap.txt b/packages/cli/snap-tests-global/migration-no-hooks-with-husky/snap.txt
index 8b27356cbf..ec9d22ab50 100644
--- a/packages/cli/snap-tests-global/migration-no-hooks-with-husky/snap.txt
+++ b/packages/cli/snap-tests-global/migration-no-hooks-with-husky/snap.txt
@@ -32,7 +32,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-no-hooks/snap.txt b/packages/cli/snap-tests-global/migration-no-hooks/snap.txt
index c3ae0f65c7..99b7a3d9fb 100644
--- a/packages/cli/snap-tests-global/migration-no-hooks/snap.txt
+++ b/packages/cli/snap-tests-global/migration-no-hooks/snap.txt
@@ -23,7 +23,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-other-hook-tool/snap.txt b/packages/cli/snap-tests-global/migration-other-hook-tool/snap.txt
index 5cb0715789..1210230c8a 100644
--- a/packages/cli/snap-tests-global/migration-other-hook-tool/snap.txt
+++ b/packages/cli/snap-tests-global/migration-other-hook-tool/snap.txt
@@ -35,7 +35,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt b/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt
index 721e849ec2..d1718df00c 100644
--- a/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt
+++ b/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt
@@ -55,7 +55,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt b/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt
index 4d39bdc8a1..c96dc92a64 100644
--- a/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt
+++ b/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt
@@ -57,7 +57,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-partially-installed-vite-plus/snap.txt b/packages/cli/snap-tests-global/migration-partially-installed-vite-plus/snap.txt
index 84b3cac9b4..59f7b0f5ed 100644
--- a/packages/cli/snap-tests-global/migration-partially-installed-vite-plus/snap.txt
+++ b/packages/cli/snap-tests-global/migration-partially-installed-vite-plus/snap.txt
@@ -42,7 +42,7 @@
 > cat pnpm-workspace.yaml # pnpm overrides and peerDependencyRules should be configured
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-partially-migrated-pre-commit/snap.txt b/packages/cli/snap-tests-global/migration-partially-migrated-pre-commit/snap.txt
index c98c420300..3d367ba88e 100644
--- a/packages/cli/snap-tests-global/migration-partially-migrated-pre-commit/snap.txt
+++ b/packages/cli/snap-tests-global/migration-partially-migrated-pre-commit/snap.txt
@@ -30,7 +30,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt b/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt
index 0379130d56..a52152e82e 100644
--- a/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt
+++ b/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt
@@ -34,7 +34,7 @@ Prettier configuration detected. Auto-migrating to Oxfmt...
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-prettier-ignore-unknown/snap.txt b/packages/cli/snap-tests-global/migration-prettier-ignore-unknown/snap.txt
index 9c0b20c101..efc29708b4 100644
--- a/packages/cli/snap-tests-global/migration-prettier-ignore-unknown/snap.txt
+++ b/packages/cli/snap-tests-global/migration-prettier-ignore-unknown/snap.txt
@@ -32,7 +32,7 @@ Prettier configuration detected. Auto-migrating to Oxfmt...
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt
index e3fcec9896..3c99c1aaea 100644
--- a/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt
+++ b/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt
@@ -29,7 +29,7 @@ Prettier configuration detected. Auto-migrating to Oxfmt...
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt b/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt
index ecebd3fbbc..14e83cdefa 100644
--- a/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt
+++ b/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt
@@ -30,7 +30,7 @@ Prettier configuration detected. Auto-migrating to Oxfmt...
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-prettier/snap.txt b/packages/cli/snap-tests-global/migration-prettier/snap.txt
index 16e259d3f4..ec5ada2f30 100644
--- a/packages/cli/snap-tests-global/migration-prettier/snap.txt
+++ b/packages/cli/snap-tests-global/migration-prettier/snap.txt
@@ -32,7 +32,7 @@ Prettier configuration detected. Auto-migrating to Oxfmt...
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-rewrite-declare-module/snap.txt b/packages/cli/snap-tests-global/migration-rewrite-declare-module/snap.txt
index 552748805f..e816e2f39a 100644
--- a/packages/cli/snap-tests-global/migration-rewrite-declare-module/snap.txt
+++ b/packages/cli/snap-tests-global/migration-rewrite-declare-module/snap.txt
@@ -20,7 +20,7 @@ declare module 'vite-plus' {
   }
 }
 
-declare module 'vite-plus/test' {
+declare module 'vitest' {
   export const describe: any;
   export const it: any;
   export const expect: any;
@@ -28,7 +28,7 @@ declare module 'vite-plus/test' {
   export const afterAll: any;
 }
 
-declare module 'vite-plus' {
+declare module 'vitest/config' {
   export function defineConfig(config: any): any;
   const _default: typeof defineConfig;
   export default _default;
@@ -55,7 +55,7 @@ declare module 'vite-plus' {
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-skip-vite-dependency/snap.txt b/packages/cli/snap-tests-global/migration-skip-vite-dependency/snap.txt
index 1fe3bc5c63..d74639391b 100644
--- a/packages/cli/snap-tests-global/migration-skip-vite-dependency/snap.txt
+++ b/packages/cli/snap-tests-global/migration-skip-vite-dependency/snap.txt
@@ -50,7 +50,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-skip-vite-peer-dependency/snap.txt b/packages/cli/snap-tests-global/migration-skip-vite-peer-dependency/snap.txt
index 8739cd00b2..29b077788e 100644
--- a/packages/cli/snap-tests-global/migration-skip-vite-peer-dependency/snap.txt
+++ b/packages/cli/snap-tests-global/migration-skip-vite-peer-dependency/snap.txt
@@ -50,7 +50,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-standalone-npm/snap.txt b/packages/cli/snap-tests-global/migration-standalone-npm/snap.txt
index 54f57fb5b5..1bcde4d80e 100644
--- a/packages/cli/snap-tests-global/migration-standalone-npm/snap.txt
+++ b/packages/cli/snap-tests-global/migration-standalone-npm/snap.txt
@@ -9,13 +9,13 @@
   "name": "migration-standalone-npm",
   "devDependencies": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
+    "vitest": "<semver>",
     "vite-plus": "latest"
   },
   "packageManager": "npm@<semver>",
   "overrides": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vitest": "<semver>"
   }
 }
 
diff --git a/packages/cli/snap-tests-global/migration-standalone-pnpm/snap.txt b/packages/cli/snap-tests-global/migration-standalone-pnpm/snap.txt
index 93b4928ac8..53b010c9be 100644
--- a/packages/cli/snap-tests-global/migration-standalone-pnpm/snap.txt
+++ b/packages/cli/snap-tests-global/migration-standalone-pnpm/snap.txt
@@ -18,7 +18,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides, peerDependencyRules, and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-subpath/snap.txt b/packages/cli/snap-tests-global/migration-subpath/snap.txt
index 71b2547c19..c327651108 100644
--- a/packages/cli/snap-tests-global/migration-subpath/snap.txt
+++ b/packages/cli/snap-tests-global/migration-subpath/snap.txt
@@ -44,7 +44,7 @@ core.hooksPath is not set
 > cat foo/pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt b/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt
index bca6478bef..ae26c63e1e 100644
--- a/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt
+++ b/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt
@@ -47,7 +47,7 @@ export default defineConfig({
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-vite-version/snap.txt b/packages/cli/snap-tests-global/migration-vite-version/snap.txt
index 4d6f2fe59e..9cdfbfdcad 100644
--- a/packages/cli/snap-tests-global/migration-vite-version/snap.txt
+++ b/packages/cli/snap-tests-global/migration-vite-version/snap.txt
@@ -27,7 +27,7 @@
 > cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/migration-vitest-peer-dep/snap.txt b/packages/cli/snap-tests-global/migration-vitest-peer-dep/snap.txt
index 04dcaa3390..e6e3ef859a 100644
--- a/packages/cli/snap-tests-global/migration-vitest-peer-dep/snap.txt
+++ b/packages/cli/snap-tests-global/migration-vitest-peer-dep/snap.txt
@@ -30,7 +30,7 @@
 > cat pnpm-workspace.yaml
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: 'catalog:'
diff --git a/packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt b/packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt
index 66cbc74528..931bc042ab 100644
--- a/packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt
+++ b/packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt
@@ -3,6 +3,7 @@
 AGENTS.md
 README.md
 apps
+bunfig.toml
 package.json
 packages
 tsconfig.json
@@ -25,6 +26,7 @@ vite.config.ts
     "prepare": "vp config"
   },
   "devDependencies": {
+    "vite": "catalog:",
     "vite-plus": "catalog:"
   },
   "overrides": {
@@ -43,7 +45,7 @@ vite.config.ts
   },
   "catalog": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
+    "vitest": "<semver>",
     "vite-plus": "latest"
   }
 }
diff --git a/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt b/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt
index 572b9d83bc..484912d7a9 100644
--- a/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt
+++ b/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt
@@ -65,7 +65,7 @@ catalog:
   "@types/node": ^24
   typescript: ^5
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: "catalog:"
diff --git a/packages/cli/snap-tests-global/test-managed-package-manager-path/snap.txt b/packages/cli/snap-tests-global/test-managed-package-manager-path/snap.txt
index 3d83ee3c9e..f3ffa760a8 100644
--- a/packages/cli/snap-tests-global/test-managed-package-manager-path/snap.txt
+++ b/packages/cli/snap-tests-global/test-managed-package-manager-path/snap.txt
@@ -1,5 +1,6 @@
 > sh -c 'vp_bin=$(command -v vp) && node_path=$(command -v node) && node_bin=$(mktemp -d) && ln -s "$node_path" "$node_bin/node" && sanitized_path="$node_bin:/bin:/usr/bin" && PATH="$sanitized_path" node --version >/dev/null && if PATH="$sanitized_path" command -v pnpm >/dev/null; then echo "pnpm unexpectedly available on sanitized PATH"; exit 1; fi && PATH="$sanitized_path" "$vp_bin" test --slowTestThreshold 10000'
- RUN  <cwd>
+
+ RUN  v<semver> <cwd>
 
  ✓ src/managed-pm-path.test.ts (1 test) <variable>ms
 
diff --git a/packages/cli/snap-tests-global/test-managed-package-manager-path/src/managed-pm-path.test.ts b/packages/cli/snap-tests-global/test-managed-package-manager-path/src/managed-pm-path.test.ts
index 1e9c1ffb06..cee9cc69d6 100644
--- a/packages/cli/snap-tests-global/test-managed-package-manager-path/src/managed-pm-path.test.ts
+++ b/packages/cli/snap-tests-global/test-managed-package-manager-path/src/managed-pm-path.test.ts
@@ -1,6 +1,6 @@
 import { execFileSync } from 'node:child_process';
 
-import { expect, test } from '@voidzero-dev/vite-plus-test';
+import { expect, test } from 'vite-plus/test';
 
 test('direct test command exposes the configured package manager on PATH', () => {
   const version = execFileSync('pnpm', ['--version'], { encoding: 'utf8' }).trim();
diff --git a/packages/cli/snap-tests/command-helper/snap.txt b/packages/cli/snap-tests/command-helper/snap.txt
index 4bc9b3f406..80a8ba3a14 100644
--- a/packages/cli/snap-tests/command-helper/snap.txt
+++ b/packages/cli/snap-tests/command-helper/snap.txt
@@ -257,11 +257,11 @@ Options:
   -h, --help                    Display this message 
 
 > vp test -h # test help message
-vp test/<semver>
+vitest/<semver>
  WARN: no options were found for your subcommands so we printed the whole output
 
 Usage:
-  $ vp test [...filters]
+  $ vitest [...filters]
 
 Commands:
   run [...filters]      
@@ -275,16 +275,16 @@ Commands:
   complete [shell]      
 
 For more info, run any command with the `--help` flag:
-  $ vp test run --help
-  $ vp test related --help
-  $ vp test watch --help
-  $ vp test dev --help
-  $ vp test bench --help
-  $ vp test init --help
-  $ vp test list --help
-  $ vp test --help
-  $ vp test complete --help
-  $ vp test --help --expand-help
+  $ vitest run --help
+  $ vitest related --help
+  $ vitest watch --help
+  $ vitest dev --help
+  $ vitest bench --help
+  $ vitest init --help
+  $ vitest list --help
+  $ vitest --help
+  $ vitest complete --help
+  $ vitest --help --expand-help
 
 Options:
   -v, --version                                              Display version number 
diff --git a/packages/cli/snap-tests/command-install-auto-create-package-json/snap.txt b/packages/cli/snap-tests/command-install-auto-create-package-json/snap.txt
index 539887a21f..ccc942b871 100644
--- a/packages/cli/snap-tests/command-install-auto-create-package-json/snap.txt
+++ b/packages/cli/snap-tests/command-install-auto-create-package-json/snap.txt
@@ -14,7 +14,6 @@ no package.json
 }
 
 > vp add testnpm2 -D && cat package.json # should add package to auto-created package.json
-✓ Lockfile passes supply-chain policies (verified <variable>ms ago)
 Packages: +<variable>
 +
 Progress: resolved <variable>, reused <variable>, downloaded <variable>, added <variable>, done
diff --git a/packages/cli/snap-tests/create-approve-builds-bun/snap.txt b/packages/cli/snap-tests/create-approve-builds-bun/snap.txt
index 855d88be50..1a2f29e76d 100644
--- a/packages/cli/snap-tests/create-approve-builds-bun/snap.txt
+++ b/packages/cli/snap-tests/create-approve-builds-bun/snap.txt
@@ -16,11 +16,12 @@
     "core-js": "3.39.0"
   },
   "devDependencies": {
+    "vite": "npm:@voidzero-dev/vite-plus-core@latest",
     "vite-plus": "latest"
   },
   "overrides": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vitest": "<semver>"
   },
   "devEngines": {
     "packageManager": {
@@ -56,11 +57,12 @@ These dependencies may not work until built. Run vp pm approve-builds core-js in
     "core-js": "3.39.0"
   },
   "devDependencies": {
+    "vite": "npm:@voidzero-dev/vite-plus-core@latest",
     "vite-plus": "latest"
   },
   "overrides": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vitest": "<semver>"
   },
   "devEngines": {
     "packageManager": {
@@ -91,11 +93,12 @@ bun pm trust v<semver> (<hash>)
     "core-js": "3.39.0"
   },
   "devDependencies": {
+    "vite": "npm:@voidzero-dev/vite-plus-core@latest",
     "vite-plus": "latest"
   },
   "overrides": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vitest": "<semver>"
   },
   "devEngines": {
     "packageManager": {
diff --git a/packages/cli/snap-tests/create-approve-builds-migrate-pnpm11/snap.txt b/packages/cli/snap-tests/create-approve-builds-migrate-pnpm11/snap.txt
index acd90c1392..ac8879bff3 100644
--- a/packages/cli/snap-tests/create-approve-builds-migrate-pnpm11/snap.txt
+++ b/packages/cli/snap-tests/create-approve-builds-migrate-pnpm11/snap.txt
@@ -11,7 +11,7 @@ allowBuilds:
   core-js: true
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: "catalog:"
@@ -41,7 +41,7 @@ allowBuilds:
   core-js: set this to true or false
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: "catalog:"
@@ -63,7 +63,7 @@ allowBuilds:
   core-js: true
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: "catalog:"
diff --git a/packages/cli/snap-tests/create-approve-builds-pnpm11/snap.txt b/packages/cli/snap-tests/create-approve-builds-pnpm11/snap.txt
index edd0892fc9..ae6586d93e 100644
--- a/packages/cli/snap-tests/create-approve-builds-pnpm11/snap.txt
+++ b/packages/cli/snap-tests/create-approve-builds-pnpm11/snap.txt
@@ -9,7 +9,7 @@ allowBuilds:
   core-js: true
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: "catalog:"
@@ -37,7 +37,7 @@ allowBuilds:
   core-js: set this to true or false
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: "catalog:"
@@ -59,7 +59,7 @@ allowBuilds:
   core-js: true
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: "catalog:"
diff --git a/packages/cli/snap-tests/create-approve-builds-yarn/snap.txt b/packages/cli/snap-tests/create-approve-builds-yarn/snap.txt
index 7995b8f347..831f12dea0 100644
--- a/packages/cli/snap-tests/create-approve-builds-yarn/snap.txt
+++ b/packages/cli/snap-tests/create-approve-builds-yarn/snap.txt
@@ -25,7 +25,7 @@
   },
   "resolutions": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vitest": "<semver>"
   },
   "devEngines": {
     "packageManager": {
@@ -62,7 +62,7 @@ These dependencies may not work until built. Enable them in the workspace root p
   },
   "resolutions": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vitest": "<semver>"
   },
   "devEngines": {
     "packageManager": {
diff --git a/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt b/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt
index 3804d2905f..3fe290bc75 100644
--- a/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt
+++ b/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt
@@ -26,7 +26,7 @@ packages:
   - packages/*
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
+  vitest: <semver>
   vite-plus: latest
 overrides:
   vite: "catalog:"
diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt
index a33d958b0c..7cb09d00a9 100644
--- a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt
+++ b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt
@@ -59,22 +59,6 @@ Finished in <variable>ms on 1 file with <variable> rules using <variable> thread
  5 │ 
    ╰────
 
-  × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/browser-playwright' instead of '@vitest/browser-playwright' in Vite+ projects.
-   ╭─[src/types.ts:6:16]
- 5 │ 
- 6 │ declare module '@vitest/browser-playwright' {}
-   ·                ────────────────────────────
- 7 │ declare module '@vitest/browser-playwright/context' {}
-   ╰────
-
-  × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/browser/context' instead of '@vitest/browser-playwright/context' in Vite+ projects.
-   ╭─[src/types.ts:7:16]
- 6 │ declare module '@vitest/browser-playwright' {}
- 7 │ declare module '@vitest/browser-playwright/context' {}
-   ·                ────────────────────────────────────
- 8 │ 
-   ╰────
-
   × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/client' instead of 'vite/client' in Vite+ projects.
     ╭─[src/types.ts:9:25]
   8 │ 
@@ -83,7 +67,7 @@ Finished in <variable>ms on 1 file with <variable> rules using <variable> thread
  10 │ 
     ╰────
 
-Found 0 warnings and 7 errors.
+Found 0 warnings and 5 errors.
 Finished in <variable>ms on 1 file with <variable> rules using <variable> threads.
 
 > vp lint --fix src/index.ts src/types.ts # rewrite vite and vitest imports via the vite-plus oxlint plugin
@@ -106,8 +90,8 @@ type BrowserContext = typeof import('vite-plus/test/browser/context');
 type BrowserClient = typeof import('vite-plus/test/client');
 type PlaywrightProvider = typeof import('vite-plus/test/browser/providers/playwright');
 
-declare module 'vite-plus/test/browser-playwright' {}
-declare module 'vite-plus/test/browser/context' {}
+declare module '@vitest/browser-playwright' {}
+declare module '@vitest/browser-playwright/context' {}
 
 import client = require('vite-plus/client');
 
diff --git a/packages/cli/snap-tests/test-inline-snapshot-indent/snap.txt b/packages/cli/snap-tests/test-inline-snapshot-indent/snap.txt
index 2fb66bcc52..1cb18f6450 100644
--- a/packages/cli/snap-tests/test-inline-snapshot-indent/snap.txt
+++ b/packages/cli/snap-tests/test-inline-snapshot-indent/snap.txt
@@ -1,5 +1,6 @@
 > vp test run -u src/inline-snapshot.test.ts # write inline snapshot via --update (regression test for #1553)
- RUN  <cwd>
+
+ RUN  v<semver> <cwd>
 
  ✓ src/inline-snapshot.test.ts (1 test) <variable>ms
 
diff --git a/packages/cli/snap-tests/test-vi-mock-hoist/package.json b/packages/cli/snap-tests/test-vi-mock-hoist/package.json
new file mode 100644
index 0000000000..837574a549
--- /dev/null
+++ b/packages/cli/snap-tests/test-vi-mock-hoist/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "test-vi-mock-hoist",
+  "private": true
+}
diff --git a/packages/cli/snap-tests/test-vi-mock-hoist/snap.txt b/packages/cli/snap-tests/test-vi-mock-hoist/snap.txt
new file mode 100644
index 0000000000..fc1f439187
--- /dev/null
+++ b/packages/cli/snap-tests/test-vi-mock-hoist/snap.txt
@@ -0,0 +1,11 @@
+> vp test run src/vi-mock-hoist.test.ts # vi.mock() from 'vite-plus/test' must hoist via upstream mocker (no vite-plus patch/shim)
+
+ RUN  v<semver> <cwd>
+
+ ✓ src/vi-mock-hoist.test.ts (1 test) <variable>ms
+
+ Test Files  1 passed (1)
+      Tests  1 passed (1)
+   Start at  <date>
+   Duration  <variable>ms (transform <variable>ms, setup <variable>ms, import <variable>ms, tests <variable>ms, environment <variable>ms)
+
diff --git a/packages/cli/snap-tests/test-vi-mock-hoist/src/dep.ts b/packages/cli/snap-tests/test-vi-mock-hoist/src/dep.ts
new file mode 100644
index 0000000000..51459bed42
--- /dev/null
+++ b/packages/cli/snap-tests/test-vi-mock-hoist/src/dep.ts
@@ -0,0 +1 @@
+export const value = 'real';
diff --git a/packages/cli/snap-tests/test-vi-mock-hoist/src/vi-mock-hoist.test.ts b/packages/cli/snap-tests/test-vi-mock-hoist/src/vi-mock-hoist.test.ts
new file mode 100644
index 0000000000..0ac6251953
--- /dev/null
+++ b/packages/cli/snap-tests/test-vi-mock-hoist/src/vi-mock-hoist.test.ts
@@ -0,0 +1,14 @@
+import { expect, it, vi } from 'vite-plus/test';
+
+import { value } from './dep.ts';
+
+// `vi.mock` is written AFTER the import on purpose. The static mocker must
+// recognize `vi` imported from `vite-plus/test` and hoist this call above the
+// import, otherwise the real `./dep` loads first and `value` stays 'real'.
+// Upstream vitest >=4.1.9 recognizes the `vite-plus/test` redistribution
+// natively (no vite-plus-side patch/shim required).
+vi.mock('./dep.ts', () => ({ value: 'mocked' }));
+
+it('hoists vi.mock() above imports for the vite-plus/test specifier', () => {
+  expect(value).toBe('mocked');
+});
diff --git a/packages/cli/snap-tests/test-vi-mock-hoist/steps.json b/packages/cli/snap-tests/test-vi-mock-hoist/steps.json
new file mode 100644
index 0000000000..2c960ce946
--- /dev/null
+++ b/packages/cli/snap-tests/test-vi-mock-hoist/steps.json
@@ -0,0 +1,6 @@
+{
+  "ignoredPlatforms": ["win32"],
+  "commands": [
+    "vp test run src/vi-mock-hoist.test.ts # vi.mock() from 'vite-plus/test' must hoist via upstream mocker (no vite-plus patch/shim)"
+  ]
+}
diff --git a/packages/cli/snap-tests/vite-plugins-async-test/snap.txt b/packages/cli/snap-tests/vite-plugins-async-test/snap.txt
index cb8596d640..cd91b5d731 100644
--- a/packages/cli/snap-tests/vite-plugins-async-test/snap.txt
+++ b/packages/cli/snap-tests/vite-plugins-async-test/snap.txt
@@ -1,5 +1,6 @@
 > vp test # async plugins factory should load vitest plugin with configureVitest hook
- RUN  <cwd>
+
+ RUN  v<semver> <cwd>
 
  ✓ src/index.test.ts (1 test) <variable>ms
 
diff --git a/packages/cli/snap-tests/vite-plugins-async-test/src/index.test.ts b/packages/cli/snap-tests/vite-plugins-async-test/src/index.test.ts
index 7f37cab022..d6f3f94935 100644
--- a/packages/cli/snap-tests/vite-plugins-async-test/src/index.test.ts
+++ b/packages/cli/snap-tests/vite-plugins-async-test/src/index.test.ts
@@ -1,7 +1,7 @@
 import fs from 'node:fs';
 import path from 'node:path';
 
-import { expect, onTestFinished, test } from '@voidzero-dev/vite-plus-test';
+import { expect, onTestFinished, test } from 'vitest';
 
 test('async plugin factory should load vitest plugin with configureVitest hook', () => {
   const markerPath = path.join(import.meta.dirname, '..', '.vitest-plugin-loaded');
diff --git a/packages/cli/src/__tests__/define-config-plugins.spec.ts b/packages/cli/src/__tests__/define-config-plugins.spec.ts
new file mode 100644
index 0000000000..11aa4485a1
--- /dev/null
+++ b/packages/cli/src/__tests__/define-config-plugins.spec.ts
@@ -0,0 +1,414 @@
+import type { Plugin } from '@voidzero-dev/vite-plus-core';
+import { describe, expect, it } from 'vitest';
+
+import {
+  assertCoverageProviderVersionMatch,
+  AUTO_INLINE_DEPS,
+  checkCoverageProviderVersion,
+  computeAutoInlineList,
+  defineConfig,
+  resolveCoverageProviderToCheck,
+} from '../define-config.ts';
+import { VITEST_VERSION } from '../utils/constants.ts';
+
+const RESOLVER_PLUGIN_NAME = 'vite-plus:vitest-resolver';
+const COVERAGE_GUARD_PLUGIN_NAME = 'vite-plus:coverage-version-guard';
+
+function pluginName(p: unknown): string | undefined {
+  if (
+    p &&
+    typeof p === 'object' &&
+    'name' in p &&
+    typeof (p as { name: unknown }).name === 'string'
+  ) {
+    return (p as { name: string }).name;
+  }
+  return undefined;
+}
+
+describe('defineConfig project plugin injection', () => {
+  it('injects resolver + auto-inline plugins at the root plugins array', () => {
+    const existing: Plugin = { name: 'user-existing-root-plugin' };
+    const result = defineConfig({ plugins: [existing] }) as { plugins: unknown[] };
+
+    expect(pluginName(result.plugins[0])).toBe(RESOLVER_PLUGIN_NAME);
+    expect(pluginName(result.plugins[1])).toBe(AUTO_INLINE_PLUGIN_NAME);
+    expect(pluginName(result.plugins[2])).toBe(COVERAGE_GUARD_PLUGIN_NAME);
+    expect(pluginName(result.plugins[3])).toBe('user-existing-root-plugin');
+  });
+
+  it('injects resolver + auto-inline plugins into an inline-object project entry, preserving existing plugins', () => {
+    const existing: Plugin = { name: 'user-unit-project-plugin' };
+    const result = defineConfig({
+      test: {
+        projects: [
+          {
+            plugins: [existing],
+            test: { name: 'unit', include: ['test/unit/**/*.spec.ts'], environment: 'node' },
+          },
+        ],
+      },
+    }) as { test: { projects: unknown[] } };
+
+    const project = result.test.projects[0] as { plugins: unknown[]; test: { name: string } };
+    expect(project.test.name).toBe('unit');
+    expect(pluginName(project.plugins[0])).toBe(RESOLVER_PLUGIN_NAME);
+    expect(pluginName(project.plugins[1])).toBe(AUTO_INLINE_PLUGIN_NAME);
+    expect(pluginName(project.plugins[2])).toBe(COVERAGE_GUARD_PLUGIN_NAME);
+    expect(pluginName(project.plugins[3])).toBe('user-unit-project-plugin');
+    // Sanity: the existing plugin reference is preserved (clone shallow-copies the array).
+    expect(project.plugins[3]).toBe(existing);
+  });
+
+  it('injects plugins into the return value of a function-shaped project entry', () => {
+    const existing: Plugin = { name: 'user-fn-project-plugin' };
+    const projectFn = () => ({
+      plugins: [existing],
+      test: { name: 'nuxt', environment: 'happy-dom' as const },
+    });
+    const result = defineConfig({
+      test: { projects: [projectFn] },
+    }) as { test: { projects: unknown[] } };
+
+    const wrapped = result.test.projects[0];
+    expect(typeof wrapped).toBe('function');
+
+    // Vitest passes a `ConfigEnv` to the function; we don't depend on its
+    // shape here, the wrapper just forwards it.
+    const fakeEnv = { mode: 'test', command: 'serve' as const };
+    const resolved = (wrapped as (env: typeof fakeEnv) => { plugins: unknown[] })(fakeEnv);
+    expect(pluginName(resolved.plugins[0])).toBe(RESOLVER_PLUGIN_NAME);
+    expect(pluginName(resolved.plugins[1])).toBe(AUTO_INLINE_PLUGIN_NAME);
+    expect(pluginName(resolved.plugins[2])).toBe(COVERAGE_GUARD_PLUGIN_NAME);
+    expect(pluginName(resolved.plugins[3])).toBe('user-fn-project-plugin');
+  });
+
+  it('passes string-glob project entries through unchanged', () => {
+    const result = defineConfig({
+      test: {
+        projects: ['./packages/*', './apps/*'],
+      },
+    }) as { test: { projects: unknown[] } };
+
+    expect(result.test.projects).toEqual(['./packages/*', './apps/*']);
+  });
+
+  it('handles projects with no existing plugins array', () => {
+    const result = defineConfig({
+      test: {
+        projects: [
+          {
+            test: { name: 'no-plugins', environment: 'node' },
+          },
+        ],
+      },
+    }) as { test: { projects: unknown[] } };
+
+    const project = result.test.projects[0] as { plugins: unknown[]; test: { name: string } };
+    expect(project.test.name).toBe('no-plugins');
+    expect(project.plugins).toHaveLength(3);
+    expect(pluginName(project.plugins[0])).toBe(RESOLVER_PLUGIN_NAME);
+    expect(pluginName(project.plugins[1])).toBe(AUTO_INLINE_PLUGIN_NAME);
+    expect(pluginName(project.plugins[2])).toBe(COVERAGE_GUARD_PLUGIN_NAME);
+  });
+});
+
+const AUTO_INLINE_PLUGIN_NAME = 'vite-plus:auto-inline-matcher-deps';
+
+/** Builds a mock require-factory where only `installedPkgs` resolve. */
+function makeRequireFactory(
+  installedPkgs: string[],
+): (from: string) => { resolve: (id: string) => string } {
+  return (_from: string) => ({
+    resolve(id: string) {
+      if (installedPkgs.includes(id)) {
+        return `/mock/node_modules/${id}/index.js`;
+      }
+      throw new Error(`Cannot find module '${id}'`);
+    },
+  });
+}
+
+/** A mock require-factory where every package resolves. */
+const allInstalledFactory = makeRequireFactory([
+  '@testing-library/jest-dom',
+  '@storybook/test',
+  'jest-extended',
+]);
+
+/** A mock require-factory where no auto-inline package resolves. */
+const noneInstalledFactory = makeRequireFactory([]);
+
+describe('computeAutoInlineList', () => {
+  const ALL = [...AUTO_INLINE_DEPS];
+
+  it('inlines all packages when all are installed and no existing list', () => {
+    expect(computeAutoInlineList(undefined, '/project', allInstalledFactory)).toEqual(ALL);
+  });
+
+  it('inlines only installed packages — absent ones are skipped', () => {
+    const only = makeRequireFactory(['@testing-library/jest-dom']);
+    expect(computeAutoInlineList(undefined, '/project', only)).toEqual([
+      '@testing-library/jest-dom',
+    ]);
+  });
+
+  it('returns null when no auto-inline package is installed', () => {
+    expect(computeAutoInlineList(undefined, '/project', noneInstalledFactory)).toBeNull();
+  });
+
+  it('merges with an existing user inline array, preserving order and deduplicating', () => {
+    const existing: (string | RegExp)[] = ['my-pkg', '@testing-library/jest-dom'];
+    const result = computeAutoInlineList(existing, '/project', allInstalledFactory);
+    expect(result).toEqual([
+      'my-pkg',
+      '@testing-library/jest-dom',
+      '@storybook/test',
+      'jest-extended',
+    ]);
+    // Original array must not be mutated.
+    expect(existing).toEqual(['my-pkg', '@testing-library/jest-dom']);
+  });
+
+  it("returns null when `inline: true` (user opted into 'inline everything')", () => {
+    expect(computeAutoInlineList(true, '/project', allInstalledFactory)).toBeNull();
+  });
+
+  it('treats a regexp entry that matches an auto-inline pkg as already covered', () => {
+    const existing: (string | RegExp)[] = [/^@testing-library\//, /^@storybook\//];
+    const result = computeAutoInlineList(existing, '/project', allInstalledFactory);
+    // Both '@testing-library/jest-dom' and '@storybook/test' are covered;
+    // only 'jest-extended' should be appended.
+    expect(result).toHaveLength(3);
+    expect(result![0]).toBeInstanceOf(RegExp);
+    expect(result![1]).toBeInstanceOf(RegExp);
+    expect(result![2]).toBe('jest-extended');
+  });
+
+  it('returns null when all auto-inline packages are already in the existing list', () => {
+    const existing: (string | RegExp)[] = [...AUTO_INLINE_DEPS];
+    expect(computeAutoInlineList(existing, '/project', allInstalledFactory)).toBeNull();
+  });
+
+  it('passes the project root to the require factory', () => {
+    const capturedFromPaths: string[] = [];
+    const factory = (from: string) => {
+      capturedFromPaths.push(from);
+      return { resolve: (_id: string) => `/mock/node_modules/${_id}/index.js` };
+    };
+    computeAutoInlineList(undefined, '/custom/root', factory);
+    expect(capturedFromPaths).toEqual(['/custom/root/package.json']);
+  });
+});
+
+describe('defineConfig auto-inline deps plugin registration', () => {
+  it('registers the auto-inline plugin in the root plugins array with enforce:pre and configResolved', () => {
+    const result = defineConfig({}) as { plugins: unknown[] };
+    const plugin = result.plugins.find(
+      (p): p is Record<string, unknown> =>
+        !!p && typeof p === 'object' && (p as { name?: unknown }).name === AUTO_INLINE_PLUGIN_NAME,
+    );
+    expect(plugin).toBeDefined();
+    expect(plugin?.enforce).toBe('pre');
+    expect(typeof plugin?.configResolved).toBe('function');
+  });
+});
+
+/**
+ * Build injectable deps for {@link checkCoverageProviderVersion}: only the
+ * packages in `installed` resolve, and reading their package.json returns the
+ * mapped version.
+ */
+function makeCoverageDeps(installed: Record<string, string>): {
+  createRequire: (from: string) => { resolve: (id: string) => string };
+  readFile: (path: string) => string;
+} {
+  return {
+    createRequire: (_from: string) => ({
+      resolve(id: string) {
+        const pkg = id.replace(/\/package\.json$/, '');
+        if (pkg in installed) {
+          return `/mock/node_modules/${pkg}/package.json`;
+        }
+        throw new Error(`Cannot find module '${id}'`);
+      },
+    }),
+    readFile(path: string) {
+      const pkg = path.replace(/^\/mock\/node_modules\//, '').replace(/\/package\.json$/, '');
+      return JSON.stringify({ version: installed[pkg] });
+    },
+  };
+}
+
+describe('resolveCoverageProviderToCheck', () => {
+  // Input is Vitest's RESOLVED coverage options (CLI flags already merged by
+  // Vitest), so the helper never parses argv itself.
+  it('returns @vitest/coverage-v8 for enabled coverage with the default provider', () => {
+    expect(resolveCoverageProviderToCheck({ enabled: true })).toBe('@vitest/coverage-v8');
+  });
+
+  it('honors an explicit istanbul provider', () => {
+    expect(resolveCoverageProviderToCheck({ enabled: true, provider: 'istanbul' })).toBe(
+      '@vitest/coverage-istanbul',
+    );
+  });
+
+  it('returns null when coverage is off or undefined', () => {
+    expect(resolveCoverageProviderToCheck(undefined)).toBeNull();
+    expect(resolveCoverageProviderToCheck({ enabled: false })).toBeNull();
+    expect(resolveCoverageProviderToCheck({ provider: 'v8' })).toBeNull();
+  });
+
+  it('returns null for a custom/unknown provider vite-plus does not bundle a runner for', () => {
+    expect(resolveCoverageProviderToCheck({ enabled: true, provider: 'custom' })).toBeNull();
+  });
+});
+
+describe('assertCoverageProviderVersionMatch', () => {
+  it('does not throw when the provider version matches the bundled runner', () => {
+    expect(() =>
+      assertCoverageProviderVersionMatch('@vitest/coverage-v8', VITEST_VERSION),
+    ).not.toThrow();
+  });
+
+  it('throws when the provider version is skewed from the bundled runner', () => {
+    expect(() => assertCoverageProviderVersionMatch('@vitest/coverage-v8', '4.1.8')).toThrow(
+      /bundles vitest@/,
+    );
+  });
+
+  it('does not throw when the provider version is unknown (not installed)', () => {
+    expect(() => assertCoverageProviderVersionMatch('@vitest/coverage-v8', null)).not.toThrow();
+    expect(() =>
+      assertCoverageProviderVersionMatch('@vitest/coverage-v8', undefined),
+    ).not.toThrow();
+  });
+});
+
+describe('checkCoverageProviderVersion', () => {
+  it('throws when coverage is on and the installed provider is version-skewed', () => {
+    const deps = makeCoverageDeps({ '@vitest/coverage-v8': '4.1.8' });
+    expect(() => checkCoverageProviderVersion({ enabled: true }, '/project', deps)).toThrow(
+      /@vitest\/coverage-v8@4\.1\.8/,
+    );
+  });
+
+  it('does not throw when the installed provider matches the bundled runner', () => {
+    const deps = makeCoverageDeps({ '@vitest/coverage-v8': VITEST_VERSION });
+    expect(() => checkCoverageProviderVersion({ enabled: true }, '/project', deps)).not.toThrow();
+  });
+
+  it('is a no-op when coverage is off, even if a skewed provider is installed', () => {
+    const deps = makeCoverageDeps({ '@vitest/coverage-v8': '4.1.8' });
+    expect(() => checkCoverageProviderVersion(undefined, '/project', deps)).not.toThrow();
+  });
+
+  it('is a no-op when the provider is not installed (vitest emits its own error)', () => {
+    const deps = makeCoverageDeps({});
+    expect(() => checkCoverageProviderVersion({ enabled: true }, '/project', deps)).not.toThrow();
+  });
+
+  it('checks the istanbul provider when it is the selected provider', () => {
+    const deps = makeCoverageDeps({ '@vitest/coverage-istanbul': '4.1.8' });
+    expect(() =>
+      checkCoverageProviderVersion({ enabled: true, provider: 'istanbul' }, '/project', deps),
+    ).toThrow(/@vitest\/coverage-istanbul@4\.1\.8/);
+  });
+
+  it('falls back to the bundled-vitest anchor when the project root cannot resolve the provider', () => {
+    // pnpm peer-set / hoisted layouts: the provider is loadable by vitest (next
+    // to the bundled vitest) but is NOT resolvable from the project root. The
+    // guard must still catch the skew via the bundled-vitest fallback anchor
+    // instead of silently skipping. The mock fails resolution from the project
+    // root and only resolves from any other anchor.
+    const projectRootAnchor = '/project/package.json';
+    const deps = {
+      createRequire: (from: string) => ({
+        resolve(id: string) {
+          if (from === projectRootAnchor) {
+            throw new Error('provider not reachable from the project root');
+          }
+          return `/mock/node_modules/${id.replace(/\/package\.json$/, '')}/package.json`;
+        },
+      }),
+      readFile: () => JSON.stringify({ version: '4.1.8' }),
+    };
+    expect(() => checkCoverageProviderVersion({ enabled: true }, '/project', deps)).toThrow(
+      /@vitest\/coverage-v8@4\.1\.8/,
+    );
+  });
+});
+
+/** Extract the coverage-version-guard plugin from a fresh defineConfig. */
+function getCoverageGuard(): { configureVitest: (ctx: unknown) => void } {
+  const result = defineConfig({}) as { plugins: unknown[] };
+  const plugin = result.plugins.find(
+    (p): p is { configureVitest: (ctx: unknown) => void } =>
+      !!p && typeof p === 'object' && (p as { name?: unknown }).name === COVERAGE_GUARD_PLUGIN_NAME,
+  );
+  if (!plugin) {
+    throw new Error('coverage guard plugin not found');
+  }
+  return plugin;
+}
+
+describe('defineConfig coverage version guard plugin registration', () => {
+  it('registers the coverage guard plugin with a configureVitest hook', () => {
+    const result = defineConfig({}) as { plugins: unknown[] };
+    const plugin = result.plugins.find(
+      (p): p is Record<string, unknown> =>
+        !!p &&
+        typeof p === 'object' &&
+        (p as { name?: unknown }).name === COVERAGE_GUARD_PLUGIN_NAME,
+    );
+    expect(plugin).toBeDefined();
+    expect(typeof plugin?.configureVitest).toBe('function');
+  });
+
+  it('configureVitest is a no-op when the runner coverage is off (startup path)', () => {
+    const guard = getCoverageGuard();
+    // The hook short-circuits before any resolution when coverage is disabled,
+    // so a non-existent root is fine.
+    expect(() =>
+      guard.configureVitest({
+        vitest: {
+          config: { root: '/project', coverage: { enabled: false } },
+          enableCoverage: async () => {},
+        },
+      }),
+    ).not.toThrow();
+  });
+
+  it('wraps the shared vitest.enableCoverage so the late path runs the check, and still delegates', async () => {
+    const guard = getCoverageGuard();
+    let originalCalls = 0;
+    const original = async () => {
+      originalCalls += 1;
+    };
+    const vitest: Record<string, unknown> = {
+      config: { root: '/project', coverage: { provider: 'v8' } },
+      enableCoverage: original,
+    };
+    guard.configureVitest({ vitest });
+    // enableCoverage was replaced by the guard's wrapper...
+    expect(vitest.enableCoverage).not.toBe(original);
+    // ...and the wrapper delegates to the original (no provider resolves from
+    // this fake root, so the late-path check is a no-op).
+    await (vitest.enableCoverage as () => Promise<void>)();
+    expect(originalCalls).toBe(1);
+  });
+
+  it('handles the shared vitest instance once even though configureVitest runs per project', () => {
+    const guard = getCoverageGuard();
+    const vitest: Record<string, unknown> = {
+      config: { root: '/project', coverage: {} },
+      enableCoverage: async () => {},
+    };
+    // The hook fires once per project but `vitest` is shared; the guard reads the
+    // global runner config (not per-project) and wraps the instance only once.
+    guard.configureVitest({ vitest });
+    const afterFirst = vitest.enableCoverage;
+    guard.configureVitest({ vitest });
+    expect(vitest.enableCoverage).toBe(afterFirst);
+  });
+});
diff --git a/packages/cli/src/__tests__/define-config-test-field.spec.ts b/packages/cli/src/__tests__/define-config-test-field.spec.ts
new file mode 100644
index 0000000000..f3a787b65f
--- /dev/null
+++ b/packages/cli/src/__tests__/define-config-test-field.spec.ts
@@ -0,0 +1,45 @@
+import { describe, expect, it } from 'vitest';
+
+import {
+  configDefaults,
+  coverageConfigDefaults,
+  defineConfig,
+  loadConfigFromFile,
+  mergeConfig,
+} from '../index.js';
+
+describe('defineConfig test field typing', () => {
+  it('accepts test config without TS error', () => {
+    const cfg = defineConfig({
+      test: {
+        globals: true,
+        environment: 'node',
+        include: ['src/**/*.spec.ts'],
+      },
+    });
+    void cfg;
+  });
+
+  it('accepts test config alongside other vite-plus fields', () => {
+    const cfg = defineConfig({
+      test: {
+        globals: true,
+        environment: 'jsdom',
+      },
+      lint: {},
+    });
+    void cfg;
+  });
+
+  it('re-exports shared and vitest-specific names without star-export conflicts', () => {
+    expect(typeof defineConfig).toBe('function');
+    expect(typeof mergeConfig).toBe('function');
+    expect(typeof loadConfigFromFile).toBe('function');
+    expect(typeof configDefaults).toBe('object');
+    expect(typeof coverageConfigDefaults).toBe('object');
+
+    const merged = mergeConfig({ test: { globals: true } }, { test: { environment: 'node' } });
+    expect(merged.test.globals).toBe(true);
+    expect(merged.test.environment).toBe('node');
+  });
+});
diff --git a/packages/cli/src/__tests__/define-config-vitest-resolver.spec.ts b/packages/cli/src/__tests__/define-config-vitest-resolver.spec.ts
new file mode 100644
index 0000000000..1fcc23f9a8
--- /dev/null
+++ b/packages/cli/src/__tests__/define-config-vitest-resolver.spec.ts
@@ -0,0 +1,267 @@
+import { fileURLToPath } from 'node:url';
+
+import type { Plugin } from '@voidzero-dev/vite-plus-core';
+import { describe, expect, it } from 'vitest';
+
+import { defineConfig, defineProject, isVitestFamilySpecifier } from '../define-config.ts';
+
+const RESOLVER_PLUGIN_NAME = 'vite-plus:vitest-resolver';
+
+function findPlugin(plugins: unknown, name: string): Record<string, unknown> | undefined {
+  if (!Array.isArray(plugins)) {
+    return undefined;
+  }
+  return plugins.find(
+    (p): p is Record<string, unknown> =>
+      !!p && typeof p === 'object' && (p as { name?: unknown }).name === name,
+  );
+}
+
+function idOf(resolved: string | { id: string } | null | undefined): string | undefined {
+  return typeof resolved === 'string' ? resolved : resolved?.id;
+}
+
+describe('isVitestFamilySpecifier', () => {
+  it('matches the bare `vitest` specifier', () => {
+    expect(isVitestFamilySpecifier('vitest')).toBe(true);
+  });
+
+  it('matches `vitest/internal/browser`', () => {
+    expect(isVitestFamilySpecifier('vitest/internal/browser')).toBe(true);
+  });
+
+  it('matches `vitest/config`', () => {
+    expect(isVitestFamilySpecifier('vitest/config')).toBe(true);
+  });
+
+  it('matches `@vitest/browser`', () => {
+    expect(isVitestFamilySpecifier('@vitest/browser')).toBe(true);
+  });
+
+  it('matches `@vitest/browser/context`', () => {
+    expect(isVitestFamilySpecifier('@vitest/browser/context')).toBe(true);
+  });
+
+  it('matches `@vitest/expect`', () => {
+    expect(isVitestFamilySpecifier('@vitest/expect')).toBe(true);
+  });
+
+  it('matches a queried subpath (query stripped before matching)', () => {
+    expect(isVitestFamilySpecifier('vitest/internal/browser?v=1')).toBe(true);
+  });
+
+  it('does NOT match `vitest-foo` (not a subpath of vitest)', () => {
+    expect(isVitestFamilySpecifier('vitest-foo')).toBe(false);
+  });
+
+  it('does NOT match the bare scope `@vitest` (no trailing slash)', () => {
+    expect(isVitestFamilySpecifier('@vitest')).toBe(false);
+  });
+
+  it('does NOT match a relative id', () => {
+    expect(isVitestFamilySpecifier('./local')).toBe(false);
+  });
+
+  it('does NOT match an absolute id', () => {
+    expect(isVitestFamilySpecifier('/abs/path/vitest')).toBe(false);
+  });
+
+  it('does NOT match a virtual id', () => {
+    expect(isVitestFamilySpecifier('\0virtual')).toBe(false);
+  });
+
+  it('does NOT match an unrelated bare specifier', () => {
+    expect(isVitestFamilySpecifier('react')).toBe(false);
+  });
+});
+
+describe('vitePlusVitestResolverPlugin', () => {
+  it('is injected into the root plugins array as an enforce:pre plugin with resolveId', () => {
+    const result = defineConfig({}) as { plugins: unknown[] };
+    const plugin = findPlugin(result.plugins, RESOLVER_PLUGIN_NAME);
+
+    expect(plugin).toBeDefined();
+    expect(plugin?.name).toBe(RESOLVER_PLUGIN_NAME);
+    expect(plugin?.enforce).toBe('pre');
+    expect(typeof plugin?.resolveId).toBe('function');
+  });
+
+  it('is injected into each `test.projects` entry (before user plugins)', () => {
+    const existing: Plugin = { name: 'user-project-plugin' };
+    const result = defineConfig({
+      test: {
+        projects: [
+          { test: { name: 'unit', environment: 'node' } },
+          { plugins: [existing], test: { name: 'browser', environment: 'jsdom' } },
+        ],
+      },
+    }) as { test: { projects: unknown[] } };
+
+    for (const project of result.test.projects) {
+      const plugins = (project as { plugins?: unknown }).plugins;
+      const plugin = findPlugin(plugins, RESOLVER_PLUGIN_NAME);
+      expect(plugin).toBeDefined();
+      expect(plugin?.enforce).toBe('pre');
+      expect(typeof plugin?.resolveId).toBe('function');
+    }
+  });
+
+  it('is injected by defineProject too (browser project configs)', () => {
+    const result = defineProject({ test: { browser: { enabled: true } } }) as {
+      plugins: unknown[];
+    };
+    const plugin = findPlugin(result.plugins, RESOLVER_PLUGIN_NAME);
+
+    expect(plugin).toBeDefined();
+    expect(plugin?.enforce).toBe('pre');
+    expect(typeof plugin?.resolveId).toBe('function');
+  });
+});
+
+const PROJECT_IMPORTER = '/fake/project/app.test.ts';
+
+type ResolveResult = { id: string } | null;
+type ResolveIdCtx = {
+  resolve: (
+    id: string,
+    importer: string | undefined,
+    options: Record<string, unknown>,
+  ) => Promise<ResolveResult>;
+};
+type ResolveId = (
+  this: ResolveIdCtx,
+  id: string,
+  importer: string | undefined,
+  options: Record<string, unknown>,
+) => Promise<string | { id: string } | null | undefined>;
+
+interface ResolveCall {
+  id: string;
+  importer: string | undefined;
+  fromProject: boolean;
+}
+
+function getResolveId(): ResolveId {
+  const result = defineConfig({}) as { plugins: unknown[] };
+  const plugin = findPlugin(result.plugins, RESOLVER_PLUGIN_NAME);
+  expect(plugin).toBeDefined();
+  return plugin?.resolveId as ResolveId;
+}
+
+// The resolver re-resolves through `this.resolve` rooted at vite-plus's own
+// (bundled) anchors BEFORE the project importer. A `this.resolve` whose
+// `importer` is NOT the project importer is therefore a bundled-anchor probe;
+// `importer === PROJECT_IMPORTER` is the last-resort project fallback.
+function makeCtx(resolveFor: (call: ResolveCall) => ResolveResult): {
+  ctx: ResolveIdCtx;
+  calls: ResolveCall[];
+} {
+  const calls: ResolveCall[] = [];
+  const ctx: ResolveIdCtx = {
+    resolve: async (id, importer) => {
+      const call: ResolveCall = { id, importer, fromProject: importer === PROJECT_IMPORTER };
+      calls.push(call);
+      return resolveFor(call);
+    },
+  };
+  return { ctx, calls };
+}
+
+describe('vitePlusVitestResolverPlugin resolveId (bundle-first)', () => {
+  // The runner `vp test` spawns is the Vitest bundled with vite-plus (see
+  // resolve-test.ts). For the run to use a SINGLE physical Vitest, every
+  // `vitest` / `@vitest/*` import must resolve to that same bundled copy — even
+  // when the project keeps its own `vitest` dependency that the default
+  // resolver would otherwise prefer.
+  it('resolves the vitest family from a bundled anchor, never the project importer', async () => {
+    const resolveId = getResolveId();
+    const BUNDLED = '/bundled/vite-plus/node_modules/vitest/dist/index.js';
+    const PROJECT = '/fake/project/node_modules/vitest/dist/index.js';
+    const { ctx, calls } = makeCtx(({ fromProject }) => ({ id: fromProject ? PROJECT : BUNDLED }));
+
+    const id = idOf(await resolveId.call(ctx, 'vitest', PROJECT_IMPORTER, {}));
+
+    expect(id).toBe(BUNDLED);
+    // The first bundled anchor hit short-circuits before the project importer.
+    expect(calls).toHaveLength(1);
+    expect(calls[0].fromProject).toBe(false);
+  });
+
+  it('tries the vite-plus anchor then the vitest anchor for the nested @vitest/* family', async () => {
+    const resolveId = getResolveId();
+    const VITEST_ANCHORED = '/bundled/.pnpm/vitest/node_modules/@vitest/expect/dist/index.js';
+    let anchorProbes = 0;
+    const { ctx, calls } = makeCtx(({ fromProject }) => {
+      if (fromProject) {
+        return { id: '/fake/project/node_modules/@vitest/expect/dist/index.js' };
+      }
+      anchorProbes += 1;
+      // vite-plus anchor misses (@vitest/expect is a dep of vitest, not vite-plus);
+      // the second (vitest) anchor resolves it.
+      return anchorProbes >= 2 ? { id: VITEST_ANCHORED } : null;
+    });
+
+    const id = idOf(await resolveId.call(ctx, '@vitest/expect', PROJECT_IMPORTER, {}));
+
+    expect(id).toBe(VITEST_ANCHORED);
+    expect(anchorProbes).toBe(2);
+    // Resolved from a bundled anchor — never fell through to the project importer.
+    expect(calls.some((c) => c.fromProject)).toBe(false);
+  });
+
+  it('falls back to the project importer only when every bundled anchor misses', async () => {
+    const resolveId = getResolveId();
+    const FALLBACK = '/fake/project/node_modules/@vitest/coverage-v8/dist/index.js';
+    const { ctx, calls } = makeCtx(({ fromProject }) => (fromProject ? { id: FALLBACK } : null));
+
+    const id = idOf(await resolveId.call(ctx, '@vitest/coverage-v8', PROJECT_IMPORTER, {}));
+
+    expect(id).toBe(FALLBACK);
+    // Bundled anchors were probed first, then the project importer as last resort.
+    expect(calls.some((c) => !c.fromProject)).toBe(true);
+    expect(calls.at(-1)?.fromProject).toBe(true);
+  });
+
+  // Regression for the require-condition CJS throw-stub: Vitest's `.` /
+  // `./config` exports map the `require` condition to `index.cjs` / `config.cjs`,
+  // which throw "Vitest cannot be imported … using require()". Routing through
+  // `this.resolve` (ESM conditions) avoids them; a raw `require.resolve` would
+  // not. This ctx emulates Vite's ESM resolver so the contrast is exercised.
+  it('resolves vitest / vitest/config to the ESM entry, never the require-condition CJS stub', async () => {
+    const resolveId = getResolveId();
+    const ctx: ResolveIdCtx = {
+      resolve: async (id) => {
+        try {
+          return { id: fileURLToPath(import.meta.resolve(id)) };
+        } catch {
+          return null;
+        }
+      },
+    };
+
+    for (const id of ['vitest', 'vitest/config']) {
+      const resolved = idOf(await resolveId.call(ctx, id, PROJECT_IMPORTER, {}));
+      expect(resolved, `${id} should resolve to a real entry`).toBeTruthy();
+      expect(
+        resolved?.endsWith('.cjs'),
+        `${id} must resolve to the ESM entry, not a CJS throw-stub: ${resolved}`,
+      ).toBe(false);
+    }
+  });
+
+  it('ignores non-family specifiers (returns null, never calls this.resolve)', async () => {
+    const resolveId = getResolveId();
+    const { ctx, calls } = makeCtx(() => ({ id: '/should/not/be/used' }));
+
+    expect(idOf(await resolveId.call(ctx, 'react', PROJECT_IMPORTER, {}))).toBeUndefined();
+    expect(calls).toHaveLength(0);
+  });
+
+  it('ignores queried family ids (defers entirely to the default pipeline)', async () => {
+    const resolveId = getResolveId();
+    const { ctx, calls } = makeCtx(() => ({ id: '/should/not/be/used' }));
+
+    expect(idOf(await resolveId.call(ctx, 'vitest?v=1', PROJECT_IMPORTER, {}))).toBeUndefined();
+    expect(calls).toHaveLength(0);
+  });
+});
diff --git a/packages/cli/src/__tests__/exports-map.spec.ts b/packages/cli/src/__tests__/exports-map.spec.ts
new file mode 100644
index 0000000000..79d22e324b
--- /dev/null
+++ b/packages/cli/src/__tests__/exports-map.spec.ts
@@ -0,0 +1,139 @@
+/**
+ * Regression tests for the generated package.json `exports` map.
+ *
+ * Node.js package-exports conditions are order-sensitive: when resolving
+ * `require('vite-plus/test/config')`, Node walks the condition object and
+ * picks the first matching key. `default` matches everything, so a wrongly
+ * ordered map like `{ types, default, require }` causes CJS consumers to
+ * load the ESM shim — the `.cjs` shim becomes unreachable.
+ *
+ * These tests pin the invariant that any dual-condition entry emits
+ * `require` BEFORE `default` and that runtime resolution returns the
+ * expected file extension for each consumer.
+ */
+import fs from 'node:fs';
+import { createRequire } from 'node:module';
+import path from 'node:path';
+import url from 'node:url';
+
+import { describe, expect, it } from 'vitest';
+
+const cliPkgDir = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '../..');
+const cliPkgJsonPath = path.join(cliPkgDir, 'package.json');
+const requireFromHere = createRequire(import.meta.url);
+
+type ExportConditions = Record<string, unknown>;
+
+function isConditionObject(value: unknown): value is ExportConditions {
+  return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
+
+// `default` is a module-shape artifact, not a named export migration cares
+// about; only the named bindings need to survive the `vitest/config` rewrite.
+function namedValueExports(mod: Record<string, unknown>): string[] {
+  return Object.keys(mod).filter((key) => key !== 'default');
+}
+
+describe('package.json exports map', () => {
+  it('every dual-condition entry emits `require` before `default`', () => {
+    const pkg = JSON.parse(fs.readFileSync(cliPkgJsonPath, 'utf-8'));
+    const exports = pkg.exports as Record<string, unknown>;
+
+    const offenders: Array<{ path: string; order: string[] }> = [];
+
+    function walk(subpath: string, value: unknown) {
+      if (!isConditionObject(value)) {
+        return;
+      }
+      const keys = Object.keys(value);
+      const requireIdx = keys.indexOf('require');
+      const defaultIdx = keys.indexOf('default');
+      if (requireIdx !== -1 && defaultIdx !== -1 && requireIdx > defaultIdx) {
+        offenders.push({ path: subpath, order: keys });
+      }
+      for (const [k, v] of Object.entries(value)) {
+        walk(`${subpath} > ${k}`, v);
+      }
+    }
+
+    for (const [subpath, value] of Object.entries(exports)) {
+      walk(subpath, value);
+    }
+
+    expect(offenders, 'entries with require ordered after default').toEqual([]);
+  });
+
+  it('./test/config has both `require` and `default`, with `require` first', () => {
+    const pkg = JSON.parse(fs.readFileSync(cliPkgJsonPath, 'utf-8'));
+    const entry = (pkg.exports as Record<string, unknown>)['./test/config'];
+    expect(isConditionObject(entry)).toBe(true);
+    const keys = Object.keys(entry as ExportConditions);
+    expect(keys).toContain('require');
+    expect(keys).toContain('default');
+    expect(keys.indexOf('require')).toBeLessThan(keys.indexOf('default'));
+  });
+
+  it('`require.resolve("vite-plus/test/config")` resolves to the .cjs shim', () => {
+    const resolved = requireFromHere.resolve('vite-plus/test/config');
+    expect(resolved.endsWith('.cjs'), `resolved to ${resolved}`).toBe(true);
+  });
+
+  it('ESM `import.meta.resolve("vite-plus/test/config")` resolves to the .js shim', () => {
+    // import.meta.resolve is sync in modern Node (>= 20.6) and respects the
+    // `default` (ESM) condition for ESM consumers.
+    const resolved = import.meta.resolve('vite-plus/test/config');
+    expect(resolved.endsWith('.js'), `resolved to ${resolved}`).toBe(true);
+  });
+
+  it('CJS shim at ./test/config delegates to vitest/config via require()', () => {
+    const cfg = requireFromHere('vite-plus/test/config') as Record<string, unknown>;
+    expect(cfg).toBeTypeOf('object');
+    // vitest/config re-exports defineConfig / configDefaults — sanity-check one.
+    expect(typeof cfg.defineConfig).toBe('function');
+  });
+});
+
+/**
+ * Migration rewrites the `vitest/config` specifier to bare `vite-plus` (see the
+ * Rust `import_rewriter.rs` rule and the `prefer-vite-plus-imports` oxlint rule
+ * in `oxlint-plugin.ts`). After that rewrite a user's
+ * `import { x } from 'vitest/config'` (and the `require(...)` form) becomes
+ * `from 'vite-plus'`, so the `vite-plus` root MUST stay a superset of
+ * `vitest/config`'s runtime exports. The named re-export lists in `index.ts`
+ * (ESM) and `index.cts` (CJS) are SEPARATE manual lists, and both deliberately
+ * omit `defineConfig` (local `./define-config.ts` wrapper) and `mergeConfig`
+ * (`@voidzero-dev/vite-plus-core`), which are supplied by other paths. These
+ * guards assert the *aggregate* surface stays complete on BOTH module systems so
+ * a future vitest bump that adds a config export can't silently leave migrated
+ * imports `undefined` — and can't be fixed in one entry while the other regresses.
+ *
+ * Note: these cover the runtime (value) surface only. `vitest/config`'s
+ * type-only exports flow through a separate `export type { … }` list in
+ * `index.ts`; removal-drift there is already caught by the repo typecheck (a
+ * re-export of a deleted type fails to compile).
+ */
+describe('vite-plus root re-exports the full vitest/config surface', () => {
+  it('exposes every vitest/config value export on the ESM vite-plus root', async () => {
+    const [vitePlus, vitestConfig] = await Promise.all([
+      import('vite-plus'),
+      import('vitest/config'),
+    ]);
+    const expected = namedValueExports(vitestConfig);
+    expect(expected.length, 'sanity: vitest/config should expose value exports').toBeGreaterThan(0);
+    const missing = expected.filter(
+      (key) => !(key in vitePlus) || (vitePlus as Record<string, unknown>)[key] === undefined,
+    );
+    expect(missing, 'vitest/config value exports missing from the ESM vite-plus root').toEqual([]);
+  });
+
+  it('exposes every vitest/config value export on the CJS vite-plus root', () => {
+    // `require('vitest/config')` -> `require('vite-plus')` resolves the package
+    // root's `require` condition (the index.cts build), a separate manual list.
+    const vitePlus = requireFromHere('vite-plus') as Record<string, unknown>;
+    const vitestConfig = requireFromHere('vitest/config') as Record<string, unknown>;
+    const expected = namedValueExports(vitestConfig);
+    expect(expected.length, 'sanity: vitest/config should expose value exports').toBeGreaterThan(0);
+    const missing = expected.filter((key) => !(key in vitePlus) || vitePlus[key] === undefined);
+    expect(missing, 'vitest/config value exports missing from the CJS vite-plus root').toEqual([]);
+  });
+});
diff --git a/packages/cli/src/__tests__/index.spec.ts b/packages/cli/src/__tests__/index.spec.ts
index ce62e702e3..6980002a12 100644
--- a/packages/cli/src/__tests__/index.spec.ts
+++ b/packages/cli/src/__tests__/index.spec.ts
@@ -1,4 +1,4 @@
-import { afterEach, beforeEach, expect, test, vi } from '@voidzero-dev/vite-plus-test';
+import { afterEach, beforeEach, expect, test, vi } from 'vitest';
 
 import {
   configDefaults,
@@ -90,6 +90,22 @@ test('lazyPlugins wraps sync function returning a Promise into array', () => {
   expect(result).not.toBeInstanceOf(Promise);
 });
 
+// defineConfig auto-injects three internal plugins before user-supplied
+// plugins: vite-plus:vitest-resolver, vite-plus:auto-inline-matcher-deps, and
+// vite-plus:coverage-version-guard. The helper below strips those prefix
+// entries so tests can assert on user-supplied plugins only.
+const RESOLVER_PLUGIN_NAME = 'vite-plus:vitest-resolver';
+const AUTO_INLINE_PLUGIN_NAME = 'vite-plus:auto-inline-matcher-deps';
+const COVERAGE_GUARD_PLUGIN_NAME = 'vite-plus:coverage-version-guard';
+const userPlugins = (plugins: unknown): unknown[] => {
+  expect(Array.isArray(plugins)).toBe(true);
+  const arr = plugins as unknown[];
+  expect((arr[0] as { name?: string })?.name).toBe(RESOLVER_PLUGIN_NAME);
+  expect((arr[1] as { name?: string })?.name).toBe(AUTO_INLINE_PLUGIN_NAME);
+  expect((arr[2] as { name?: string })?.name).toBe(COVERAGE_GUARD_PLUGIN_NAME);
+  return arr.slice(3);
+};
+
 // lazyPlugins type compatibility tests — these verify at compile time that
 // lazyPlugins return types satisfy Vite's plugins?: PluginOption[] field.
 
@@ -99,7 +115,7 @@ test('lazyPlugins sync return type satisfies plugins field', () => {
   const config = defineConfig({
     plugins: lazyPlugins(() => [{ name: 'sync-type-test' }]),
   });
-  expect(config.plugins?.length).toBe(1);
+  expect(userPlugins(config.plugins).length).toBe(1);
 });
 
 test('lazyPlugins async return type satisfies plugins field', () => {
@@ -119,7 +135,8 @@ test('lazyPlugins undefined return satisfies plugins field', () => {
   const config = defineConfig({
     plugins: lazyPlugins(() => [{ name: 'skipped' }]),
   });
-  expect(config.plugins).toBeUndefined();
+  // lazyPlugins returns undefined, but defineConfig still injects its rewrite plugin.
+  expect(userPlugins(config.plugins).length).toBe(0);
 });
 
 test('lazyPlugins with vitest configureVitest plugin satisfies plugins field', () => {
@@ -132,7 +149,7 @@ test('lazyPlugins with vitest configureVitest plugin satisfies plugins field', (
       },
     ]),
   });
-  expect(config.plugins?.length).toBe(1);
+  expect(userPlugins(config.plugins).length).toBe(1);
 });
 
 // defineConfig compatibility tests
@@ -141,35 +158,35 @@ test('defineConfig passes through plain plugins array', () => {
   const config = defineConfig({
     plugins: [{ name: 'test-plugin' }],
   });
-  expect(config.plugins?.length).toBe(1);
+  expect(userPlugins(config.plugins).length).toBe(1);
 });
 
 test('defineConfig supports Plugin objects in plugins array', () => {
   const config = defineConfig({
     plugins: [{ name: 'plugin-a' }, { name: 'plugin-b' }],
   });
-  expect(config.plugins?.length).toBe(2);
+  expect(userPlugins(config.plugins).length).toBe(2);
 });
 
 test('defineConfig supports falsy values in plugins array', () => {
   const config = defineConfig({
     plugins: [{ name: 'real-plugin' }, false, null, undefined],
   });
-  expect(config.plugins?.length).toBe(4);
+  expect(userPlugins(config.plugins).length).toBe(4);
 });
 
 test('defineConfig supports nested plugin arrays', () => {
   const config = defineConfig({
     plugins: [[{ name: 'nested-a' }, { name: 'nested-b' }], { name: 'top-level' }],
   });
-  expect(config.plugins?.length).toBe(2);
+  expect(userPlugins(config.plugins).length).toBe(2);
 });
 
 test('defineConfig supports Promise<Plugin> in plugins array', () => {
   const config = defineConfig({
     plugins: [Promise.resolve({ name: 'async-plugin' })],
   });
-  expect(config.plugins?.length).toBe(1);
+  expect(userPlugins(config.plugins).length).toBe(1);
 });
 
 test('defineConfig supports mixed PluginOption types in array', () => {
@@ -183,19 +200,20 @@ test('defineConfig supports mixed PluginOption types in array', () => {
       undefined,
     ],
   });
-  expect(config.plugins?.length).toBe(6);
+  expect(userPlugins(config.plugins).length).toBe(6);
 });
 
 test('defineConfig supports empty plugins array', () => {
   const config = defineConfig({
     plugins: [],
   });
-  expect(config.plugins?.length).toBe(0);
+  expect(userPlugins(config.plugins).length).toBe(0);
 });
 
 test('defineConfig supports config without plugins', () => {
   const config = defineConfig({});
-  expect(config.plugins).toBeUndefined();
+  // defineConfig always injects its rewrite plugin even when user omits `plugins`.
+  expect(userPlugins(config.plugins).length).toBe(0);
 });
 
 test('defineConfig supports function config with plain plugins array', () => {
@@ -203,7 +221,7 @@ test('defineConfig supports function config with plain plugins array', () => {
     plugins: [{ name: 'fn-plugin' }],
   }));
   const config = configFn({ command: 'build', mode: 'production' });
-  expect(config.plugins?.length).toBe(1);
+  expect(userPlugins(config.plugins).length).toBe(1);
 });
 
 test('defineConfig supports async function config with plain plugins array', async () => {
@@ -211,7 +229,7 @@ test('defineConfig supports async function config with plain plugins array', asy
     plugins: [{ name: 'async-fn-plugin' }],
   }));
   const config = await configFn({ command: 'build', mode: 'production' });
-  expect(config.plugins?.length).toBe(1);
+  expect(userPlugins(config.plugins).length).toBe(1);
 });
 
 test('defineConfig supports vitest plugin with configureVitest hook', () => {
@@ -225,6 +243,7 @@ test('defineConfig supports vitest plugin with configureVitest hook', () => {
       },
     ],
   });
-  expect(config.plugins?.length).toBe(1);
-  expect((config.plugins?.[0] as { name: string })?.name).toBe('vitest-plugin');
+  const userOnly = userPlugins(config.plugins);
+  expect(userOnly.length).toBe(1);
+  expect((userOnly[0] as { name: string })?.name).toBe('vitest-plugin');
 });
diff --git a/packages/cli/src/__tests__/oxlint-plugin.spec.ts b/packages/cli/src/__tests__/oxlint-plugin.spec.ts
index 45a5d1bb44..0e9f4c1b6b 100644
--- a/packages/cli/src/__tests__/oxlint-plugin.spec.ts
+++ b/packages/cli/src/__tests__/oxlint-plugin.spec.ts
@@ -66,6 +66,10 @@ describe('rewriteVitePlusImportSpecifier', () => {
     expect(rewriteVitePlusImportSpecifier('@vitest/browser/locators')).toBe(
       'vite-plus/test/locators',
     );
+    expect(rewriteVitePlusImportSpecifier('@vitest/browser/matchers')).toBe(
+      'vite-plus/test/matchers',
+    );
+    expect(rewriteVitePlusImportSpecifier('@vitest/browser/utils')).toBe('vite-plus/test/utils');
     expect(rewriteVitePlusImportSpecifier('@vitest/browser-playwright/context')).toBe(
       'vite-plus/test/browser/context',
     );
@@ -79,6 +83,12 @@ describe('rewriteVitePlusImportSpecifier', () => {
       'vite-plus/test/browser/providers/webdriverio',
     );
     expect(rewriteVitePlusImportSpecifier('@vitest/browser-playwright/locators')).toBeNull();
+    // `vitest/package.json` must NOT be rewritten — `vite-plus` does not export
+    // `./test/package.json`, so rewriting would break resolution. Mirrors the
+    // migrate rewriter's exclusion.
+    expect(rewriteVitePlusImportSpecifier('vitest/package.json')).toBeNull();
+    // ...but other `vitest/<sub>` specifiers still rewrite normally.
+    expect(rewriteVitePlusImportSpecifier('vitest/node')).toBe('vite-plus/test/node');
     expect(rewriteVitePlusImportSpecifier('tsx')).toBeNull();
   });
 });
@@ -91,6 +101,9 @@ new RuleTester({
   valid: [
     `import { defineConfig } from 'vite-plus'`,
     `export { expect } from 'vite-plus/test'`,
+    // `vitest/package.json` must NOT be autofixed — `vite-plus` has no
+    // `./test/package.json` export, so a rewrite would break resolution.
+    `import pkg from 'vitest/package.json'`,
     {
       code: `declare module 'vite-plus/test/browser' {}`,
       filename: 'types.ts',
@@ -107,8 +120,43 @@ new RuleTester({
       code: `type TestFn = typeof import('vite-plus/test')['test']`,
       filename: 'types.ts',
     },
+    // `declare module 'vitest…'` / `declare module '@vitest/browser…'` are
+    // intentionally NOT autofixed — they target the upstream module identity
+    // so type augmentations merge with what `vite-plus/test*` re-exports.
+    {
+      code: `declare module 'vitest' {}`,
+      filename: 'types.ts',
+    },
+    {
+      code: `declare module 'vitest/node' {}`,
+      filename: 'types.ts',
+    },
+    {
+      code: `declare module '@vitest/browser' {}`,
+      filename: 'types.ts',
+    },
+    {
+      code: `declare module '@vitest/browser/context' {}`,
+      filename: 'types.ts',
+    },
+    {
+      code: `declare module '@vitest/browser-playwright' {}`,
+      filename: 'types.ts',
+    },
+    {
+      code: `declare module '@vitest/browser-playwright/context' {}`,
+      filename: 'types.ts',
+    },
   ],
   invalid: [
+    {
+      // `declare module 'vite'` IS rewritten — the vite family doesn't
+      // re-export upstream vite types so augmentation works against either id.
+      code: `declare module 'vite' {}`,
+      errors: 1,
+      filename: 'types.ts',
+      output: `declare module 'vite-plus' {}`,
+    },
     {
       code: `import { defineConfig } from 'vite'`,
       errors: 1,
@@ -131,22 +179,20 @@ new RuleTester({
       output: `type TestFn = typeof import('vite-plus/test')['test']`,
     },
     {
-      code: `declare module '@vitest/browser-playwright' {}`,
+      code: `type BrowserClient = typeof import('@vitest/browser/client')`,
       errors: 1,
       filename: 'types.ts',
-      output: `declare module 'vite-plus/test/browser-playwright' {}`,
+      output: `type BrowserClient = typeof import('vite-plus/test/client')`,
     },
     {
-      code: `declare module '@vitest/browser-playwright/context' {}`,
+      code: `import { expect } from '@vitest/browser/matchers'`,
       errors: 1,
-      filename: 'types.ts',
-      output: `declare module 'vite-plus/test/browser/context' {}`,
+      output: `import { expect } from 'vite-plus/test/matchers'`,
     },
     {
-      code: `type BrowserClient = typeof import('@vitest/browser/client')`,
+      code: `import { getElementError } from '@vitest/browser/utils'`,
       errors: 1,
-      filename: 'types.ts',
-      output: `type BrowserClient = typeof import('vite-plus/test/client')`,
+      output: `import { getElementError } from 'vite-plus/test/utils'`,
     },
     {
       code: `type PlaywrightProvider = typeof import('@vitest/browser-playwright/provider')`,
diff --git a/packages/cli/src/__tests__/pack.spec.ts b/packages/cli/src/__tests__/pack.spec.ts
index 4ab798865c..cda2669b89 100644
--- a/packages/cli/src/__tests__/pack.spec.ts
+++ b/packages/cli/src/__tests__/pack.spec.ts
@@ -1,4 +1,4 @@
-import { expect, test } from '@voidzero-dev/vite-plus-test';
+import { expect, test } from 'vitest';
 
 import {
   build,
diff --git a/packages/cli/src/__tests__/resolve-lint.spec.ts b/packages/cli/src/__tests__/resolve-lint.spec.ts
index ff7be75548..c8220e9200 100644
--- a/packages/cli/src/__tests__/resolve-lint.spec.ts
+++ b/packages/cli/src/__tests__/resolve-lint.spec.ts
@@ -1,6 +1,6 @@
 import { existsSync } from 'node:fs';
 
-import { describe, expect, it } from '@voidzero-dev/vite-plus-test';
+import { describe, expect, it } from 'vitest';
 
 import { lint, resolveWindowsTsgolintExecutable } from '../resolve-lint.js';
 
diff --git a/packages/cli/src/__tests__/resolve-test.spec.ts b/packages/cli/src/__tests__/resolve-test.spec.ts
new file mode 100644
index 0000000000..f5acc13f30
--- /dev/null
+++ b/packages/cli/src/__tests__/resolve-test.spec.ts
@@ -0,0 +1,74 @@
+import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+
+import { afterEach, beforeEach, describe, expect, it } from 'vitest';
+
+import { test } from '../resolve-test.js';
+import { resolve, resolveBundled } from '../utils/constants.js';
+
+describe('resolve-test', () => {
+  it('resolves a vitest binary that exists', async () => {
+    const { binPath } = await test();
+
+    expect(binPath).toBeTruthy();
+    expect(
+      existsSync(binPath),
+      `vitest binPath should point to an existing file, got: ${binPath}`,
+    ).toBe(true);
+    expect(binPath).toContain('vitest');
+  });
+
+  // `vp test` MUST run the Vitest bundled with the CLI, not a project-local
+  // copy: the `vite-plus/test*` shims `export * from 'vitest'`, which Node
+  // resolves to vite-plus's own bundled Vitest. Spawning a different physical
+  // Vitest as the runner yields two copies in one run — a classic source of
+  // internal-state / mock-hoisting mismatches.
+  describe('prefers the bundled vitest over a project-local copy', () => {
+    let originalCwd: string;
+    let projectDir: string;
+
+    beforeEach(() => {
+      originalCwd = process.cwd();
+      // realpath so comparisons match `require.resolve` output, which resolves
+      // symlinks (macOS `tmpdir()` is /var → /private/var).
+      projectDir = realpathSync(mkdtempSync(join(tmpdir(), 'vp-resolve-test-')));
+      // A fake project-local vitest that must NOT be selected as the runner.
+      const fakeVitest = join(projectDir, 'node_modules', 'vitest');
+      mkdirSync(fakeVitest, { recursive: true });
+      writeFileSync(
+        join(fakeVitest, 'package.json'),
+        JSON.stringify({
+          name: 'vitest',
+          version: '0.0.0-fake-project-copy',
+          bin: { vitest: 'fake.mjs' },
+        }),
+      );
+      writeFileSync(join(fakeVitest, 'fake.mjs'), '');
+      process.chdir(projectDir);
+    });
+
+    afterEach(() => {
+      process.chdir(originalCwd);
+      rmSync(projectDir, { recursive: true, force: true });
+    });
+
+    it('runs the bundled vitest, never the project copy', async () => {
+      const { binPath } = await test();
+
+      expect(
+        binPath.startsWith(projectDir),
+        `runner should be the bundled vitest, not the project copy at ${projectDir}, got: ${binPath}`,
+      ).toBe(false);
+      expect(binPath).not.toContain('0.0.0-fake');
+      expect(existsSync(binPath)).toBe(true);
+    });
+
+    it('contrasts with CWD-first resolve(), which would pick the project copy', () => {
+      // The fixture genuinely shadows: the old CWD-first helper finds the fake…
+      expect(resolve('vitest/package.json').startsWith(projectDir)).toBe(true);
+      // …while the bundle-first helper does not.
+      expect(resolveBundled('vitest/package.json').startsWith(projectDir)).toBe(false);
+    });
+  });
+});
diff --git a/packages/cli/src/__tests__/versions.spec.ts b/packages/cli/src/__tests__/versions.spec.ts
index c8ef59dd9a..c66900fe7f 100644
--- a/packages/cli/src/__tests__/versions.spec.ts
+++ b/packages/cli/src/__tests__/versions.spec.ts
@@ -5,15 +5,16 @@
  * that syncVersionsExport() produces correct artifacts.
  */
 import fs from 'node:fs';
+import { createRequire } from 'node:module';
 import path from 'node:path';
 import url from 'node:url';
 
-import { describe, expect, it } from '@voidzero-dev/vite-plus-test';
+import { describe, expect, it } from 'vitest';
 
 const cliPkgDir = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '../..');
 const distDir = path.join(cliPkgDir, 'dist');
 const corePkgPath = path.join(cliPkgDir, '../core/package.json');
-const testPkgPath = path.join(cliPkgDir, '../test/package.json');
+const vitestPkgPath = createRequire(import.meta.url).resolve('vitest/package.json');
 
 describe('versions export', () => {
   describe('build artifacts', () => {
@@ -48,15 +49,13 @@ describe('versions export', () => {
       }
     });
 
-    it('should contain all test bundledVersions', async () => {
-      const testPkg = JSON.parse(fs.readFileSync(testPkgPath, 'utf-8'));
+    it('should contain vitest version matching installed package', async () => {
+      const vitestPkg = JSON.parse(fs.readFileSync(vitestPkgPath, 'utf-8'));
       const mod = await import('../../dist/versions.js');
       const versions = mod.versions as Record<string, string>;
-      for (const [key, value] of Object.entries(
-        testPkg.bundledVersions as Record<string, string>,
-      )) {
-        expect(versions[key], `versions.${key} should match test bundledVersions`).toBe(value);
-      }
+      expect(versions.vitest, 'versions.vitest should match installed vitest version').toBe(
+        vitestPkg.version,
+      );
     });
   });
 
diff --git a/packages/cli/src/define-config.ts b/packages/cli/src/define-config.ts
index 4a861014be..649cb713d2 100644
--- a/packages/cli/src/define-config.ts
+++ b/packages/cli/src/define-config.ts
@@ -1,15 +1,26 @@
+import { readFileSync } from 'node:fs';
+import { createRequire } from 'node:module';
+import { fileURLToPath } from 'node:url';
+
 import type { PluginOption, UserConfig } from '@voidzero-dev/vite-plus-core';
+import type { OxfmtConfig } from 'oxfmt';
+import type { OxlintConfig } from 'oxlint';
 import {
   defineConfig as viteDefineConfig,
+  defineProject as viteDefineProject,
   type ConfigEnv,
-} from '@voidzero-dev/vite-plus-test/config';
-import type { OxfmtConfig } from 'oxfmt';
-import type { OxlintConfig } from 'oxlint';
+  type TestProjectConfiguration,
+  type UserProjectConfigExport,
+  type UserProjectConfigFn,
+  type UserWorkspaceConfig,
+} from 'vitest/config';
+import type { InlineConfig as VitestInlineConfig } from 'vitest/node';
 
 import type { CreateTemplateEntry } from './create/org-manifest.ts';
 import type { PackUserConfig } from './pack.ts';
 import type { RunConfig } from './run-config.ts';
 import type { StagedConfig } from './staged-config.ts';
+import { VITEST_VERSION } from './utils/constants.ts';
 
 declare module '@voidzero-dev/vite-plus-core' {
   interface UserConfig {
@@ -49,6 +60,16 @@ declare module '@voidzero-dev/vite-plus-core' {
        */
       templates?: CreateTemplateEntry[];
     };
+
+    /**
+     * Vitest test configuration.
+     *
+     * Vitest augments vite's `UserConfig` with a `test` field via
+     * `declare module 'vite'`, but vite-plus-core is a fork of vite so that
+     * augmentation does not apply here. Re-declare it locally so user
+     * configs like `defineConfig({ test: { globals: true } })` typecheck.
+     */
+    test?: VitestInlineConfig;
   }
 }
 
@@ -62,6 +83,595 @@ type ViteUserConfigExport =
   | ViteUserConfigFnPromise
   | ViteUserConfigFn;
 
+/**
+ * `require` anchored at THIS module's location so `require.resolve` reaches
+ * the `vitest` / `@vitest/*` family that the `vite-plus` package directly
+ * depends on — even from a consumer project where they are only transitive.
+ * Used to locate the bundled `vitest` package (its `package.json`), NOT to
+ * resolve module ENTRIES: `require.resolve` applies the `require` export
+ * condition, which selects Vitest's CJS entries — for the bare `vitest` root
+ * a throw-stub (`index.cjs` — "Vitest cannot be imported … using require()"),
+ * and for subpaths the CJS build (e.g. `vitest/config` → `config.cjs`) rather
+ * than the ESM entry the test server's module graph needs. Module entries are
+ * resolved through Vite's own resolver instead (see
+ * [[vitePlusVitestResolverPlugin]]), which honours ESM conditions.
+ *
+ * `define-config.ts` is bundled by tsdown in BOTH formats: ESM (`shims: true`,
+ * which defines a module-scoped `__dirname`) and CJS (where `__dirname` is the
+ * Node global). The guard picks `__dirname` whenever it exists and otherwise
+ * falls back to `import.meta.url`; tsdown rewrites the latter to
+ * `pathToFileURL(__filename).href` in the CJS bundle, so it is safe in both.
+ */
+const vitePlusRequire = createRequire(
+  typeof __dirname !== 'undefined' ? __dirname : import.meta.url,
+);
+
+/**
+ * Absolute path to THIS module, used as a `this.resolve` importer so Vite's
+ * resolver roots the `vitest` / `@vitest/*` family at `vite-plus`'s own
+ * location — reaching its direct deps (`vitest`, `vitest/*`, `@vitest/browser*`)
+ * even from a consumer project where they are only transitive.
+ *
+ * `import.meta.url` is native in the ESM bundle and rewritten by tsdown to
+ * `pathToFileURL(__filename).href` in the CJS bundle, so it is a valid file URL
+ * in both.
+ */
+const vitePlusModuleFile = fileURLToPath(import.meta.url);
+
+/**
+ * Absolute path to the bundled `vitest` package's `package.json`, used as a
+ * second `this.resolve` importer. The nested `@vitest/*` family (`@vitest/expect`,
+ * `@vitest/runner`, `@vitest/snapshot`, …) are dependencies of `vitest` itself —
+ * not direct deps of `vite-plus` — so under pnpm's isolated layout they are
+ * reachable from `vitest`'s location but not from [[vitePlusModuleFile]].
+ * Resolving `package.json` is condition-agnostic, so this is safe with
+ * `require.resolve`. Cached; `null` once an attempt has failed so we never retry.
+ */
+let vitestAnchor: string | null | undefined;
+function getVitestAnchor(): string | null {
+  if (vitestAnchor !== undefined) {
+    return vitestAnchor;
+  }
+  try {
+    vitestAnchor = vitePlusRequire.resolve('vitest/package.json');
+  } catch {
+    vitestAnchor = null;
+  }
+  return vitestAnchor;
+}
+
+/**
+ * Match the `vitest` / `@vitest/*` family of bare specifiers — the imports a
+ * browser-mode Vite dev server must resolve. Any query string is stripped
+ * first; relative (`./`), absolute (`/`), and virtual (`\0`) ids never match.
+ *
+ * Exported for unit testing.
+ */
+export function isVitestFamilySpecifier(id: string): boolean {
+  const bare = id.split('?')[0];
+  if (bare.startsWith('.') || bare.startsWith('/') || bare.startsWith('\0')) {
+    return false;
+  }
+  return (
+    bare === 'vitest' ||
+    bare.startsWith('vitest/') ||
+    bare === '@vitest/browser' ||
+    bare.startsWith('@vitest/')
+  );
+}
+
+/**
+ * Rescue `vitest` / `@vitest/*` resolution for browser-mode tests.
+ *
+ * In an established project that depends only on `vite-plus`, both `vitest`
+ * and `@vitest/browser` are transitive deps. pnpm's isolated layout only
+ * exposes a package's *direct* deps, so the browser-mode Vite dev server
+ * (rooted at the consumer project) cannot resolve `vitest/internal/browser`,
+ * `@vitest/expect`, etc. Non-browser tests are unaffected — vitest's own
+ * module runner handles resolution there.
+ *
+ * This plugin re-resolves the `vitest` / `@vitest/*` family through Vite's OWN
+ * resolver, but ROOTED at `vite-plus`'s location ([[vitePlusModuleFile]]) and
+ * then the bundled `vitest`'s location ([[getVitestAnchor]]) BEFORE the
+ * project. So every such import binds to the same physical (pinned) Vitest that
+ * `vp test` spawns as the runner (see `resolveBundled` in `resolve-test.ts`)
+ * and that the `vite-plus/test*` shims re-export. Were a project-local Vitest
+ * preferred instead, a project that keeps its own `vitest` dependency would
+ * split the run across two physical Vitest module instances — the runner
+ * (bundled) vs. the test files' `vi`/`expect`/runner internals (project) — a
+ * classic source of internal-state and mock-hoisting mismatches. For the common
+ * migrated layout (a project depending only on `vite-plus`) nothing in this
+ * family is resolvable from the project root under pnpm's isolated layout
+ * anyway, so default resolution would return `null` there regardless;
+ * bundle-first only changes the project-keeps-its-own-`vitest` case, which is
+ * exactly the case we want pinned.
+ *
+ * Resolution goes through `this.resolve` (NOT [[vitePlusRequire]].resolve) so
+ * Vite's ESM export conditions are honoured: a raw `require.resolve` would pick
+ * Vitest's CJS `require`-condition entry — a throw-stub for the bare `vitest`
+ * root (`index.cjs`), and the CJS build for subpaths (e.g. `vitest/config` →
+ * `config.cjs`) instead of the ESM entry. Two bundled anchors are tried because `@vitest/browser*` are
+ * direct deps of `vite-plus` (reachable from [[vitePlusModuleFile]]) while the
+ * nested `@vitest/*` family are deps of `vitest` (reachable only from the
+ * `vitest` anchor). The project root remains the last resort for any family id
+ * the bundled tree cannot resolve, so this is never worse than deferring first.
+ *
+ * Two intentional limits of routing through `this.resolve`:
+ *   - An EXPLICIT project `resolve.alias` / `resolve.dedupe` on the vitest
+ *     family takes precedence (Vite's pipeline applies it even from a bundled
+ *     anchor). Neither is set by default in Vitest 4.x, so this only affects
+ *     projects that deliberately re-point the family — treated as an opt-out of
+ *     pinning, not defeated silently.
+ *   - Coverage providers (`@vitest/coverage-v8` / `-istanbul`) are NOT shipped
+ *     with `vite-plus`, so they hit the project fallback below. Under
+ *     `--coverage`, a project-installed provider of a different version pairs
+ *     with the bundled runner; Vitest only WARNS on the version skew and then
+ *     runs mixed versions (its provider `_initialize` logs and continues, it
+ *     does not throw), which silently yields unreliable coverage — so
+ *     [[vitePlusCoverageVersionGuardPlugin]] fails fast on a mismatch instead.
+ */
+function vitePlusVitestResolverPlugin(): PluginOption {
+  return {
+    name: 'vite-plus:vitest-resolver',
+    enforce: 'pre',
+    async resolveId(id, importer, options) {
+      if (!isVitestFamilySpecifier(id)) {
+        return null;
+      }
+      // The redirected imports are all clean bare specifiers; a queried id is
+      // outside the scope of this resolver — let the default resolver handle it.
+      if (id.includes('?')) {
+        return null;
+      }
+      // Re-resolve from vite-plus's own (pinned) tree via Vite's resolver so the
+      // runner and every imported Vitest module are a single physical instance.
+      // `skipSelf` avoids infinite recursion back into this plugin.
+      const vitestAnchorPath = getVitestAnchor();
+      const bundledAnchors = vitestAnchorPath
+        ? [vitePlusModuleFile, vitestAnchorPath]
+        : [vitePlusModuleFile];
+      for (const anchor of bundledAnchors) {
+        const resolved = await this.resolve(id, anchor, { ...options, skipSelf: true });
+        if (resolved) {
+          return resolved;
+        }
+      }
+      // Last resort: default project-rooted resolution for any family id the
+      // bundled tree cannot resolve (e.g. coverage providers not shipped with
+      // vite-plus).
+      return this.resolve(id, importer, { ...options, skipSelf: true });
+    },
+  };
+}
+
+/**
+ * Packages that register Vitest `expect` matchers via `expect.extend()` from
+ * a side-effect import. When Vite serves these from a separate module graph
+ * than the test runtime, the matchers register on a different `expect`
+ * instance and `expect(...).<matcher>` is undefined at call time (vitest
+ * issue #897). Inlining them into the test server's module graph forces
+ * registration on the same instance.
+ *
+ * Only packages that are **installed** in the consumer project are inlined.
+ * Absent packages are silently skipped so the server-deps optimizer never
+ * tries to resolve a name that does not exist in the project's node_modules.
+ *
+ * The check is deferred to a `configResolved` plugin hook so that
+ * `resolvedConfig.root` points at the actual project root (the value vite has
+ * already normalised), rather than relying on `process.cwd()` at config-load
+ * time (which can differ in workspace / monorepo setups).
+ *
+ * Exported for unit testing.
+ */
+export const AUTO_INLINE_DEPS: ReadonlyArray<string> = [
+  '@testing-library/jest-dom',
+  '@storybook/test',
+  'jest-extended',
+];
+
+/**
+ * Compute the merged `test.server.deps.inline` list for a given project root,
+ * appending only those entries from [[AUTO_INLINE_DEPS]] that are actually
+ * installed in the project.
+ *
+ * Returns `null` when nothing needs to change (either `inline: true` or an
+ * empty result), so the caller can skip the mutation step.
+ *
+ * Exported for unit testing. The `_createRequire` parameter lets tests inject
+ * a controlled resolver without needing to spy on Node's ESM module namespace.
+ */
+export function computeAutoInlineList(
+  existingInline: (string | RegExp)[] | true | undefined,
+  projectRoot: string,
+  _createRequire: (from: string) => { resolve: (id: string) => string } = createRequire,
+): (string | RegExp)[] | null {
+  // User opted into "inline everything" — don't touch.
+  if (existingInline === true) {
+    return null;
+  }
+  // Build a require resolver anchored at the project root so we only
+  // inline packages that are actually installed there.
+  const projectRequire = _createRequire(`${projectRoot}/package.json`);
+  // Start from a copy of the user-supplied array (or a fresh array when
+  // none was provided) so the originating user-config object is not mutated.
+  const merged: (string | RegExp)[] = Array.isArray(existingInline) ? [...existingInline] : [];
+  for (const pkg of AUTO_INLINE_DEPS) {
+    // Skip if already covered by a string or regexp entry.
+    if (merged.some((entry) => entry === pkg || (entry instanceof RegExp && entry.test(pkg)))) {
+      continue;
+    }
+    try {
+      projectRequire.resolve(pkg);
+    } catch {
+      // Package not installed in the project — skip silently.
+      continue;
+    }
+    merged.push(pkg);
+  }
+  // Return null when there's nothing new to inline so callers can bail early.
+  const hadEntries = Array.isArray(existingInline) ? existingInline.length : 0;
+  if (merged.length === hadEntries) {
+    return null;
+  }
+  return merged;
+}
+
+function vitePlusAutoInlineMatcherPlugin(): PluginOption {
+  return {
+    name: 'vite-plus:auto-inline-matcher-deps',
+    enforce: 'pre',
+    configResolved(resolvedConfig) {
+      // Access the vitest test config via the augmented field. Vitest augments
+      // vite's `UserConfig` but not `ResolvedConfig`, so we use `any` here.
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      const testConfig = (resolvedConfig as any).test as
+        | { server?: { deps?: { inline?: (string | RegExp)[] | true } } }
+        | undefined;
+      const merged = computeAutoInlineList(testConfig?.server?.deps?.inline, resolvedConfig.root);
+      if (merged === null) {
+        return;
+      }
+      // Mutate the resolved config so the finalised inline list is visible
+      // to vitest when it reads test.server.deps.inline.
+      if (!testConfig) {
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        (resolvedConfig as any).test = { server: { deps: { inline: merged } } };
+      } else {
+        if (!testConfig.server) {
+          testConfig.server = {};
+        }
+        if (!testConfig.server.deps) {
+          testConfig.server.deps = {};
+        }
+        testConfig.server.deps.inline = merged;
+      }
+    },
+  };
+}
+
+/** Coverage providers vite-plus can version-check against the bundled runner. */
+const KNOWN_COVERAGE_PROVIDERS: ReadonlySet<string> = new Set(['v8', 'istanbul']);
+
+/**
+ * Resolve the coverage provider package name that should be version-checked, or
+ * `null` when no check applies (coverage off, or a `custom`/unknown provider
+ * vite-plus does not bundle a runner for).
+ *
+ * Takes Vitest's OWN resolved coverage options (`enabled`/`provider`), which the
+ * `configureVitest` hook exposes AFTER Vitest's CLI parser has run — so the
+ * `--coverage` family of flags is already folded into `enabled`/`provider` and
+ * we never re-parse `process.argv` ourselves. Unset `provider` defaults to `v8`
+ * (Vitest's default).
+ *
+ * Exported for unit testing.
+ */
+export function resolveCoverageProviderToCheck(
+  coverage: { enabled?: boolean; provider?: string } | undefined,
+): string | null {
+  if (!coverage?.enabled) {
+    return null;
+  }
+  const name = coverage.provider ?? 'v8';
+  return KNOWN_COVERAGE_PROVIDERS.has(name) ? `@vitest/coverage-${name}` : null;
+}
+
+/**
+ * vite-plus bundles `vitest@VITEST_VERSION` as the test runner, but coverage
+ * providers (`@vitest/coverage-v8` / `-istanbul`) are project-installed peer
+ * deps it does not ship. Vitest only PRINTS A WARNING on a provider/runner
+ * version skew and then runs mixed versions (verified in 4.1.9: the provider's
+ * `_initialize` calls `logger.warn`, it never throws), silently producing
+ * unreliable coverage. Fail fast instead.
+ *
+ * Exported for unit testing.
+ */
+export function assertCoverageProviderVersionMatch(
+  providerPackageName: string,
+  installedVersion: string | null | undefined,
+  expectedVersion: string = VITEST_VERSION,
+): void {
+  if (installedVersion && installedVersion !== expectedVersion) {
+    throw new Error(
+      `vite-plus bundles vitest@${expectedVersion}, but ${providerPackageName}@${installedVersion} ` +
+        `is installed. A coverage provider must match the test runner version: Vitest only prints a ` +
+        `warning on a mismatch and then runs mixed versions, which produces unreliable coverage. ` +
+        `Pin ${providerPackageName} to ${expectedVersion} in your dependencies.`,
+    );
+  }
+}
+
+/**
+ * The bundled vitest's `package.json` path — the SAME anchor Vitest's own
+ * `import('@vitest/coverage-*')` resolves against (its dist walks up from here).
+ * Used as a fallback resolution anchor for the coverage guard. Lazily computed
+ * and cached; `null` when the bundled vitest is somehow unreachable, in which
+ * case the guard simply relies on the project-root anchor.
+ */
+let bundledVitestAnchorCache: string | null | undefined;
+function bundledVitestAnchor(): string | null {
+  if (bundledVitestAnchorCache === undefined) {
+    try {
+      bundledVitestAnchorCache = createRequire(import.meta.url).resolve('vitest/package.json');
+    } catch {
+      bundledVitestAnchorCache = null;
+    }
+  }
+  return bundledVitestAnchorCache;
+}
+
+/**
+ * Read a project-installed coverage provider's version, mirroring how Vitest
+ * itself resolves the provider. Vitest install-checks it from BOTH the runner
+ * root AND its own bundled dir (`isPackageExists(dep, {paths:[root, vitestDir]})`)
+ * and then loads it via a bare `import('@vitest/coverage-*')` anchored at that
+ * bundled dir. So the guard tries the project root FIRST — the supported layout
+ * where a directly-declared provider is symlinked at the root, the same copy the
+ * bundled vitest walks up to — then falls back to the bundled-vitest anchor,
+ * which catches hoisted / pnpm peer-set layouts where the provider lives next to
+ * vitest but is not resolvable from the project root (a silent skip otherwise).
+ * `@vitest/coverage-*`'s exports map has a `"./*": "./*"` catch-all, so
+ * `./package.json` is resolvable. Returns `null` when no anchor can resolve it —
+ * Vitest then emits its own (already clear) missing-provider error.
+ *
+ * The `_createRequire` / `_readFile` parameters let tests inject controlled
+ * resolvers without spying on Node's module/fs namespaces.
+ */
+function readInstalledCoverageProviderVersion(
+  providerPackageName: string,
+  projectRoot: string,
+  _createRequire: (from: string) => { resolve: (id: string) => string } = createRequire,
+  _readFile: (path: string) => string = (path) => readFileSync(path, 'utf8'),
+): string | null {
+  const anchors = [`${projectRoot}/package.json`, bundledVitestAnchor()];
+  for (const anchor of anchors) {
+    if (!anchor) {
+      continue;
+    }
+    try {
+      const req = _createRequire(anchor);
+      const pkgJsonPath = req.resolve(`${providerPackageName}/package.json`);
+      const parsed = JSON.parse(_readFile(pkgJsonPath)) as { version?: string };
+      if (parsed.version) {
+        return parsed.version;
+      }
+    } catch {
+      // This anchor can't resolve the provider — try the next one.
+    }
+  }
+  return null;
+}
+
+/**
+ * Orchestrates the coverage version guard: detect the active provider from
+ * Vitest's resolved coverage options, read its installed version from the
+ * project root, and throw on a mismatch. A no-op when coverage is off or the
+ * provider is not installed.
+ *
+ * Exported (with injectable `deps`) for unit testing.
+ */
+export function checkCoverageProviderVersion(
+  coverage: { enabled?: boolean; provider?: string } | undefined,
+  projectRoot: string,
+  deps: {
+    createRequire?: (from: string) => { resolve: (id: string) => string };
+    readFile?: (path: string) => string;
+  } = {},
+): void {
+  const providerPackageName = resolveCoverageProviderToCheck(coverage);
+  if (!providerPackageName) {
+    return;
+  }
+  const installedVersion = readInstalledCoverageProviderVersion(
+    providerPackageName,
+    projectRoot,
+    deps.createRequire,
+    deps.readFile,
+  );
+  assertCoverageProviderVersionMatch(providerPackageName, installedVersion);
+}
+
+interface ResolvedCoverageView {
+  root: string;
+  coverage?: { enabled?: boolean; provider?: string };
+}
+
+/**
+ * The shared Vitest instance, narrowed to what the late-path guard touches. Its
+ * `enableCoverage()` is the programmatic/watch/UI path that turns coverage on
+ * AFTER `configureVitest` and creates the provider lazily — wrapping it lets the
+ * same skew check guard that path.
+ */
+interface VitestInstanceView {
+  config: ResolvedCoverageView;
+  enableCoverage: () => Promise<void>;
+}
+
+/**
+ * The `configureVitest` plugin hook context, narrowed to the fields the guard
+ * reads. Vitest passes more (incl. a per-project `project`), but coverage is a
+ * single, GLOBAL runner concern: Vitest creates ONE provider from the shared
+ * runner config and imports it from the runner context — never per project. So
+ * the guard reads the shared `vitest.config` (its `coverage.{enabled,provider}`
+ * already reflects the `--coverage` CLI flags, unlike a `configResolved` Vite
+ * hook) and resolves the provider from `vitest.config.root` (the workspace/
+ * runner root), which resolves to the exact provider copy Vitest loads. Reading
+ * the per-project `project.config.root` would miss a root-level skew or
+ * false-positive on a leaf-local provider the runner never loads.
+ */
+interface VitestConfigureContext {
+  vitest: VitestInstanceView;
+}
+
+/**
+ * Vitest instances the guard has already handled. The `configureVitest` hook
+ * fires once PER PROJECT but `vitest` is shared and coverage is global, so this
+ * runs the whole guard exactly once per instance (and never leaks, since it is
+ * keyed weakly on identity).
+ */
+const coverageGuardedVitestInstances = new WeakSet<object>();
+
+function vitePlusCoverageVersionGuardPlugin(): PluginOption {
+  return {
+    name: 'vite-plus:coverage-version-guard',
+    // `configureVitest` is a Vitest-specific plugin hook absent from Vite's
+    // `Plugin` type (vite-plus-core is a vite fork, so Vitest's `declare module
+    // 'vite'` augmentation does not reach it). Cast the object so the extra hook
+    // type-checks; Vitest invokes it via `getSortedPluginHooks('configureVitest')`.
+    configureVitest(context: VitestConfigureContext) {
+      const { vitest } = context;
+      // Coverage is global and the provider is imported once from the runner, so
+      // run the guard once per shared instance even though this hook fires per
+      // project.
+      if (coverageGuardedVitestInstances.has(vitest)) {
+        return;
+      }
+      coverageGuardedVitestInstances.add(vitest);
+
+      // STARTUP path: `vitest run --coverage` / config `coverage.enabled` — the
+      // only path `vp test`/CI take. `vitest.config.coverage` is the resolved
+      // runner config; `vitest.config.root` anchors the provider resolution.
+      checkCoverageProviderVersion(vitest.config.coverage, vitest.config.root);
+
+      // LATE path: Vitest's programmatic `enableCoverage()` (node API / watch /
+      // UI toggle) flips coverage on after this hook and creates the provider
+      // lazily, so the startup check above already returned. Wrap it so the same
+      // skew check runs before the provider loads. The wrapper always delegates.
+      const enableCoverage = vitest.enableCoverage.bind(vitest);
+      vitest.enableCoverage = async () => {
+        // `enableCoverage()` sets enabled:true then creates the provider; force
+        // enabled so the check does not early-return, and read the provider from
+        // the live runner config (the late-path source of truth).
+        checkCoverageProviderVersion(
+          { enabled: true, provider: vitest.config.coverage?.provider },
+          vitest.config.root,
+        );
+        return enableCoverage();
+      };
+    },
+  } as PluginOption;
+}
+
+/**
+ * Inject the vitest resolver plugin, the auto-inline matcher plugin, and the
+ * coverage version guard into a single inline project config. Used both for
+ * root configs and for object-shaped entries inside `test.projects`.
+ *
+ * The shapes overlap (both have an optional top-level `plugins` array and
+ * an optional `test.server.deps.inline`), so a shared helper keeps the
+ * wiring consistent.
+ */
+function injectPluginIntoInlineConfig<
+  T extends {
+    plugins?: UserConfig['plugins'];
+    test?: { server?: { deps?: { inline?: unknown } } };
+  },
+>(config: T): T {
+  return {
+    ...config,
+    plugins: [
+      vitePlusVitestResolverPlugin(),
+      vitePlusAutoInlineMatcherPlugin(),
+      vitePlusCoverageVersionGuardPlugin(),
+      ...(config.plugins ?? []),
+    ],
+  } as T;
+}
+
+/**
+ * Walk `config.test?.projects` and inject the vite-plus plugins into each
+ * project entry. Vitest spins up an independent Vite pipeline per project, so
+ * root-level plugins do NOT propagate — without this, files matched by a
+ * project's `include` glob never get the vitest resolver / auto-inline plugins.
+ *
+ * Entry shapes (from `TestProjectConfiguration`):
+ *   - string  (glob path like `'./packages/*'`)  → passed through unchanged.
+ *   - object  (inline config with `test: {...}`) → clone and prepend plugin.
+ *   - function (sync or async)                   → wrap so its result is injected.
+ *   - Promise (resolves to inline config)        → chain `.then(injectPlugin)`.
+ *
+ * String/glob entries cannot be cloned, so they carry no injected plugin. This
+ * only weakens the COVERAGE guard, and only narrowly: coverage is global, and
+ * the migration rewrites every nested config file to vite-plus
+ * `defineConfig`/`defineProject` (which re-inject the guard), so a migrated
+ * workspace still fires it from its resolved projects. The residual gap is a
+ * hand-authored workspace whose string globs resolve to raw `vitest/config`
+ * sub-configs or bare directory projects — there a provider/runner skew falls
+ * back to Vitest's own (softer) warning instead of the guard's hard error.
+ */
+function injectPluginIntoProject(project: TestProjectConfiguration): TestProjectConfiguration {
+  if (typeof project === 'string') {
+    return project;
+  }
+  if (typeof project === 'function') {
+    const wrapped: UserProjectConfigFn = (env: ConfigEnv) => {
+      const result = project(env);
+      if (result instanceof Promise) {
+        return result.then(injectPluginIntoInlineConfig);
+      }
+      return injectPluginIntoInlineConfig(result);
+    };
+    return wrapped;
+  }
+  if (project instanceof Promise) {
+    return project.then(injectPluginIntoInlineConfig);
+  }
+  if (typeof project === 'object' && project !== null) {
+    return injectPluginIntoInlineConfig(project);
+  }
+  return project;
+}
+
+function injectPlugin(config: UserConfig): UserConfig {
+  const injected = injectPluginIntoInlineConfig(config);
+  const projects = injected.test?.projects;
+  if (!projects || projects.length === 0) {
+    return injected;
+  }
+  return {
+    ...injected,
+    test: {
+      ...injected.test,
+      projects: projects.map(injectPluginIntoProject),
+    },
+  };
+}
+
+function injectPluginIntoConfig(config: ViteUserConfigExport): ViteUserConfigExport {
+  if (typeof config === 'function') {
+    return (env: ConfigEnv) => {
+      const result = config(env);
+      if (result instanceof Promise) {
+        return result.then(injectPlugin);
+      }
+      return injectPlugin(result);
+    };
+  }
+  if (config instanceof Promise) {
+    return config.then(injectPlugin);
+  }
+  return injectPlugin(config);
+}
+
 export function defineConfig(config: UserConfig): UserConfig;
 export function defineConfig(config: Promise<UserConfig>): Promise<UserConfig>;
 export function defineConfig(config: ViteUserConfigFnObject): ViteUserConfigFnObject;
@@ -69,7 +679,44 @@ export function defineConfig(config: ViteUserConfigFnPromise): ViteUserConfigFnP
 export function defineConfig(config: ViteUserConfigExport): ViteUserConfigExport;
 
 export function defineConfig(config: ViteUserConfigExport): ViteUserConfigExport {
-  return viteDefineConfig(config);
+  return viteDefineConfig(injectPluginIntoConfig(config));
+}
+
+/**
+ * Inject the vite-plus plugins into a `defineProject` export. A project config
+ * (`UserWorkspaceConfig`) cannot itself nest `test.projects`, so this only
+ * touches the top-level `plugins` array (no project recursion like
+ * [[injectPluginIntoConfig]] does).
+ */
+function injectPluginIntoProjectExport(config: UserProjectConfigExport): UserProjectConfigExport {
+  if (typeof config === 'function') {
+    return ((env: ConfigEnv) => {
+      const result = config(env);
+      return result instanceof Promise
+        ? result.then(injectPluginIntoInlineConfig)
+        : injectPluginIntoInlineConfig(result);
+    }) as UserProjectConfigFn;
+  }
+  if (config instanceof Promise) {
+    return config.then(injectPluginIntoInlineConfig);
+  }
+  return injectPluginIntoInlineConfig(config);
+}
+
+/**
+ * `defineProject` counterpart of [[defineConfig]]. A migrated project config
+ * that uses `defineProject({ test: { browser: ... } })` — e.g. a file named by
+ * a string `test.projects` entry — must still receive the vite-plus resolver /
+ * auto-inline plugins, or a browser project can fail to resolve `vitest` /
+ * `@vitest/*` from its own root under pnpm strict / Yarn PnP. The raw
+ * `vitest/config` helper does not add them.
+ */
+export function defineProject(config: UserWorkspaceConfig): UserWorkspaceConfig;
+export function defineProject(config: Promise<UserWorkspaceConfig>): Promise<UserWorkspaceConfig>;
+export function defineProject(config: UserProjectConfigFn): UserProjectConfigFn;
+export function defineProject(config: UserProjectConfigExport): UserProjectConfigExport;
+export function defineProject(config: UserProjectConfigExport): UserProjectConfigExport {
+  return viteDefineProject(injectPluginIntoProjectExport(config) as never);
 }
 
 const VITE_COMMANDS = new Set(['dev', 'build', 'test', 'preview']);
diff --git a/packages/cli/src/index.cts b/packages/cli/src/index.cts
index 02f4874efb..09b5b2bf2f 100644
--- a/packages/cli/src/index.cts
+++ b/packages/cli/src/index.cts
@@ -1,12 +1,23 @@
 const vite = require('@voidzero-dev/vite-plus-core');
 
-const vitest = require('@voidzero-dev/vite-plus-test/config');
+const {
+  configDefaults,
+  coverageConfigDefaults,
+  defaultBrowserPort,
+  defaultExclude,
+  defaultInclude,
+} = require('vitest/config');
 
-const { defineConfig, lazyPlugins } = require('./define-config');
+const { defineConfig, defineProject, lazyPlugins } = require('./define-config');
 
 module.exports = {
   ...vite,
-  ...vitest,
+  configDefaults,
+  coverageConfigDefaults,
+  defaultBrowserPort,
+  defaultExclude,
+  defaultInclude,
+  defineProject,
   defineConfig,
   lazyPlugins,
 };
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index 1d9d932662..f438526097 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -1,7 +1,29 @@
-import { defineConfig, lazyPlugins } from './define-config.ts';
+import { defineConfig, defineProject, lazyPlugins } from './define-config.ts';
 
 export * from '@voidzero-dev/vite-plus-core';
 
-export * from '@voidzero-dev/vite-plus-test/config';
+export {
+  configDefaults,
+  coverageConfigDefaults,
+  defaultBrowserPort,
+  defaultExclude,
+  defaultInclude,
+} from 'vitest/config';
 
-export { defineConfig, lazyPlugins };
+export type {
+  TestProjectConfiguration,
+  TestProjectInlineConfiguration,
+  TestTagDefinition,
+  TestUserConfig,
+  UserProjectConfigExport,
+  UserProjectConfigFn,
+  UserWorkspaceConfig,
+  ViteUserConfig,
+  ViteUserConfigExport,
+  ViteUserConfigFn,
+  ViteUserConfigFnObject,
+  ViteUserConfigFnPromise,
+  WatcherTriggerPattern,
+} from 'vitest/config';
+
+export { defineConfig, defineProject, lazyPlugins };
diff --git a/packages/cli/src/migration/__tests__/bun-catalog-file-protocol.spec.ts b/packages/cli/src/migration/__tests__/bun-catalog-file-protocol.spec.ts
index e4050a529b..d04dbce46c 100644
--- a/packages/cli/src/migration/__tests__/bun-catalog-file-protocol.spec.ts
+++ b/packages/cli/src/migration/__tests__/bun-catalog-file-protocol.spec.ts
@@ -14,9 +14,7 @@ vi.mock('../../utils/constants.js', async (importOriginal) => {
     VITE_PLUS_VERSION: 'file:/tmp/tgz/vite-plus-0.0.0.tgz',
     VITE_PLUS_OVERRIDE_PACKAGES: {
       vite: 'file:/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz',
-      vitest: 'file:/tmp/tgz/voidzero-dev-vite-plus-test-0.0.0.tgz',
       '@voidzero-dev/vite-plus-core': 'file:/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz',
-      '@voidzero-dev/vite-plus-test': 'file:/tmp/tgz/voidzero-dev-vite-plus-test-0.0.0.tgz',
     },
   };
 });
@@ -78,13 +76,11 @@ describe('rewriteMonorepo bun catalog with file: protocol', () => {
     // overrides should use file: paths directly, not catalog:
     const overrides = pkg.overrides as Record<string, string>;
     expect(overrides.vite).toBe('file:/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz');
-    expect(overrides.vitest).toBe('file:/tmp/tgz/voidzero-dev-vite-plus-test-0.0.0.tgz');
+    expect(overrides.vitest).toBeUndefined();
     expect(overrides['@voidzero-dev/vite-plus-core']).toBe(
       'file:/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz',
     );
-    expect(overrides['@voidzero-dev/vite-plus-test']).toBe(
-      'file:/tmp/tgz/voidzero-dev-vite-plus-test-0.0.0.tgz',
-    );
+    expect(overrides['@voidzero-dev/vite-plus-test']).toBeUndefined();
   });
 
   it('does not write file: paths into named catalogs', () => {
@@ -127,6 +123,64 @@ describe('rewriteMonorepo bun catalog with file: protocol', () => {
     expect(pkg.devDependencies.vite).toBe('file:/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz');
   });
 
+  it('writes bunfig.toml with `peer = false` so vitest peer-dep on vite does not break install', () => {
+    // vitest@4.1.9 declares peer vite^6/^7/^8. With overrides.vite pointing at
+    // file:vite-plus-core@0.0.0 (whose package.json version does not match),
+    // bun aborts the install. pnpm/yarn/npm tolerate this; bun has no equivalent
+    // to pnpm's peerDependencyRules and only respects the `[install] peer = false`
+    // setting in bunfig.toml. The migrator must emit that file or every bun
+    // user hits `error: vite@^6.0.0 || ^7.0.0 || ^8.0.0 failed to resolve`.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'bun-monorepo',
+        workspaces: ['packages/*'],
+        packageManager: 'bun@1.3.11',
+      }),
+    );
+    rewriteMonorepo(makeWorkspaceInfo(tmpDir, PackageManager.bun), true);
+
+    const bunfigPath = path.join(tmpDir, 'bunfig.toml');
+    expect(fs.existsSync(bunfigPath)).toBe(true);
+    expect(fs.readFileSync(bunfigPath, 'utf8')).toMatch(/^\[install\][\s\S]*peer\s*=\s*false/m);
+  });
+
+  it('preserves an existing bunfig.toml `peer` setting (does not overwrite user intent)', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'bun-monorepo',
+        workspaces: ['packages/*'],
+        packageManager: 'bun@1.3.11',
+      }),
+    );
+    fs.writeFileSync(path.join(tmpDir, 'bunfig.toml'), '[install]\npeer = true\n');
+    rewriteMonorepo(makeWorkspaceInfo(tmpDir, PackageManager.bun), true);
+
+    // User's explicit `peer = true` should remain — we don't silently flip it.
+    expect(fs.readFileSync(path.join(tmpDir, 'bunfig.toml'), 'utf8')).toMatch(/peer\s*=\s*true/);
+  });
+
+  it('appends `peer = false` under an existing [install] section without `peer` setting', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'bun-monorepo',
+        workspaces: ['packages/*'],
+        packageManager: 'bun@1.3.11',
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'bunfig.toml'),
+      '[install]\nregistry = "https://registry.npmjs.org/"\n',
+    );
+    rewriteMonorepo(makeWorkspaceInfo(tmpDir, PackageManager.bun), true);
+
+    const bunfig = fs.readFileSync(path.join(tmpDir, 'bunfig.toml'), 'utf8');
+    expect(bunfig).toMatch(/registry\s*=\s*"https:\/\/registry\.npmjs\.org\/"/);
+    expect(bunfig).toMatch(/peer\s*=\s*false/);
+  });
+
   it('does not write file: paths into peer dependencies', () => {
     const pkg = {
       peerDependencies: {
@@ -141,7 +195,7 @@ describe('rewriteMonorepo bun catalog with file: protocol', () => {
     rewritePackageJson(pkg, PackageManager.pnpm, true);
 
     expect(pkg.peerDependencies.vite).toBe('^7.0.0');
-    expect(pkg.peerDependencies.vitest).toBe('*');
+    expect(pkg.peerDependencies.vitest).toBe('catalog:test');
     expect(pkg.optionalDependencies.vite).toBe(
       'file:/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz',
     );
diff --git a/packages/cli/src/migration/__tests__/install-failure-guard.spec.ts b/packages/cli/src/migration/__tests__/install-failure-guard.spec.ts
new file mode 100644
index 0000000000..6304c1f2fa
--- /dev/null
+++ b/packages/cli/src/migration/__tests__/install-failure-guard.spec.ts
@@ -0,0 +1,77 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import { describe, expect, it } from 'vitest';
+
+const here = path.dirname(fileURLToPath(import.meta.url));
+const binPath = path.resolve(here, '..', 'bin.ts');
+
+/**
+ * `runViteInstall` returns `{ status: 'failed', exitCode }` (it does not throw)
+ * when the install process exits non-zero. Earlier versions of `migration/bin.ts`
+ * only summed `installSummary.durationMs` and reported success regardless of
+ * `status`, which left the project's `node_modules` desynced from a freshly
+ * mutated `package.json` (network/auth failures, registry blips, pnpm lockfile
+ * conflicts after override resolution) while still printing
+ * "Dependencies installed" to the user.
+ *
+ * Two install sites need this handling:
+ *   1. The full migration path (`executeMigrationPlan`'s final reinstall, run
+ *      AFTER manifest/source rewrites land).
+ *   2. The early-return path (`main` when `vite-plus` is already a dep but a
+ *      stale-wrapper repair or ESLint/Prettier migration touches package.json).
+ *
+ * Both paths must funnel install results through `handleInstallResult` so that
+ * failures warn the user, append to `report.warnings`, and flip
+ * `process.exitCode`. This is a guard test — if a future refactor drops the
+ * helper or stops calling it from either path, this fails loudly so reviewers
+ * can re-evaluate the install-failure UX before it ships.
+ */
+describe('migration install failure handling', () => {
+  const binSource = fs.readFileSync(binPath, 'utf8');
+
+  describe('handleInstallResult helper', () => {
+    it('defines a `handleInstallResult` function in bin.ts', () => {
+      expect(binSource).toMatch(/function handleInstallResult\s*\(/);
+    });
+
+    it('branches on `installSummary.status === "installed"` and credits duration', () => {
+      expect(binSource).toMatch(/installSummary\.status === 'installed'/);
+      expect(binSource).toMatch(/return installSummary\.durationMs/);
+    });
+
+    it('branches on `installSummary.status === "failed"` and warns + flips exitCode', () => {
+      expect(binSource).toMatch(/installSummary\.status === 'failed'/);
+      expect(binSource).toMatch(/warnMsg\(/);
+      expect(binSource).toMatch(/report\.warnings\.push\(/);
+      expect(binSource).toMatch(/process\.exitCode\s*=/);
+    });
+  });
+
+  describe('full migration path (executeMigrationPlan)', () => {
+    it('reconciles `finalInstallSummary` through `handleInstallResult`', () => {
+      // The full-path final reinstall must run through the helper so a failed
+      // install after manifest/source rewrites does not silently report a
+      // desynced project as a successful migration.
+      expect(binSource).toMatch(/finalInstallSummary[\s\S]{0,1500}handleInstallResult\(/);
+    });
+
+    it('does NOT add `finalInstallSummary.durationMs` directly to the return value', () => {
+      // The buggy round-7 version returned
+      //   `initialInstallSummary.durationMs + finalInstallSummary.durationMs`
+      // unconditionally. The fix routes `finalInstallSummary` through the
+      // helper which returns 0 on failure. Catch any regression that re-adds
+      // the raw `.durationMs` access.
+      expect(binSource).not.toMatch(/finalInstallSummary\.durationMs/);
+    });
+  });
+
+  describe('early-return path (main, hasVitePlusDependency branch)', () => {
+    it('reconciles `installSummary` through `handleInstallResult`', () => {
+      expect(binSource).toMatch(
+        /const installSummary = await runViteInstall\([\s\S]{0,1500}handleInstallResult\(/,
+      );
+    });
+  });
+});
diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts
index 3963fc91bc..986291a1fb 100644
--- a/packages/cli/src/migration/__tests__/migrator.spec.ts
+++ b/packages/cli/src/migration/__tests__/migrator.spec.ts
@@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
 import { parse as parseYaml } from 'yaml';
 
 import { PackageManager } from '../../types/index.js';
+import { VITE_PLUS_OVERRIDE_PACKAGES, VITEST_VERSION } from '../../utils/constants.js';
 import { createMigrationReport } from '../report.js';
 
 // Mock VITE_PLUS_VERSION to a stable value for snapshot tests.
@@ -213,7 +214,12 @@ describe('rewritePackageJson', () => {
     }
   });
 
-  it('does not auto-add vitest on a pure normalize pass (only on actual vite/vitest/REMOVE migrations)', async () => {
+  it('adds a direct vitest for a vitest-adjacent dep even when vite-plus is already present', async () => {
+    // `vitest-browser-svelte` declares a NON-optional `vitest` peer. Even though
+    // `vite-plus` is already here (bundling vitest transitively), a strict pnpm /
+    // Yarn PnP layout won't expose that transitive vitest to the package root, so
+    // the peer can't resolve. The migrator must pin a direct `vitest` regardless of
+    // whether `vite-plus` is already present.
     const pkg = {
       devDependencies: {
         'vite-plus': '^0.1.20',
@@ -223,6 +229,23 @@ describe('rewritePackageJson', () => {
 
     rewritePackageJson(pkg, PackageManager.pnpm, true);
 
+    expect(pkg.devDependencies['vite-plus']).toBe('catalog:');
+    expect((pkg.devDependencies as Record<string, string>).vitest).toBe('catalog:');
+  });
+
+  it('does not auto-add vitest on a genuine normalize pass (no browser mode, no vitest-adjacent dep)', async () => {
+    // vite-plus present, nothing vitest-adjacent, no browser mode -> nothing to
+    // pin. needDirectVitest stays false and the package is left untouched beyond
+    // the vite-plus spec normalization.
+    const pkg = {
+      devDependencies: {
+        'vite-plus': '^0.1.20',
+        '@scope/some-plugin': '^1.0.0',
+      },
+    };
+
+    rewritePackageJson(pkg, PackageManager.pnpm, true);
+
     expect(pkg.devDependencies['vite-plus']).toBe('catalog:');
     expect((pkg.devDependencies as Record<string, string>).vitest).toBeUndefined();
   });
@@ -240,6 +263,8 @@ describe('rewritePackageJson', () => {
     rewritePackageJson(pkg, PackageManager.yarn, true);
 
     expect(pkg.devDependencies.vite).toBe('catalog:');
+    // vitest is a managed override key — non-catalog specs are rewritten to
+    // `catalog:` so the override is resolved through the catalog.
     expect(pkg.dependencies.vitest).toBe('catalog:');
     expect((pkg.devDependencies as Record<string, string>)['vite-plus']).toBe('catalog:');
   });
@@ -259,7 +284,10 @@ describe('rewritePackageJson', () => {
 
     expect(pkg.devDependencies.vite).toBe('catalog:');
     expect(pkg.optionalDependencies.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
-    expect(pkg.optionalDependencies.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    // vitest is now a managed override key — yarn optional deps receive the
+    // literal override version so the resolution doesn't depend on catalog
+    // lookup at the optionalDependency site.
+    expect(pkg.optionalDependencies.vitest).toBe('4.1.9');
     expect((pkg.devDependencies as Record<string, string>)['vite-plus']).toBe('catalog:');
   });
 
@@ -342,7 +370,12 @@ describe('rewritePackageJson', () => {
     ).toBe('catalog:');
   });
 
-  it('should preserve playwright when removing @vitest/browser-playwright', async () => {
+  it('keeps and normalizes @vitest/browser-playwright and ensures the playwright peer', async () => {
+    // Playwright is opt-in: vite-plus no longer bundles the provider at runtime
+    // (its `playwright` peer is non-optional), so the migration KEEPS the user's
+    // declared `@vitest/browser-playwright` (version-normalized to the bundled
+    // vitest version) and ensures its runtime framework peer `playwright`.
+    // `@vitest/browser` stays in REMOVE_PACKAGES and is still stripped.
     const pkg = {
       devDependencies: {
         '@vitest/browser': '^4.0.0',
@@ -351,12 +384,83 @@ describe('rewritePackageJson', () => {
       },
     };
     rewritePackageJson(pkg, PackageManager.pnpm);
+    // Standalone (supportCatalog=false) → concrete pinned spec.
+    expect(pkg.devDependencies).toHaveProperty('@vitest/browser-playwright', VITEST_VERSION);
     expect(pkg.devDependencies).toHaveProperty('playwright', '*');
     expect(pkg.devDependencies).not.toHaveProperty('@vitest/browser');
-    expect(pkg.devDependencies).not.toHaveProperty('@vitest/browser-playwright');
   });
 
-  it('should preserve webdriverio when removing @vitest/browser-webdriverio', async () => {
+  it('injects a direct vite devDependency for an npm project that uses an opt-in browser provider', async () => {
+    // npm's flat node_modules cannot dedupe the provider's own
+    // `@vitest/browser → @vitest/mocker` subtree against the one vite-plus
+    // bundles, leaving several nested `@vitest/mocker` copies. The `vite`
+    // override only lands inside the `vitest` subtree, so the nested mockers
+    // can't resolve their (optional) `vite` peer and `@vitest/mocker/dist/node.js`
+    // throws `ERR_MODULE_NOT_FOUND: Cannot find package 'vite'` at config load.
+    // A direct `vite` devDep (= the override target) forces npm to hoist a
+    // single top-level `node_modules/vite` every nested mocker resolves.
+    const pkg = {
+      devDependencies: {
+        '@vitest/browser-playwright': '^4.0.0',
+        playwright: '^1.60.0',
+        vitest: '^4.0.0',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.npm);
+    expect(pkg.devDependencies).toHaveProperty('vite', VITE_PLUS_OVERRIDE_PACKAGES.vite);
+  });
+
+  it('does not inject a direct vite devDependency for npm projects without a browser provider', async () => {
+    // Node-mode projects dedupe cleanly (a single hoisted `@vitest/mocker`
+    // next to a top-level `vite`), so the migration must not add a direct
+    // `vite` dep — leaving non-browser consumers untouched.
+    const pkg = {
+      devDependencies: {
+        vitest: '^4.0.0',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.npm);
+    expect(pkg.devDependencies).not.toHaveProperty('vite');
+  });
+
+  it('does not inject a direct vite devDependency for non-npm provider projects', async () => {
+    // pnpm/yarn use symlink/PnP layouts that already expose the `vite` override
+    // to the provider subtree, so the npm-only direct-`vite` workaround must not
+    // fire for them.
+    const pkg = {
+      devDependencies: {
+        '@vitest/browser-playwright': '^4.0.0',
+        playwright: '^1.60.0',
+        vitest: '^4.0.0',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.pnpm);
+    expect(pkg.devDependencies).not.toHaveProperty('vite');
+  });
+
+  it('normalizes a pre-existing direct vite dep to the override target for an npm provider project', async () => {
+    // A pre-existing direct `vite` is already normalized to the override target
+    // by the `VITE_PLUS_OVERRIDE_PACKAGES` loop (vite-plus replaces `vite` with
+    // its bundled core). The provider workaround must not duplicate or clobber
+    // it — the single direct `vite` stays pointed at the override target.
+    const pkg = {
+      devDependencies: {
+        '@vitest/browser-playwright': '^4.0.0',
+        playwright: '^1.60.0',
+        vite: '^7.0.0',
+        vitest: '^4.0.0',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.npm);
+    expect(pkg.devDependencies).toHaveProperty('vite', VITE_PLUS_OVERRIDE_PACKAGES.vite);
+  });
+
+  it('keeps and normalizes @vitest/browser-webdriverio and ensures the webdriverio peer', async () => {
+    // Webdriverio is opt-in: vite-plus no longer bundles the provider, so the
+    // migration KEEPS the user's declared `@vitest/browser-webdriverio`
+    // (version-normalized to the bundled vitest version) and ensures its
+    // runtime framework peer `webdriverio`. `@vitest/browser` stays in
+    // REMOVE_PACKAGES and is still stripped.
     const pkg = {
       devDependencies: {
         '@vitest/browser': '^4.0.0',
@@ -365,8 +469,10 @@ describe('rewritePackageJson', () => {
       },
     };
     rewritePackageJson(pkg, PackageManager.pnpm);
+    // Standalone (supportCatalog=false) → concrete pinned spec.
+    expect(pkg.devDependencies).toHaveProperty('@vitest/browser-webdriverio', VITEST_VERSION);
     expect(pkg.devDependencies).toHaveProperty('webdriverio', '*');
-    expect(pkg.devDependencies).not.toHaveProperty('@vitest/browser-webdriverio');
+    expect(pkg.devDependencies).not.toHaveProperty('@vitest/browser');
   });
 
   it('should not overwrite playwright if already in devDependencies', async () => {
@@ -395,6 +501,80 @@ describe('rewritePackageJson', () => {
     expect(pkg.dependencies).toHaveProperty('playwright', '^1.40.0');
     expect(pkg.devDependencies).not.toHaveProperty('playwright');
   });
+
+  it('adds a direct vitest devDependency when the package uses browser mode', async () => {
+    // A package that drives vitest browser mode but has no direct vitest dep
+    // (e.g. it only imports `vite-plus/test/browser-playwright`). `@vitest/browser`
+    // needs `vitest` resolvable from the package root, so the migration must
+    // pin it as a direct devDependency.
+    const pkg = {
+      devDependencies: {
+        playwright: '^1.58.0',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.pnpm, true, undefined, undefined, true);
+    expect(pkg.devDependencies).toHaveProperty('vitest', 'catalog:');
+    expect(pkg.devDependencies).toHaveProperty('vite-plus', 'catalog:');
+  });
+
+  it('uses a concrete vitest version for browser mode in non-catalog package managers', async () => {
+    const pkg = {
+      devDependencies: {
+        playwright: '^1.58.0',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.npm, false, undefined, undefined, true);
+    expect((pkg as { devDependencies?: Record<string, string> }).devDependencies?.vitest).toBe(
+      VITEST_VERSION,
+    );
+  });
+
+  it('does not overwrite an existing direct vitest dep in browser mode', async () => {
+    const pkg = {
+      devDependencies: {
+        vitest: '^4.0.0',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.pnpm, true, undefined, undefined, true);
+    // existing direct dep is normalized through the override path, not replaced
+    expect(pkg.devDependencies.vitest).toBe('catalog:');
+  });
+
+  it('does not add vitest when browser mode is not detected', async () => {
+    const pkg = {
+      devDependencies: {
+        vite: '^7.0.0',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.pnpm, true, undefined, undefined, false);
+    expect(pkg.devDependencies).not.toHaveProperty('vitest');
+  });
+
+  it('adds a direct vitest dep when a browser provider is declared but not source-imported', async () => {
+    // Config-only browser mode: vitest is enabled via `vite.config.ts`
+    // (e.g. `test.browser.provider: 'playwright'`) and the provider package is
+    // declared in devDependencies, but no source file `import`s it. The
+    // source-scan signal (`usesVitestBrowserMode`) is therefore false; the
+    // dep declaration in the original package.json must still drive the
+    // direct-`vitest` injection so the browser optimizer can resolve `vitest`
+    // from the package root under pnpm strict / Yarn PnP.
+    const pkg = {
+      devDependencies: {
+        '@vitest/browser': '^4.1.7',
+        '@vitest/browser-playwright': '^4.1.7',
+      },
+    };
+    rewritePackageJson(pkg, PackageManager.pnpm, true, undefined, undefined, false);
+    expect(pkg.devDependencies).toHaveProperty('vitest', 'catalog:');
+    expect(pkg.devDependencies).toHaveProperty('vite-plus', 'catalog:');
+    // The base `@vitest/browser` is still stripped (bundled by vite-plus).
+    expect(pkg.devDependencies).not.toHaveProperty('@vitest/browser');
+    // Playwright is opt-in: vite-plus keeps it in the user's deps, normalized to
+    // the bundled vitest version, so the rewritten import resolves.
+    expect(pkg.devDependencies).toHaveProperty('@vitest/browser-playwright', VITEST_VERSION);
+    // The provider's runtime peer dep is preserved.
+    expect(pkg.devDependencies).toHaveProperty('playwright', '*');
+  });
 });
 
 describe('rewriteEslintPackageJson', () => {
@@ -1047,11 +1227,11 @@ describe('ensureVitePlusBootstrap', () => {
       devEngines: { packageManager: { name: string } };
     };
     expect(pkg.overrides.vite).toContain('@voidzero-dev/vite-plus-core');
-    expect(pkg.overrides.vitest).toContain('@voidzero-dev/vite-plus-test');
+    expect(pkg.overrides.vitest).toBe('4.1.9');
     expect(pkg.devEngines.packageManager.name).toBe(PackageManager.npm);
   });
 
-  it('does not rewrite semantic npm overrides that already point at Vite+ packages', () => {
+  it('rewrites the stale vitest wrapper override without pinning the @vitest/* family for npm projects', () => {
     fs.writeFileSync(
       path.join(tmpDir, 'package.json'),
       JSON.stringify({
@@ -1066,13 +1246,23 @@ describe('ensureVitePlusBootstrap', () => {
         },
       }),
     );
-    const before = fs.readFileSync(path.join(tmpDir, 'package.json'), 'utf-8');
-
-    expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.npm)).toBe(false);
+    expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.npm)).toBe(true);
     const result = ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.npm));
 
-    expect(result.changed).toBe(false);
-    expect(fs.readFileSync(path.join(tmpDir, 'package.json'), 'utf-8')).toBe(before);
+    // The `vite` alias still points at the live `@voidzero-dev/vite-plus-core`
+    // package, so it satisfies the migration and is left untouched. The `vitest`
+    // alias points at the DELETED `@voidzero-dev/vite-plus-test` wrapper, so it is
+    // rewritten to the bundled vitest version. The `@vitest/*` family is NOT pinned:
+    // it resolves transitively from `vitest`'s own exact deps.
+    expect(result.changed).toBe(true);
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides: Record<string, string>;
+    };
+    expect(pkg.overrides.vite).toBe('npm:@voidzero-dev/vite-plus-core@0.1.0');
+    expect(pkg.overrides.vitest).toBe('4.1.9');
+    expect(pkg.overrides['@vitest/expect']).toBeUndefined();
+    expect(pkg.overrides['@vitest/coverage-v8']).toBeUndefined();
+    expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.npm)).toBe(false);
   });
 
   it('rewrites direct npm Vite dependencies before adding overrides', () => {
@@ -1102,7 +1292,7 @@ describe('ensureVitePlusBootstrap', () => {
       dependencies: Record<string, string>;
     };
     expect(pkg.devDependencies.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
-    expect(pkg.dependencies.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    expect(pkg.dependencies.vitest).toBe('4.1.9');
   });
 
   it('normalizes catalog vite-plus pins for npm projects', () => {
@@ -1342,7 +1532,7 @@ describe('ensureVitePlusBootstrap', () => {
     };
     expect(yarnrc.nodeLinker).toBe('node-modules');
     expect(yarnrc.catalog.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
-    expect(yarnrc.catalog.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    expect(yarnrc.catalog.vitest).toBe('4.1.9');
     expect(yarnrc.catalog['vite-plus']).toBe('latest');
   });
 
@@ -1380,7 +1570,104 @@ describe('ensureVitePlusBootstrap', () => {
       peerDependencyRules: { allowAny: string[]; allowedVersions: Record<string, string> };
     };
     expect(workspace.peerDependencyRules.allowAny).toEqual(['vite', 'vitest']);
-    expect(workspace.peerDependencyRules.allowedVersions).toEqual({ vite: '*', vitest: '*' });
+    expect(workspace.peerDependencyRules.allowedVersions).toEqual({
+      vite: '*',
+      vitest: '*',
+    });
+  });
+
+  it('exempts the vitest family from a pnpm minimumReleaseAge gate, preserving existing entries', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { 'vite-plus': 'catalog:' },
+        devEngines: {
+          packageManager: { name: 'pnpm', version: '10.33.0', onFail: 'download' },
+        },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      [
+        'catalog:',
+        '  vite: npm:@voidzero-dev/vite-plus-core@latest',
+        '  vitest: npm:@voidzero-dev/vite-plus-test@latest',
+        'minimumReleaseAge: 1440',
+        'minimumReleaseAgeExclude:',
+        '  - my-internal-pkg',
+        '',
+      ].join('\n'),
+    );
+
+    ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.pnpm));
+
+    const workspace = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      minimumReleaseAgeExclude: string[];
+    };
+    // Vite+ pins a possibly-fresh vitest; its family must bypass the gate.
+    expect(workspace.minimumReleaseAgeExclude).toContain('vitest');
+    expect(workspace.minimumReleaseAgeExclude).toContain('@vitest/*');
+    // The user's own entry and the vite-plus/ox families are preserved.
+    expect(workspace.minimumReleaseAgeExclude).toContain('my-internal-pkg');
+    expect(workspace.minimumReleaseAgeExclude).toContain('vite-plus');
+  });
+
+  it('does not add minimumReleaseAgeExclude when the pnpm age gate is absent', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { 'vite-plus': 'catalog:' },
+        devEngines: {
+          packageManager: { name: 'pnpm', version: '10.33.0', onFail: 'download' },
+        },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      [
+        'catalog:',
+        '  vite: npm:@voidzero-dev/vite-plus-core@latest',
+        '  vitest: npm:@voidzero-dev/vite-plus-test@latest',
+        '',
+      ].join('\n'),
+    );
+
+    ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.pnpm));
+
+    const workspace = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      minimumReleaseAgeExclude?: string[];
+    };
+    // No gate present → we never introduce the exclude list.
+    expect(workspace.minimumReleaseAgeExclude).toBeUndefined();
+  });
+
+  it('merges the vitest family into an existing yarn npmPreapprovedPackages list', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { 'vite-plus': 'latest' },
+        devEngines: {
+          packageManager: { name: 'yarn', version: '4.0.0', onFail: 'download' },
+        },
+      }),
+    );
+    // A project that already preapproves private packages must keep them.
+    fs.writeFileSync(
+      path.join(tmpDir, '.yarnrc.yml'),
+      ['npmPreapprovedPackages:', '  - "@my-org/*"', ''].join('\n'),
+    );
+
+    ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.yarn));
+
+    const yarnrc = readYamlObject(path.join(tmpDir, '.yarnrc.yml')) as {
+      npmPreapprovedPackages: string[];
+    };
+    expect(yarnrc.npmPreapprovedPackages).toContain('@my-org/*');
+    expect(yarnrc.npmPreapprovedPackages).toContain('vitest');
+    expect(yarnrc.npmPreapprovedPackages).toContain('@vitest/*');
   });
 
   it('preserves package.json workspace patterns when creating pnpm-workspace.yaml', () => {
@@ -1409,13 +1696,44 @@ describe('ensureVitePlusBootstrap', () => {
 
 describe('rewriteStandaloneProject pnpm workspace yaml', () => {
   let tmpDir: string;
+  const savedEnv: Record<string, string | undefined> = {};
+  // Env keys the Yarn-hoisting resolver consults at HIGHEST precedence. Clear them in
+  // setup so an ambient `YARN_NODE_LINKER=pnp` (etc.) in the runner's environment can't
+  // override the fixture `.yarnrc.yml` values and make these tests non-hermetic; the
+  // env-precedence tests set them explicitly. `HOME`/`USERPROFILE` both matter because
+  // `os.homedir()` reads `HOME` on POSIX but `USERPROFILE` on Windows — set both so the
+  // home-rc lookup is redirected on every platform.
+  const ISOLATED_ENV = [
+    'HOME',
+    'USERPROFILE',
+    'YARN_NODE_LINKER',
+    'YARN_NM_HOISTING_LIMITS',
+  ] as const;
 
   beforeEach(() => {
     tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vp-test-pnpm-'));
+    for (const key of ISOLATED_ENV) {
+      savedEnv[key] = process.env[key];
+      delete process.env[key];
+    }
+    // Point Yarn's home `.yarnrc.yml` (the lowest-precedence config source the resolver
+    // consults) at a clean, empty dir so these tests can't read a contributor's real
+    // ~/.yarnrc.yml. Tests that need a home rc set the home env vars themselves.
+    const cleanHome = path.join(tmpDir, '.home');
+    fs.mkdirSync(cleanHome, { recursive: true });
+    process.env.HOME = cleanHome;
+    process.env.USERPROFILE = cleanHome;
   });
 
   afterEach(() => {
     fs.rmSync(tmpDir, { recursive: true, force: true });
+    for (const key of ISOLATED_ENV) {
+      if (savedEnv[key] === undefined) {
+        delete process.env[key];
+      } else {
+        process.env[key] = savedEnv[key];
+      }
+    }
   });
 
   it('creates pnpm-workspace.yaml when no existing pnpm config in package.json', () => {
@@ -1466,7 +1784,9 @@ describe('rewriteStandaloneProject pnpm workspace yaml', () => {
     const overrides = pnpm.overrides as Record<string, string>;
     expect(overrides['some-pkg']).toBe('1.0.0');
     expect(overrides.vite).toBeDefined();
-    expect(overrides.vitest).toBeDefined();
+    // vitest is pinned via overrides so downstream projects resolve a single
+    // vitest copy (the one vp-cli ships).
+    expect(overrides.vitest).toBe('4.1.9');
 
     // peerDependencyRules should be present
     expect(pnpm.peerDependencyRules).toBeDefined();
@@ -1496,8 +1816,9 @@ describe('rewriteStandaloneProject pnpm workspace yaml', () => {
     const pkg = readJson(path.join(tmpDir, 'package.json'));
     const pnpm = pkg.pnpm as Record<string, unknown>;
     const rules = pnpm.peerDependencyRules as Record<string, unknown>;
-    // Custom entries preserved, Vite entries merged
-    expect(rules.allowAny).toEqual(expect.arrayContaining(['react', 'vite', 'vitest']));
+    // Custom entries preserved, Vite entries merged (vitest is no longer
+    // injected as it's not a managed override key anymore).
+    expect(rules.allowAny).toEqual(expect.arrayContaining(['react', 'vite']));
     // ignoreMissing preserved
     expect(rules.ignoreMissing).toEqual(['@types/node']);
   });
@@ -1511,6 +1832,8 @@ describe('rewriteStandaloneProject pnpm workspace yaml', () => {
 
     const yaml = readYaml(path.join(tmpDir, 'pnpm-workspace.yaml'));
     expect(yaml).toContain("vite: 'catalog:'");
+    // vitest is now a managed override key — it resolves through the catalog
+    // like vite does.
     expect(yaml).toContain("vitest: 'catalog:'");
   });
 
@@ -1554,12 +1877,16 @@ describe('rewriteStandaloneProject pnpm workspace yaml', () => {
       catalogs: Record<string, Record<string, string>>;
     };
     expect(yaml.overrides.vite).toBe('catalog:vite7');
+    // vitest is now a managed override key — it is added to overrides as a
+    // `catalog:` reference, and its catalog entry is rewritten to the pinned
+    // vitest version vp-cli ships.
     expect(yaml.overrides.vitest).toBe('catalog:');
-    expect(yaml.catalog.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    expect(yaml.catalog.vitest).toBe('4.1.9');
     expect(yaml.catalogs.vite7.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
     expect(yaml.catalogs.vite7.react).toBe('^18.0.0');
     expect(yaml.catalogs.vite7['vite-plus']).toBe('latest');
-    expect(yaml.catalogs.test.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    // Named catalog vitest entries are also pinned to the managed override version.
+    expect(yaml.catalogs.test.vitest).toBe('4.1.9');
     expect(yaml.catalogs.test.tsdown).toBeUndefined();
     expect(yaml.catalogs.test['vite-plus']).toBeUndefined();
 
@@ -1570,114 +1897,2298 @@ describe('rewriteStandaloneProject pnpm workspace yaml', () => {
     expect(pkg.devDependencies.vite).toBe('catalog:vite7');
     expect(pkg.devDependencies['vite-plus']).toBe('catalog:');
     expect(pkg.peerDependencies.vite).toBe('^7.0.0');
+    // vitest peer `catalog:` is resolved against the pre-rewrite catalog
+    // (which still holds the user's `^4.0.0`); only the catalog file itself
+    // is later rewritten to the pinned vp-cli version. The peer range stays
+    // as the user wrote it.
     expect(pkg.peerDependencies.vitest).toBe('^4.0.0');
     expect(pkg.peerDependencies).not.toHaveProperty('tsdown');
   });
 
-  it('preserves named pnpm overrides when moving root overrides to pnpm-workspace.yaml', () => {
+  it('drops only global/vite-plus-parent selector-shaped REMOVE_PACKAGES overrides from package.json pnpm.overrides', () => {
+    // Project keeps its pnpm config in package.json (`pkg.pnpm.overrides`).
+    // A selector-shaped provider key is stripped only when it would re-pin
+    // vite-plus's OWN provider dep — a versioned global pin or a `vite-plus`
+    // parent. A provider selector scoped under a SPECIFIC non-vite-plus parent
+    // (`some-app>@vitest/browser-playwright`) only constrains that parent's
+    // subtree and is preserved.
     fs.writeFileSync(
       path.join(tmpDir, 'package.json'),
       JSON.stringify({
-        name: 'pnpm-monorepo',
-        workspaces: ['packages/*'],
-        devDependencies: { vite: 'catalog:vite7' },
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
         pnpm: {
           overrides: {
-            vite: 'catalog:vite7',
-            react: '^18.0.0',
+            'vite-plus>@vitest/browser-playwright': '^4.0.0',
+            'some-app>@vitest/browser-playwright': '^4.0.0',
+            'a>vite-plus>@vitest/browser-playwright': '^4.0.0',
+            '@vitest/browser-playwright@4': '4.1.7',
+            // `@vitest/browser-preview` stays in REMOVE_PACKAGES, so it remains a
+            // vite-plus-OWNED provider ancestor: an un-anchored chain through it
+            // still constrains vite-plus's own `@vitest/browser` dep — dropped.
+            '@vitest/browser-preview>@vitest/browser': '4.0.0',
+            'vite-plus>@vitest/browser-preview>@vitest/browser': '4.0.0',
+            'some-app>@vitest/browser-preview>@vitest/browser': '4.0.0',
+            // Playwright is now opt-in (NOT owned by vite-plus), so an un-anchored
+            // chain PARENTED by playwright constrains the user's own provider
+            // subtree, not vite-plus's — PRESERVED.
+            '@vitest/browser-playwright>@vitest/browser': '4.0.0',
+            'vite-plus>@vitest/browser-playwright>@vitest/browser': '4.0.0',
+            'other>foo': '1.0.0',
           },
         },
       }),
     );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      pnpm?: { overrides?: Record<string, string> };
+    };
+    const overrides = pkg.pnpm?.overrides ?? {};
+    // Playwright-as-TARGET: vite-plus parent and versioned global pin reach
+    // vite-plus's own (now direct-dep) provider — dropped.
+    expect(overrides).not.toHaveProperty('vite-plus>@vitest/browser-playwright');
+    expect(overrides).not.toHaveProperty('@vitest/browser-playwright@4');
+    // An OWNED-provider ancestor (`@vitest/browser-preview`) still constrains
+    // vite-plus's provider subtree (selectors are not root-anchored), with or
+    // without an explicit vite-plus prefix — dropped.
+    expect(overrides).not.toHaveProperty('@vitest/browser-preview>@vitest/browser');
+    expect(overrides).not.toHaveProperty('vite-plus>@vitest/browser-preview>@vitest/browser');
+    // Provider-as-TARGET selector scoped under a SPECIFIC non-vite-plus parent —
+    // PRESERVED.
+    expect(overrides['some-app>@vitest/browser-playwright']).toBe('^4.0.0');
+    expect(overrides['some-app>@vitest/browser-preview>@vitest/browser']).toBe('4.0.0');
+    // Playwright is opt-in (not a vite-plus-owned ancestor), so a chain PARENTED
+    // by it constrains the user's own subtree — PRESERVED even with a vite-plus
+    // prefix above it.
+    expect(overrides['@vitest/browser-playwright>@vitest/browser']).toBe('4.0.0');
+    expect(overrides['vite-plus>@vitest/browser-playwright>@vitest/browser']).toBe('4.0.0');
+    // A chain with an outer non-vite-plus ancestor (`a>vite-plus>…`) requires
+    // vite-plus to sit UNDER `a`, so it never matches the root vite-plus edge —
+    // PRESERVED.
+    expect(overrides['a>vite-plus>@vitest/browser-playwright']).toBe('^4.0.0');
+    // Unrelated selector keys must survive.
+    expect(overrides['other>foo']).toBe('1.0.0');
+  });
+
+  it('drops a vite-plus-scoped provider pin and prunes the emptied vite-plus parent', () => {
+    // A provider pin nested under a `vite-plus` parent forces vite-plus's own
+    // (now direct-dep) provider, so it must be dropped. Removing the sole pin
+    // empties the `vite-plus` parent, which is then pruned.
     fs.writeFileSync(
-      path.join(tmpDir, 'pnpm-workspace.yaml'),
-      ['packages:', '  - packages/*', 'catalogs:', '  vite7:', '    vite: ^7.0.0', ''].join('\n'),
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          'vite-plus': { '@vitest/browser-playwright': '4.0.0' },
+          // An OWNED-provider parent (`@vitest/browser-preview`, still bundled by
+          // vite-plus) reaches vite-plus's provider subtree even without an
+          // explicit vite-plus level — its child pin is dropped and the emptied
+          // parent pruned with it.
+          '@vitest/browser-preview': { '@vitest/browser': '4.0.0' },
+        },
+      }),
     );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
 
-    rewriteMonorepo(makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true);
-
-    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
-      overrides: Record<string, string>;
-      catalogs: Record<string, Record<string, string>>;
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, unknown>;
     };
-    expect(yaml.overrides.vite).toBe('catalog:vite7');
-    expect(yaml.overrides.vitest).toBe('catalog:');
-    expect(yaml.overrides.react).toBe('^18.0.0');
-    expect(yaml.catalogs.vite7.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
+    const overrides = pkg.overrides ?? {};
+    // `vite-plus` parent (with playwright-as-target child) is dropped and pruned.
+    expect(overrides).not.toHaveProperty('vite-plus');
+    // Owned-provider parent emptied by dropping its child pin is pruned too.
+    expect(overrides).not.toHaveProperty('@vitest/browser-preview');
+  });
+
+  it('preserves a provider override scoped under an unrelated parent', () => {
+    // npm/bun nested overrides are SCOPED: a provider pin under `some-pkg`
+    // forces the provider only within some-pkg's subtree, NOT vite-plus's own
+    // provider dep. Deleting it would be silent loss of the user's unrelated
+    // override, so it (and its parent) must survive untouched.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          'some-pkg': { '@vitest/browser-playwright': '4.0.0' },
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
 
     const pkg = readJson(path.join(tmpDir, 'package.json')) as {
-      pnpm?: unknown;
+      overrides?: Record<string, Record<string, string>>;
     };
-    expect(pkg.pnpm).toBeUndefined();
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).toHaveProperty('some-pkg');
+    expect(overrides['some-pkg']['@vitest/browser-playwright']).toBe('4.0.0');
   });
 
-  it('preserves default pnpm catalog overrides over stale workspace named overrides', () => {
+  it('leaves an already-declared coverage provider untouched (no pin, no override)', () => {
+    // Coverage providers are vitest PEER deps the project installs and versions
+    // ITSELF. vite-plus never pins or overrides them: the user owns the provider
+    // version. (The runtime guard in define-config.ts only fail-fasts on a skew
+    // at `vp test --coverage` time; it does not rewrite the project's deps.)
     fs.writeFileSync(
       path.join(tmpDir, 'package.json'),
       JSON.stringify({
-        name: 'pnpm-monorepo',
-        workspaces: ['packages/*'],
-        devDependencies: { vite: 'catalog:' },
-        pnpm: {
-          overrides: {
-            vite: 'catalog:',
-          },
+        name: 'test',
+        devDependencies: {
+          vite: '^7.0.0',
+          vitest: '^4.0.0',
+          '@vitest/coverage-v8': '^4.0.0',
+          '@vitest/coverage-istanbul': '^4.0.0',
         },
       }),
     );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      devDependencies: Record<string, string>;
+      overrides?: Record<string, unknown>;
+    };
+    // Provider versions are preserved exactly as the user declared them.
+    expect(pkg.devDependencies['@vitest/coverage-v8']).toBe('^4.0.0');
+    expect(pkg.devDependencies['@vitest/coverage-istanbul']).toBe('^4.0.0');
+    // vitest itself is still pinned to the bundled version.
+    expect(pkg.devDependencies.vitest).toBe(VITEST_VERSION);
+    // …and coverage is never written into the override sink.
+    const overrides = pkg.overrides ?? {};
+    expect(overrides['@vitest/coverage-v8']).toBeUndefined();
+    expect(overrides['@vitest/coverage-istanbul']).toBeUndefined();
+  });
+
+  it('does not add a coverage provider the project never declared', () => {
+    // A project that uses vitest WITHOUT a coverage provider must not have one
+    // injected by the migration — the user installs it only if they need it.
     fs.writeFileSync(
-      path.join(tmpDir, 'pnpm-workspace.yaml'),
-      [
-        'packages:',
-        '  - packages/*',
-        'overrides:',
-        '  vite: catalog:vite7',
-        'catalogs:',
-        '  vite7:',
-        '    vite: ^7.0.0',
-        '',
-      ].join('\n'),
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', vitest: '^4.0.0' },
+      }),
     );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
 
-    rewriteMonorepo(makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true);
-
-    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
-      overrides: Record<string, string>;
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      devDependencies: Record<string, string>;
     };
-    expect(yaml.overrides.vite).toBe('catalog:');
-    expect(yaml.overrides.vitest).toBe('catalog:');
+    expect(pkg.devDependencies['@vitest/coverage-v8']).toBeUndefined();
+    expect(pkg.devDependencies['@vitest/coverage-istanbul']).toBeUndefined();
   });
 
-  it('does not resolve peer dependency catalog specs to migrated aliases', () => {
+  it('drops a vite-plus-scoped provider pin while keeping non-provider siblings', () => {
+    // Inside a `vite-plus` subtree only the provider pin is dropped; an
+    // unrelated sibling (`lodash`) keeps the `vite-plus` parent alive.
     fs.writeFileSync(
       path.join(tmpDir, 'package.json'),
       JSON.stringify({
         name: 'test',
-        peerDependencies: {
-          vite: 'catalog:vite7',
-          vitest: 'catalog:',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          'vite-plus': { '@vitest/browser-playwright': '4.0.0', lodash: '4.17.0' },
         },
       }),
     );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, Record<string, string>>;
+    };
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).toHaveProperty('vite-plus');
+    expect(overrides['vite-plus']).not.toHaveProperty('@vitest/browser-playwright');
+    expect(overrides['vite-plus'].lodash).toBe('4.17.0');
+  });
+
+  it('drops a top-level global provider pin', () => {
+    // A TOP-LEVEL provider pin is a global override that reaches vite-plus's
+    // bundled copy, so it must be dropped (regression guard).
     fs.writeFileSync(
-      path.join(tmpDir, 'pnpm-workspace.yaml'),
-      [
-        'catalog:',
-        '  vitest: npm:@voidzero-dev/vite-plus-test@latest',
-        'catalogs:',
-        '  vite7:',
-        '    vite: npm:@voidzero-dev/vite-plus-core@latest',
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          '@vitest/browser-playwright': '4.0.0',
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, unknown>;
+    };
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).not.toHaveProperty('@vitest/browser-playwright');
+  });
+
+  it('drops a long-form top-level provider self-pin but keeps unrelated children', () => {
+    // A long-form provider override pins the provider's own version via the `.`
+    // self-key; that pin is dropped (it reaches vite-plus's bundled copy) while
+    // unrelated scoped children (`bar`) are preserved.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          '@vitest/browser-playwright': { '.': '4.0.0', bar: '1.0.0' },
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, Record<string, string>>;
+    };
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).toHaveProperty('@vitest/browser-playwright');
+    const provider = overrides['@vitest/browser-playwright'];
+    // The provider's own version pin (`.`) is dropped; the `.` self-key must
+    // be asserted via `in` (Jest's `toHaveProperty('.')` treats `.` as a path
+    // separator and would not match the literal key).
+    expect('.' in provider).toBe(false);
+    expect(provider.bar).toBe('1.0.0');
+  });
+
+  it('preserves a provider pin nested under an outer non-vite-plus ancestor', () => {
+    // `{ a: { vite-plus: { provider } } }` forces the provider only for the
+    // vite-plus instance that sits UNDER `a` — NOT the project's own (root)
+    // vite-plus direct dep. It is the npm/bun nested equivalent of the flat pnpm
+    // `a>vite-plus>provider` chain, so (like that chain) it must be PRESERVED.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          a: { 'vite-plus': { '@vitest/browser-playwright': '4.0.0' } },
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, Record<string, Record<string, string>>>;
+    };
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).toHaveProperty('a');
+    expect(overrides.a['vite-plus']['@vitest/browser-playwright']).toBe('4.0.0');
+  });
+
+  it('drops a root vite-plus provider pin nested one level deep and prunes the parent', () => {
+    // `{ vite-plus: { provider } }` forces the provider as a direct dep of the
+    // root vite-plus, so it IS dropped; the emptied `vite-plus` parent is pruned.
+    // Contrast the outer-ancestor case above — only a single-segment `vite-plus`
+    // chain reaches the protected edge.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          'vite-plus': { '@vitest/browser-playwright': { '.': '4.0.0' } },
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, unknown>;
+    };
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).not.toHaveProperty('vite-plus');
+  });
+
+  it('preserves a deep provider override under unrelated parents', () => {
+    // No `vite-plus` parent anywhere on the path: the provider pin is the
+    // user's scoped override (`a > b > provider`) and must survive fully.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          a: { b: { '@vitest/browser-playwright': '4.0.0' } },
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, Record<string, Record<string, string>>>;
+    };
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).toHaveProperty('a');
+    expect(overrides.a).toHaveProperty('b');
+    expect(overrides.a.b['@vitest/browser-playwright']).toBe('4.0.0');
+  });
+
+  it('does not over-delete a non-provider override scoped under vite-plus', () => {
+    // A non-provider pin (`lodash`) under `vite-plus` is a legitimate user
+    // override; descending into the `vite-plus` subtree must leave it untouched.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          'vite-plus': { lodash: '4.17.0' },
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, Record<string, string>>;
+    };
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).toHaveProperty('vite-plus');
+    expect(overrides['vite-plus'].lodash).toBe('4.17.0');
+  });
+
+  it('leaves a user-authored pre-existing empty override object untouched', () => {
+    // We only prune parents WE empty by dropping provider pins. A parent the
+    // user authored as already-empty must be preserved as-is even when an
+    // unrelated top-level provider key is dropped in the same pass.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          'some-pkg': {},
+          '@vitest/browser-playwright': '4.0.0',
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, unknown>;
+    };
+    const overrides = pkg.overrides ?? {};
+    expect(overrides).not.toHaveProperty('@vitest/browser-playwright');
+    expect(overrides).toHaveProperty('some-pkg');
+    expect(overrides['some-pkg']).toEqual({});
+  });
+
+  it('does not crash on a nested object value under a managed bun catalog override key', () => {
+    // Bun monorepo: a nested object value under a MANAGED override key (e.g.
+    // `vitest`) is a user override scoped under that key, not a version pin.
+    // The bun catalog rewrite must not pass it to getCatalogDependencySpec
+    // (which calls `.startsWith` and would crash / clobber it to a string).
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'bun-monorepo',
+        workspaces: ['packages/*'],
+        devDependencies: { vite: '^7.0.0' },
+        overrides: {
+          vitest: { '@vitest/runner': '4.0.0' },
+        },
+      }),
+    );
+
+    expect(() =>
+      rewriteMonorepo(makeWorkspaceInfo(tmpDir, PackageManager.bun), true),
+    ).not.toThrow();
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      overrides?: Record<string, unknown>;
+    };
+    const overrides = pkg.overrides ?? {};
+    // The nested override object is left intact, not clobbered to a string.
+    expect(overrides.vitest).toEqual({ '@vitest/runner': '4.0.0' });
+  });
+
+  it('drops stale @vitest/browser* overrides from pnpm-workspace.yaml', () => {
+    // The migration moves provider packages out of project manifests and adds
+    // them as direct vite-plus deps. A pre-existing workspace override pinning
+    // an old provider version would then force vite-plus's own provider dep to
+    // an incompatible version against the bundled vitest.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      [
+        'packages:',
+        '  - packages/*',
+        'overrides:',
+        "  '@vitest/browser-playwright': 4.0.0",
+        "  '@vitest/browser-webdriverio': 4.0.0",
+        "  '@vitest/browser-playwright@4': 4.0.0",
+        "  'vite-plus>@vitest/browser-playwright': 4.0.0",
+        "  'some-app>@vitest/browser-playwright': 4.0.0",
+        '  some-other-pkg: 1.0.0',
+        "  'unrelated>some-other-pkg': 1.0.0",
         '',
       ].join('\n'),
     );
 
     rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
 
-    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
-      peerDependencies: Record<string, string>;
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      overrides: Record<string, string>;
     };
-    expect(pkg.peerDependencies.vite).toBe('*');
-    expect(pkg.peerDependencies.vitest).toBe('*');
+    // Playwright is opt-in: vite-plus keeps it in the user's deps pinned to the
+    // bundled vitest version, but a stale override pinning an old version (as a
+    // TARGET — bare/versioned/global pin, or a `vite-plus`-parented selector)
+    // would win over that direct dep and misalign the provider against bundled
+    // vitest — so the stale override is dropped (the dep stays installed, the pin
+    // does not).
+    expect(yaml.overrides).not.toHaveProperty('@vitest/browser-playwright');
+    expect(yaml.overrides).not.toHaveProperty('@vitest/browser-playwright@4');
+    expect(yaml.overrides).not.toHaveProperty('vite-plus>@vitest/browser-playwright');
+    // A provider-as-TARGET selector scoped under a SPECIFIC non-vite-plus parent
+    // only constrains that parent's subtree, so it is PRESERVED.
+    expect(yaml.overrides['some-app>@vitest/browser-playwright']).toBe('4.0.0');
+    // Webdriverio is opt-in: vite-plus keeps it in the user's deps pinned to the
+    // bundled vitest version, but a stale override pinning an old version would
+    // win over that direct dep and misalign the provider against bundled vitest —
+    // so the stale override is dropped too (the dep stays installed, the pin
+    // does not).
+    expect(yaml.overrides).not.toHaveProperty('@vitest/browser-webdriverio');
+    expect(yaml.overrides['some-other-pkg']).toBe('1.0.0');
+    expect(yaml.overrides['unrelated>some-other-pkg']).toBe('1.0.0');
+  });
+
+  it('adds a direct vitest dep when a vite config enables browser mode', () => {
+    // A package whose vite config imports a browser provider but has no direct
+    // vitest dep — `@vitest/browser` needs `vitest` resolvable from the package
+    // root, so the migration must pin it. Mirrors the vibe-dashboard regression.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { playwright: '^1.58.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'vite.config.ts'),
+      [
+        "import { playwright } from 'vite-plus/test/browser-playwright';",
+        "import { defineConfig } from 'vite-plus';",
+        'export default defineConfig({',
+        '  test: { browser: { enabled: true, provider: playwright() } },',
+        '});',
+        '',
+      ].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json'));
+    const devDeps = pkg.devDependencies as Record<string, string>;
+    expect(devDeps.vitest).toBe('catalog:');
+    expect(devDeps['vite-plus']).toBe('catalog:');
+  });
+
+  it('detects browser mode from a test file when the config has no hint', () => {
+    // Browser config can live in a workspace-referenced config under any name;
+    // the source scan also catches `@vitest/browser*` imports in test files.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.mkdirSync(path.join(tmpDir, 'src', '__tests__'), { recursive: true });
+    fs.writeFileSync(
+      path.join(tmpDir, 'src', '__tests__', 'app.test.ts'),
+      "import { page } from '@vitest/browser/context';\n",
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    expect(devDeps.vitest).toBe('catalog:');
+  });
+
+  // Published browser surfaces whose specifier carries NO `vite-plus/test/browser`
+  // substring must still be detected as browser mode, so migration pins the
+  // direct `vitest` the optimizer needs resolvable from the package root under
+  // pnpm strict / Yarn PnP. Two families:
+  //   - the bare browser shims `vite-plus/test/{client,context,locators,matchers,
+  //     utils}` (build.ts createBareBrowserShims; the rewriter flattens four of
+  //     them, `context` is the published bare export), and
+  //   - the generated plugin shims `vite-plus/test/plugins/browser*` (build.ts
+  //     PLUGIN_SHIM_ENTRIES) sitting under a `/plugins/` segment, and
+  //   - the published internal shim `vite-plus/test/internal/browser`
+  //     (re-exports `vitest/internal/browser`).
+  // Each is a browser surface yet a package importing only one of them with no
+  // `@vitest/browser*` dep must get a direct `vitest` (and must NOT gain an
+  // injected `@vitest/browser`).
+  for (const subpath of [
+    'client',
+    'context',
+    'locators',
+    'matchers',
+    'utils',
+    'plugins/browser',
+    'plugins/browser-context',
+    'plugins/browser-playwright',
+    'internal/browser',
+  ] as const) {
+    it(`detects browser mode from the published \`vite-plus/test/${subpath}\` shim`, () => {
+      fs.writeFileSync(
+        path.join(tmpDir, 'package.json'),
+        JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+      );
+      fs.mkdirSync(path.join(tmpDir, 'src', '__tests__'), { recursive: true });
+      fs.writeFileSync(
+        path.join(tmpDir, 'src', '__tests__', 'app.test.ts'),
+        `import { thing } from 'vite-plus/test/${subpath}';\n`,
+      );
+      rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+      const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+        string,
+        string
+      >;
+      // Browser mode pins a direct `vitest`…
+      expect(devDeps.vitest).toBe('catalog:');
+      // …but must NOT inject any browser/provider dep the source never asked for.
+      expect(devDeps).not.toHaveProperty('@vitest/browser');
+    });
+  }
+
+  it('injects the webdriverio provider + peer from a source-only vitest config and allows driver builds', () => {
+    // Opt-in provider: vite-plus no longer bundles `@vitest/browser-webdriverio`.
+    // A project that imports it in source with NO declared dep must have the
+    // provider injected into its own deps (pinned to the bundled vitest version)
+    // plus the `webdriverio` framework peer, and the edgedriver/geckodriver
+    // postinstalls allowed.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'vitest.config.ts'),
+      [
+        "import { webdriverio } from '@vitest/browser-webdriverio';",
+        "import { defineConfig } from 'vite-plus';",
+        'export default defineConfig({',
+        '  test: { browser: { enabled: true, provider: webdriverio() } },',
+        '});',
+        '',
+      ].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    // Opt-in provider is pinned to a CONCRETE bundled vitest version in the
+    // user's own deps — it is deliberately NOT in VITE_PLUS_OVERRIDE_PACKAGES, so
+    // no catalog entry is written for it and it must self-resolve.
+    expect(devDeps).toHaveProperty('@vitest/browser-webdriverio', VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('injects the playwright provider + peer from a source-only vitest config', () => {
+    // Opt-in provider: vite-plus no longer bundles `@vitest/browser-playwright`
+    // at runtime (its `playwright` peer is non-optional). A project that imports
+    // it in source with NO declared dep must have the provider injected into its
+    // own deps (pinned to the bundled vitest version) plus the `playwright`
+    // framework peer. (Playwright has no edgedriver/geckodriver postinstall, so
+    // — unlike webdriverio — it does not touch allowBuilds.)
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'vitest.config.ts'),
+      [
+        "import { playwright } from '@vitest/browser-playwright';",
+        "import { defineConfig } from 'vite-plus';",
+        'export default defineConfig({',
+        '  test: { browser: { enabled: true, provider: playwright() } },',
+        '});',
+        '',
+      ].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    // Opt-in provider pinned to a CONCRETE bundled vitest version in the user's
+    // own deps — deliberately NOT in VITE_PLUS_OVERRIDE_PACKAGES, so no catalog
+    // entry is written for it and it must self-resolve.
+    expect(devDeps).toHaveProperty('@vitest/browser-playwright', VITEST_VERSION);
+    expect(devDeps.playwright).toBe('*');
+  });
+
+  it('injects the playwright provider on a re-run from the migrated provider-subpath import', () => {
+    // Re-running migration on an ALREADY-migrated project: the import rewriter
+    // maps `@vitest/browser-playwright/provider` to
+    // `vite-plus/test/browser/providers/playwright`, so an already-migrated
+    // source can contain that subpath form. The playwright source scan must
+    // recognize it, or the re-run would skip injecting the (no-longer-bundled)
+    // provider and the import would break under pnpm strict / Yarn PnP.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'vitest.config.ts'),
+      [
+        "import { playwright } from 'vite-plus/test/browser/providers/playwright';",
+        "import { defineConfig } from 'vite-plus';",
+        'export default defineConfig({',
+        '  test: { browser: { enabled: true, provider: playwright() } },',
+        '});',
+        '',
+      ].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    expect(devDeps).toHaveProperty('@vitest/browser-playwright', VITEST_VERSION);
+    expect(devDeps.playwright).toBe('*');
+  });
+
+  it('injects the webdriverio provider on a re-run from the migrated provider-subpath import', () => {
+    // Re-running migration on an ALREADY-migrated project: the import rewriter
+    // maps `@vitest/browser-webdriverio/provider` to
+    // `vite-plus/test/browser/providers/webdriverio`, so an already-migrated
+    // source can contain that subpath form (not just `vite-plus/test/browser-
+    // webdriverio`). The webdriverio source scan must recognize it, or the re-run
+    // would skip injecting the (no-longer-bundled) provider and the import would
+    // break under pnpm strict / Yarn PnP.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'vitest.config.ts'),
+      [
+        "import { webdriverio } from 'vite-plus/test/browser/providers/webdriverio';",
+        "import { defineConfig } from 'vite-plus';",
+        'export default defineConfig({',
+        '  test: { browser: { enabled: true, provider: webdriverio() } },',
+        '});',
+        '',
+      ].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    expect(devDeps).toHaveProperty('@vitest/browser-webdriverio', VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('injects the webdriverio provider from a source-only import of the plugin shim', () => {
+    // `vite-plus/test/plugins/browser-webdriverio` is a generated shim that
+    // re-exports `@vitest/browser-webdriverio` wholesale, so importing it uses
+    // the (now opt-in, no-longer-bundled) provider. A source-only import of it
+    // must still trigger provider+peer injection and driver-build allowance.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'vitest.config.ts'),
+      [
+        "import { webdriverio } from 'vite-plus/test/plugins/browser-webdriverio';",
+        "import { defineConfig } from 'vite-plus';",
+        'export default defineConfig({',
+        '  test: { browser: { enabled: true, provider: webdriverio() } },',
+        '});',
+        '',
+      ].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    expect(devDeps).toHaveProperty('@vitest/browser-webdriverio', VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('keeps a peer-only catalog webdriverio provider resolvable (no dangling catalog reference)', () => {
+    // A package declares the provider ONLY as a `peerDependencies` `catalog:`
+    // entry. The migration installs the provider into the user's own deps so the
+    // rewritten import resolves, but it must NOT delete the catalog entry the
+    // surviving peer still references — deleting it would dangle the `catalog:`
+    // spec and break the next install. (Catalog deletion uses REMOVE_PACKAGES,
+    // not the override-drop set, precisely so webdriverio entries are preserved.)
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        peerDependencies: { '@vitest/browser-webdriverio': 'catalog:' },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      [
+        'packages:',
+        '  - packages/*',
+        'catalog:',
+        "  '@vitest/browser-webdriverio': 4.0.0",
+        '',
+      ].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json'));
+    const devDeps = pkg.devDependencies as Record<string, string>;
+    // Provider installed in the user's own deps at the bundled vitest version.
+    expect(devDeps).toHaveProperty('@vitest/browser-webdriverio', VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+    // Peer-only declaration is left intact and its `catalog:` reference still
+    // resolves because the catalog entry is preserved (NOT deleted).
+    expect((pkg.peerDependencies as Record<string, string>)['@vitest/browser-webdriverio']).toBe(
+      'catalog:',
+    );
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      catalog: Record<string, string>;
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.catalog['@vitest/browser-webdriverio']).toBe('4.0.0');
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('drops a stale npm @vitest/browser-webdriverio override that would conflict with the injected provider', () => {
+    // npm hard-fails with EOVERRIDE when an override pins the provider to a
+    // version different from the migrated direct dep. Because webdriverio is now
+    // KEPT/injected as a direct dep (not stripped), the migration must prune the
+    // stale `overrides` entry before injecting `@vitest/browser-webdriverio@4.1.9`.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', '@vitest/browser-webdriverio': '^4.0.0' },
+        overrides: { '@vitest/browser-webdriverio': '4.0.0', 'some-other-pkg': '1.0.0' },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json'));
+    const overrides = pkg.overrides as Record<string, string>;
+    // Stale provider override dropped (it would EOVERRIDE-conflict with the dep).
+    expect(overrides).not.toHaveProperty('@vitest/browser-webdriverio');
+    // Unrelated overrides preserved.
+    expect(overrides['some-other-pkg']).toBe('1.0.0');
+    // Provider normalized to the bundled vitest version, peer ensured.
+    const devDeps = pkg.devDependencies as Record<string, string>;
+    expect(devDeps['@vitest/browser-webdriverio']).toBe(VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+  });
+
+  it('drops a stale npm @vitest/browser-playwright override that would conflict with the kept provider', () => {
+    // Same hazard as webdriverio: playwright is now opt-in and KEPT as a direct
+    // dep (not stripped), so a stale `overrides` pin to a different version would
+    // EOVERRIDE-conflict with the migrated `@vitest/browser-playwright@4.1.9`. The
+    // migration must prune it before normalizing the provider dep.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', '@vitest/browser-playwright': '^4.0.0' },
+        overrides: { '@vitest/browser-playwright': '4.0.0', 'some-other-pkg': '1.0.0' },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json'));
+    const overrides = pkg.overrides as Record<string, string>;
+    expect(overrides).not.toHaveProperty('@vitest/browser-playwright');
+    expect(overrides['some-other-pkg']).toBe('1.0.0');
+    const devDeps = pkg.devDependencies as Record<string, string>;
+    expect(devDeps['@vitest/browser-playwright']).toBe(VITEST_VERSION);
+    expect(devDeps.playwright).toBe('*');
+  });
+
+  it('drops a stale @vitest/browser-webdriverio override pinned with a COMPARATOR range', () => {
+    // A `name@range` override key may use a semver comparator (`@>=4`, `@>4`,
+    // `@<5`). The `>` MUST NOT be mistaken for a pnpm `parent>child` selector
+    // (pnpm's own delimiter rule excludes a `>` preceded by `@`), or the key's
+    // target is parsed incorrectly and the stale pin survives, forcing the
+    // provider off the migrated 4.1.9 dep. A comparator-range key for an
+    // unrelated package must still be preserved.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', '@vitest/browser-webdriverio': '^4.0.0' },
+        overrides: {
+          '@vitest/browser-webdriverio@>=4': '4.0.0',
+          'some-other-pkg@>=1': '1.0.0',
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.npm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json'));
+    const overrides = pkg.overrides as Record<string, string>;
+    expect(overrides).not.toHaveProperty('@vitest/browser-webdriverio@>=4');
+    // Unrelated comparator-range override preserved.
+    expect(overrides['some-other-pkg@>=1']).toBe('1.0.0');
+    const devDeps = pkg.devDependencies as Record<string, string>;
+    expect(devDeps['@vitest/browser-webdriverio']).toBe(VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+  });
+
+  it('drops a stale yarn @vitest/browser-webdriverio resolution that would force the wrong provider version', () => {
+    // Same hazard as npm, via yarn `resolutions`: a leftover pin would force the
+    // stale provider over the migrated, bundled-vitest-aligned 4.1.9 dep.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', '@vitest/browser-webdriverio': '^4.0.0' },
+        resolutions: { '@vitest/browser-webdriverio': '4.0.0', 'some-other-pkg': '1.0.0' },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.yarn), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json'));
+    const resolutions = pkg.resolutions as Record<string, string>;
+    expect(resolutions).not.toHaveProperty('@vitest/browser-webdriverio');
+    expect(resolutions['some-other-pkg']).toBe('1.0.0');
+    const devDeps = pkg.devDependencies as Record<string, string>;
+    expect(devDeps['@vitest/browser-webdriverio']).toBe(VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+  });
+
+  it('drops only global/glob/vite-plus-parent yarn SELECTOR-shaped @vitest/browser-webdriverio resolutions', () => {
+    // Yarn resolutions commonly use selector shapes (glob `**/pkg`, nested
+    // `parent/pkg`). A pin is pruned only when it would reach vite-plus's OWN
+    // direct provider dep — i.e. a versioned global pin, a NAME glob that matches
+    // vite-plus (`**`, `vite-*`), or a parent that is literally `vite-plus`. A
+    // selector scoped under a SPECIFIC non-vite-plus parent — including a
+    // wildcard RANGE on that parent (`parent@*`, `parent@workspace:*`) or a name
+    // glob that does NOT match vite-plus (`react-*`) — only constrains that
+    // parent's subtree and is preserved (over-reaching would silently change
+    // that parent's resolved transitive provider).
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', '@vitest/browser-webdriverio': '^4.0.0' },
+        resolutions: {
+          '**/@vitest/browser-webdriverio': '4.0.0',
+          'vite-*/@vitest/browser-webdriverio': '4.0.0',
+          'vite-plus/@vitest/browser-webdriverio': '4.0.0',
+          '**/vite-plus/@vitest/browser-webdriverio': '4.0.0',
+          'some-parent/@vitest/browser-webdriverio': '4.0.0',
+          'react-*/@vitest/browser-webdriverio': '4.0.0',
+          'parent@*/@vitest/browser-webdriverio': '4.0.0',
+          'parent@workspace:*/@vitest/browser-webdriverio': '4.0.0',
+          'some-parent/**/@vitest/browser-webdriverio': '4.0.0',
+          'some-parent/vite-*/@vitest/browser-webdriverio': '4.0.0',
+          '@vitest/browser-webdriverio@4': '4.0.0',
+          '**/some-other-pkg': '1.0.0',
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.yarn), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json'));
+    const resolutions = pkg.resolutions as Record<string, string>;
+    // Glob parent matches all parents (incl. vite-plus) — dropped.
+    expect(resolutions).not.toHaveProperty('**/@vitest/browser-webdriverio');
+    // Name glob that matches vite-plus — dropped.
+    expect(resolutions).not.toHaveProperty('vite-*/@vitest/browser-webdriverio');
+    // Parent is literally vite-plus — dropped.
+    expect(resolutions).not.toHaveProperty('vite-plus/@vitest/browser-webdriverio');
+    // `**`-padded vite-plus reaches the root vite-plus edge — dropped.
+    expect(resolutions).not.toHaveProperty('**/vite-plus/@vitest/browser-webdriverio');
+    // Versioned global pin — dropped.
+    expect(resolutions).not.toHaveProperty('@vitest/browser-webdriverio@4');
+    // Scoped under a SPECIFIC non-vite-plus parent — PRESERVED (does not affect
+    // vite-plus's own provider dep).
+    expect(resolutions['some-parent/@vitest/browser-webdriverio']).toBe('4.0.0');
+    // A name glob that does NOT match vite-plus — PRESERVED.
+    expect(resolutions['react-*/@vitest/browser-webdriverio']).toBe('4.0.0');
+    // A wildcard RANGE on a specific parent is not a glob parent — PRESERVED.
+    expect(resolutions['parent@*/@vitest/browser-webdriverio']).toBe('4.0.0');
+    expect(resolutions['parent@workspace:*/@vitest/browser-webdriverio']).toBe('4.0.0');
+    // A nested glob gated by a SPECIFIC non-vite-plus ancestor only constrains
+    // that ancestor's subtree, NOT the root vite-plus edge — PRESERVED.
+    expect(resolutions['some-parent/**/@vitest/browser-webdriverio']).toBe('4.0.0');
+    expect(resolutions['some-parent/vite-*/@vitest/browser-webdriverio']).toBe('4.0.0');
+    // Unrelated selector resolutions survive.
+    expect(resolutions['**/some-other-pkg']).toBe('1.0.0');
+    const devDeps = pkg.devDependencies as Record<string, string>;
+    expect(devDeps['@vitest/browser-webdriverio']).toBe(VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+  });
+
+  it('preserves yarn from/target resolutions that do NOT target the provider (yarn-grammar faithful)', () => {
+    // A yarn `from/target` resolution key forces the TRAILING descriptor, not
+    // the parent. Verified against @yarnpkg/parsers parseResolution:
+    //   `@vitest/browser-webdriverio@4/some-transitive-dep`
+    //       -> from=@vitest/browser-webdriverio@4, descriptor=some-transitive-dep
+    //   `@vitest/browser-webdriverio@npm:@other/fork@1.2.3`
+    //       -> from=@vitest/browser-webdriverio@npm:@other, descriptor=fork@1.2.3
+    // Neither targets the provider, so neither may be pruned — dropping them
+    // would silently delete an unrelated user resolution. (Yarn rejects keys
+    // whose range embeds a `/`, e.g. `pkg@patch:…/…` or git/URL ranges, so those
+    // can never appear as valid keys.) Only keys whose TARGET is the provider
+    // are dropped — see the SELECTOR-shaped test above.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', '@vitest/browser-webdriverio': '^4.0.0' },
+        resolutions: {
+          '@vitest/browser-webdriverio@4/some-transitive-dep': '1.0.0',
+          '@vitest/browser-webdriverio@npm:@other/fork@1.2.3': '2.0.0',
+          '@vitest/browser-webdriverio': '4.0.0',
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.yarn), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json'));
+    const resolutions = pkg.resolutions as Record<string, string>;
+    // Parent-through-provider key targets some-transitive-dep — preserved.
+    expect(resolutions['@vitest/browser-webdriverio@4/some-transitive-dep']).toBe('1.0.0');
+    // npm-alias key targets `fork` (the aliased descriptor), not the provider — preserved.
+    expect(resolutions['@vitest/browser-webdriverio@npm:@other/fork@1.2.3']).toBe('2.0.0');
+    // The bare key DOES target the provider — pruned so it can't force the
+    // stale provider over the migrated 4.1.9 dep.
+    expect(resolutions).not.toHaveProperty('@vitest/browser-webdriverio');
+    const devDeps = pkg.devDependencies as Record<string, string>;
+    expect(devDeps['@vitest/browser-webdriverio']).toBe(VITEST_VERSION);
+    expect(devDeps.webdriverio).toBe('*');
+  });
+
+  it('does not add vitest for a package without browser mode', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'vite.config.ts'),
+      "import { defineConfig } from 'vite';\nexport default defineConfig({});\n",
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    expect(devDeps).not.toHaveProperty('vitest');
+  });
+
+  it('detects browser mode from a declared provider dep with no source imports', () => {
+    // Config-only browser mode: `vite.config.ts` enables the browser runner by
+    // provider name (resolved by vitest at runtime) and the user lists
+    // `@vitest/browser-playwright` in devDependencies — but no source or config
+    // file imports `@vitest/browser*`. The source-scan signal is therefore
+    // false; the dep declaration is what tells us the package drives browser
+    // mode. After migration, `vitest` must still be pinned as a direct dep so
+    // the browser optimizer can resolve it under pnpm strict / Yarn PnP.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { '@vitest/browser-playwright': '^4.1.7' },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'vite.config.ts'),
+      [
+        "import { defineConfig } from 'vite';",
+        "export default defineConfig({ test: { browser: { provider: 'playwright' } } });",
+        '',
+      ].join('\n'),
+    );
+
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    expect(devDeps.vitest).toBe('catalog:');
+    expect(devDeps['vite-plus']).toBe('catalog:');
+    // Playwright is opt-in: vite-plus keeps the provider in the user's deps,
+    // normalized to the bundled vitest version.
+    expect(devDeps['@vitest/browser-playwright']).toBe(VITEST_VERSION);
+    // Provider's runtime peer dep is preserved.
+    expect(devDeps.playwright).toBe('*');
+  });
+
+  it('preserves named pnpm overrides when moving root overrides to pnpm-workspace.yaml', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'pnpm-monorepo',
+        workspaces: ['packages/*'],
+        devDependencies: { vite: 'catalog:vite7' },
+        pnpm: {
+          overrides: {
+            vite: 'catalog:vite7',
+            react: '^18.0.0',
+          },
+        },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      ['packages:', '  - packages/*', 'catalogs:', '  vite7:', '    vite: ^7.0.0', ''].join('\n'),
+    );
+
+    rewriteMonorepo(makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      overrides: Record<string, string>;
+      catalogs: Record<string, Record<string, string>>;
+    };
+    expect(yaml.overrides.vite).toBe('catalog:vite7');
+    // vitest is now injected into overrides as a managed override key.
+    expect(yaml.overrides.vitest).toBe('catalog:');
+    expect(yaml.overrides.react).toBe('^18.0.0');
+    expect(yaml.catalogs.vite7.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      pnpm?: unknown;
+    };
+    expect(pkg.pnpm).toBeUndefined();
+  });
+
+  it('preserves default pnpm catalog overrides over stale workspace named overrides', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'pnpm-monorepo',
+        workspaces: ['packages/*'],
+        devDependencies: { vite: 'catalog:' },
+        pnpm: {
+          overrides: {
+            vite: 'catalog:',
+          },
+        },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      [
+        'packages:',
+        '  - packages/*',
+        'overrides:',
+        '  vite: catalog:vite7',
+        'catalogs:',
+        '  vite7:',
+        '    vite: ^7.0.0',
+        '',
+      ].join('\n'),
+    );
+
+    rewriteMonorepo(makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      overrides: Record<string, string>;
+    };
+    expect(yaml.overrides.vite).toBe('catalog:');
+    // vitest is now a managed override key — added to overrides as catalog: ref.
+    expect(yaml.overrides.vitest).toBe('catalog:');
+  });
+
+  it('does not resolve peer dependency catalog specs to migrated aliases', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        peerDependencies: {
+          vite: 'catalog:vite7',
+          vitest: 'catalog:',
+        },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      [
+        'catalog:',
+        '  vitest: npm:@voidzero-dev/vite-plus-test@latest',
+        'catalogs:',
+        '  vite7:',
+        '    vite: npm:@voidzero-dev/vite-plus-core@latest',
+        '',
+      ].join('\n'),
+    );
+
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      peerDependencies: Record<string, string>;
+    };
+    expect(pkg.peerDependencies.vite).toBe('*');
+    // vitest is now a managed override key — peer dep catalog refs that
+    // resolve to the override target are coerced to '*'.
+    expect(pkg.peerDependencies.vitest).toBe('*');
+  });
+
+  it('adds vitest only to the monorepo package that uses browser mode', () => {
+    // Root has no browser config; only `apps/dashboard` does. The browser-mode
+    // scan must stop at the nested package.json boundary so the root package
+    // does not inherit the sub-package's signal.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'root', devDependencies: {} }),
+    );
+    fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), 'packages:\n  - apps/*\n');
+    const appDir = path.join(tmpDir, 'apps', 'dashboard');
+    fs.mkdirSync(appDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(appDir, 'package.json'),
+      JSON.stringify({ name: '@vibe/dashboard', devDependencies: { playwright: '^1.58.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(appDir, 'vite.config.ts'),
+      [
+        "import { playwright } from 'vite-plus/test/browser-playwright';",
+        "import { defineConfig } from 'vite-plus';",
+        'export default defineConfig({ test: { browser: { provider: playwright() } } });',
+        '',
+      ].join('\n'),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@vibe/dashboard', path: 'apps/dashboard' }];
+    rewriteMonorepo(workspaceInfo, true);
+
+    const rootDeps = (readJson(path.join(tmpDir, 'package.json')).devDependencies ?? {}) as Record<
+      string,
+      string
+    >;
+    expect(rootDeps).not.toHaveProperty('vitest');
+
+    const appDeps = readJson(path.join(appDir, 'package.json')).devDependencies as Record<
+      string,
+      string
+    >;
+    expect(appDeps.vitest).toBe('catalog:');
+  });
+
+  it('opts vite-plus workspaces out of yarn nmHoistingLimits so the bundled vitest dedupes', () => {
+    // Yarn `node-modules` + `nmHoistingLimits: workspaces` would give every
+    // workspace that gets `vite-plus` (which depends on the bundled `vitest`) its
+    // own physical `vitest`/`@vitest/runner` copy, splitting `vp test`'s runner
+    // across two instances -> `TypeError: ...reading 'config'`. The migration must
+    // set `installConfig.hoistingLimits: none` on each vite-plus-receiving
+    // workspace so its vitest hoists to the single shared root copy, WITHOUT
+    // touching the root `.yarnrc.yml` isolation (which unrelated workspaces such as
+    // a React Native `example` may rely on).
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'root',
+        private: true,
+        workspaces: ['packages/*'],
+        devDependencies: {},
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: workspaces\n',
+    );
+    // A workspace WITH vitest -> gets vite-plus -> must be opted out.
+    const libDir = path.join(tmpDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+    );
+    // A workspace WITHOUT any vite-plus signal -> must stay untouched.
+    const isoDir = path.join(tmpDir, 'packages', 'iso');
+    fs.mkdirSync(isoDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(isoDir, 'package.json'),
+      JSON.stringify({ name: '@scope/iso', dependencies: { 'left-pad': '^1.0.0' } }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.yarn);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [
+      { name: '@scope/lib', path: 'packages/lib' },
+      { name: '@scope/iso', path: 'packages/iso' },
+    ];
+    const report = createMigrationReport();
+    rewriteMonorepo(workspaceInfo, true, false, report);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    expect(libPkg.installConfig?.hoistingLimits).toBe('none');
+
+    // No vite-plus added -> no installConfig opt-out.
+    const isoPkg = readJson(path.join(isoDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: unknown;
+    };
+    expect(isoPkg.devDependencies ?? {}).not.toHaveProperty('vite-plus');
+    expect(isoPkg.installConfig).toBeUndefined();
+
+    // The root .yarnrc.yml isolation is preserved (not silently removed) and the
+    // root package.json is not given a redundant per-workspace opt-out.
+    expect(readYamlObject(path.join(tmpDir, '.yarnrc.yml')).nmHoistingLimits).toBe('workspaces');
+    expect(
+      (readJson(path.join(tmpDir, 'package.json')) as { installConfig?: unknown }).installConfig,
+    ).toBeUndefined();
+
+    // Auto-fix is silent: a deduped workspace needs no manual-step warning.
+    expect(report.warnings.some((w) => w.includes('isolates dependency hoisting'))).toBe(false);
+  });
+
+  it('leaves yarn workspaces alone when nmHoistingLimits does not isolate them', () => {
+    // Default hoisting (no nmHoistingLimits) already dedupes vitest to root, so the
+    // migration must NOT add a spurious installConfig opt-out.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'root',
+        private: true,
+        workspaces: ['packages/*'],
+        devDependencies: {},
+      }),
+    );
+    fs.writeFileSync(path.join(tmpDir, '.yarnrc.yml'), 'nodeLinker: node-modules\n');
+    const libDir = path.join(tmpDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.yarn);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@scope/lib', path: 'packages/lib' }];
+    const report = createMigrationReport();
+    rewriteMonorepo(workspaceInfo, true, false, report);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: unknown;
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    expect(libPkg.installConfig).toBeUndefined();
+    expect(report.warnings.some((w) => w.includes('isolates dependency hoisting'))).toBe(false);
+  });
+
+  it('does not auto-fix yarn `dependencies` hoisting (the opt-out cannot dedupe it)', () => {
+    // The stricter `nmHoistingLimits: dependencies` keeps a dep BELOW each
+    // dependent package even when the workspace opts out to `none` (verified with
+    // Yarn 4.17: two workspaces sharing a dep still produced two physical copies),
+    // so writing the opt-out would be false remediation — a package.json that looks
+    // fixed but keeps the crash layout. The migration must leave installConfig
+    // untouched for this mode.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'root',
+        private: true,
+        workspaces: ['packages/*'],
+        devDependencies: {},
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: dependencies\n',
+    );
+    const libDir = path.join(tmpDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.yarn);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@scope/lib', path: 'packages/lib' }];
+    const report = createMigrationReport();
+    rewriteMonorepo(workspaceInfo, true, false, report);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: unknown;
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    expect(libPkg.installConfig).toBeUndefined();
+    // Not silently broken: warn that vp test can crash for this isolated workspace.
+    expect(report.warnings.some((w) => w.includes('isolates dependency hoisting'))).toBe(true);
+  });
+
+  it('preserves an explicit workspace installConfig.hoistingLimits instead of clobbering it', () => {
+    // A workspace that deliberately set its OWN hoisting limit (e.g. to isolate its
+    // whole tree for Metro) and also uses Vite+ must keep that explicit invariant —
+    // `installConfig.hoistingLimits` governs the ENTIRE workspace tree, not just the
+    // vitest family. The opt-out only relaxes the INHERITED root limit (unset field).
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'root',
+        private: true,
+        workspaces: ['packages/*'],
+        devDependencies: {},
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: workspaces\n',
+    );
+    const libDir = path.join(tmpDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({
+        name: '@scope/lib',
+        devDependencies: { vitest: '^4.0.0' },
+        installConfig: { hoistingLimits: 'workspaces' },
+      }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.yarn);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@scope/lib', path: 'packages/lib' }];
+    const report = createMigrationReport();
+    rewriteMonorepo(workspaceInfo, true, false, report);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    // Explicit value preserved, NOT overwritten to 'none'.
+    expect(libPkg.installConfig?.hoistingLimits).toBe('workspaces');
+    // The preserved isolation still splits vp test, so it must be flagged.
+    expect(report.warnings.some((w) => w.includes('isolates dependency hoisting'))).toBe(true);
+  });
+
+  it('warns on workspace-level hoisting isolation even when the root nmHoistingLimits is unset', () => {
+    // A workspace can isolate its OWN tree via `installConfig.hoistingLimits`
+    // regardless of the root limit. With the root unset, that workspace still keeps
+    // its own vitest copy, so the migration must preserve the explicit value AND
+    // warn — the per-workspace check cannot be gated on the root limit.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'root',
+        private: true,
+        workspaces: ['packages/*'],
+        devDependencies: {},
+      }),
+    );
+    fs.writeFileSync(path.join(tmpDir, '.yarnrc.yml'), 'nodeLinker: node-modules\n');
+    const libDir = path.join(tmpDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({
+        name: '@scope/lib',
+        devDependencies: { vitest: '^4.0.0' },
+        installConfig: { hoistingLimits: 'workspaces' },
+      }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.yarn);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@scope/lib', path: 'packages/lib' }];
+    const report = createMigrationReport();
+    rewriteMonorepo(workspaceInfo, true, false, report);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    expect(libPkg.installConfig?.hoistingLimits).toBe('workspaces');
+    expect(report.warnings.some((w) => w.includes('isolates dependency hoisting'))).toBe(true);
+  });
+
+  it('warns on workspace-level hoisting isolation even when the root nmHoistingLimits is none', () => {
+    // Root explicitly `none` (default deduping) but the workspace pins its own
+    // `dependencies` isolation -> it still keeps its own vitest copy -> the
+    // migration must preserve the explicit value AND warn.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'root',
+        private: true,
+        workspaces: ['packages/*'],
+        devDependencies: {},
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: none\n',
+    );
+    const libDir = path.join(tmpDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({
+        name: '@scope/lib',
+        devDependencies: { vitest: '^4.0.0' },
+        installConfig: { hoistingLimits: 'dependencies' },
+      }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.yarn);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@scope/lib', path: 'packages/lib' }];
+    const report = createMigrationReport();
+    rewriteMonorepo(workspaceInfo, true, false, report);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    expect(libPkg.installConfig?.hoistingLimits).toBe('dependencies');
+    expect(report.warnings.some((w) => w.includes('isolates dependency hoisting'))).toBe(true);
+  });
+
+  it('auto-fixes a direct rewriteMonorepoProject call by deriving the root .yarnrc.yml limit', () => {
+    // Callers other than rewriteMonorepo (e.g. `vp create` integrating a package
+    // into an existing monorepo) call rewriteMonorepoProject directly with no
+    // workspace context and no root-limit argument. The root
+    // `nmHoistingLimits: workspaces` must still be discovered by walking up from
+    // the package directory, so the workspace is deduped — not silently left split.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'root', private: true, workspaces: ['packages/*'] }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: workspaces\n',
+    );
+    const libDir = path.join(tmpDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+    );
+
+    // Direct call: no workspaceContext, no root-limit arg.
+    rewriteMonorepoProject(libDir, PackageManager.yarn, true, true);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    expect(libPkg.installConfig?.hoistingLimits).toBe('none');
+  });
+
+  it('finds the real monorepo root limit even when the workspace has its own .yarnrc.yml', () => {
+    // `vp create` (and install) can write a package-local `.yarnrc.yml` under a
+    // workspace before it is rewritten. The hoisting lookup must NOT treat that
+    // child rc as the project root — it must find the actual workspace root (the
+    // package.json with `workspaces`) and read ITS nmHoistingLimits, so the
+    // workspace is still deduped rather than silently left split.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'root', private: true, workspaces: ['packages/*'] }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: workspaces\n',
+    );
+    const libDir = path.join(tmpDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+    );
+    // A package-local `.yarnrc.yml` (no nmHoistingLimits) must NOT shadow the root.
+    fs.writeFileSync(path.join(libDir, '.yarnrc.yml'), 'nodeLinker: node-modules\n');
+
+    // Direct call (no workspaceContext): the lookup must walk past the child rc to
+    // the workspace root.
+    rewriteMonorepoProject(libDir, PackageManager.yarn, true, true);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    expect(libPkg.installConfig?.hoistingLimits).toBe('none');
+  });
+
+  it('honours nmHoistingLimits set in an ANCESTOR .yarnrc.yml above the monorepo root', () => {
+    // Yarn merges `.yarnrc.yml` across the project root AND its ancestor directories
+    // (verified with Yarn 4.17: a key set only in an ancestor rc is in effect). So a
+    // monorepo root whose own `.yarnrc.yml` omits `nmHoistingLimits` can still inherit
+    // `workspaces` isolation from a parent rc. The lookup must resolve the effective
+    // limit across the chain, not read only the root dir's rc, or the workspace is
+    // silently left split.
+    const ancestorDir = tmpDir;
+    const rootDir = path.join(ancestorDir, 'monorepo');
+    fs.mkdirSync(rootDir, { recursive: true });
+    // Ancestor rc (ABOVE the monorepo root) sets the isolating limit.
+    fs.writeFileSync(
+      path.join(ancestorDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: workspaces\n',
+    );
+    // The monorepo root is the package.json with `workspaces`; its own rc omits the key.
+    fs.writeFileSync(
+      path.join(rootDir, 'package.json'),
+      JSON.stringify({ name: 'root', private: true, workspaces: ['packages/*'] }),
+    );
+    fs.writeFileSync(path.join(rootDir, '.yarnrc.yml'), 'nodeLinker: node-modules\n');
+    const libDir = path.join(rootDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+    );
+
+    // Direct call (no workspaceContext): the lookup must walk past the root rc to the
+    // ancestor rc to find the effective `workspaces` limit.
+    rewriteMonorepoProject(libDir, PackageManager.yarn, true, true);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    expect(libPkg.installConfig?.hoistingLimits).toBe('none');
+  });
+
+  it('lets a monorepo-root .yarnrc.yml override an ancestor nmHoistingLimits (closer wins)', () => {
+    // Yarn's config merge gives the closest `.yarnrc.yml` precedence: a root rc that
+    // sets `nmHoistingLimits: none` neutralizes an ancestor's `workspaces`, so the
+    // layout already dedupes and the migration must NOT add a spurious opt-out.
+    const ancestorDir = tmpDir;
+    const rootDir = path.join(ancestorDir, 'monorepo');
+    fs.mkdirSync(rootDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(ancestorDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: workspaces\n',
+    );
+    fs.writeFileSync(
+      path.join(rootDir, 'package.json'),
+      JSON.stringify({ name: 'root', private: true, workspaces: ['packages/*'] }),
+    );
+    // Root rc explicitly opts back into full hoisting — overrides the ancestor.
+    fs.writeFileSync(
+      path.join(rootDir, '.yarnrc.yml'),
+      'nodeLinker: node-modules\nnmHoistingLimits: none\n',
+    );
+    const libDir = path.join(rootDir, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+    );
+
+    rewriteMonorepoProject(libDir, PackageManager.yarn, true, true);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    // Closer rc wins (none) -> no isolation -> no opt-out added.
+    expect(libPkg.installConfig?.hoistingLimits).toBeUndefined();
+  });
+
+  it('skips the hoisting opt-out for a PnP Yarn project (nmHoistingLimits is inert without node-modules)', () => {
+    // Yarn DEFAULTS to Plug'n'Play; `nmHoistingLimits` only splits physical copies
+    // under the `node-modules` linker. With `nodeLinker` unset the effective linker is
+    // `pnp`, resolution is virtual, and there is no dual-`@vitest/runner` — so writing
+    // `installConfig.hoistingLimits: none` would be a spurious mutation. Skip it.
+    const savedLinker = process.env.YARN_NODE_LINKER;
+    delete process.env.YARN_NODE_LINKER;
+    try {
+      fs.writeFileSync(
+        path.join(tmpDir, 'package.json'),
+        JSON.stringify({ name: 'root', private: true, workspaces: ['packages/*'] }),
+      );
+      // nmHoistingLimits set, but nodeLinker unset -> effective linker is pnp.
+      fs.writeFileSync(path.join(tmpDir, '.yarnrc.yml'), 'nmHoistingLimits: workspaces\n');
+      const libDir = path.join(tmpDir, 'packages', 'lib');
+      fs.mkdirSync(libDir, { recursive: true });
+      fs.writeFileSync(
+        path.join(libDir, 'package.json'),
+        JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+      );
+
+      rewriteMonorepoProject(libDir, PackageManager.yarn, true, true);
+
+      const libPkg = readJson(path.join(libDir, 'package.json')) as {
+        devDependencies?: Record<string, string>;
+        installConfig?: unknown;
+      };
+      expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+      expect(libPkg.installConfig).toBeUndefined();
+    } finally {
+      if (savedLinker === undefined) {
+        delete process.env.YARN_NODE_LINKER;
+      } else {
+        process.env.YARN_NODE_LINKER = savedLinker;
+      }
+    }
+  });
+
+  it('honours YARN_NM_HOISTING_LIMITS=workspaces from the environment (highest precedence)', () => {
+    // Yarn lets `YARN_<KEY>` env vars override `.yarnrc.yml` (verified with Yarn 4.17).
+    // A repo whose rc omits `nmHoistingLimits` but runs under
+    // `YARN_NM_HOISTING_LIMITS=workspaces` is still isolated, so the workspace must be
+    // auto-fixed.
+    const savedLimit = process.env.YARN_NM_HOISTING_LIMITS;
+    process.env.YARN_NM_HOISTING_LIMITS = 'workspaces';
+    try {
+      fs.writeFileSync(
+        path.join(tmpDir, 'package.json'),
+        JSON.stringify({ name: 'root', private: true, workspaces: ['packages/*'] }),
+      );
+      // node-modules linker, but no rc nmHoistingLimits — the env supplies it.
+      fs.writeFileSync(path.join(tmpDir, '.yarnrc.yml'), 'nodeLinker: node-modules\n');
+      const libDir = path.join(tmpDir, 'packages', 'lib');
+      fs.mkdirSync(libDir, { recursive: true });
+      fs.writeFileSync(
+        path.join(libDir, 'package.json'),
+        JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+      );
+
+      rewriteMonorepoProject(libDir, PackageManager.yarn, true, true);
+
+      const libPkg = readJson(path.join(libDir, 'package.json')) as {
+        installConfig?: { hoistingLimits?: string };
+      };
+      expect(libPkg.installConfig?.hoistingLimits).toBe('none');
+    } finally {
+      if (savedLimit === undefined) {
+        delete process.env.YARN_NM_HOISTING_LIMITS;
+      } else {
+        process.env.YARN_NM_HOISTING_LIMITS = savedLimit;
+      }
+    }
+  });
+
+  it('lets YARN_NM_HOISTING_LIMITS=none override an in-tree workspaces limit (env wins)', () => {
+    // Env precedence cuts both ways: an env override to `none` makes an in-tree
+    // `workspaces` value non-effective, so the layout already dedupes and the
+    // migration must NOT add a spurious opt-out.
+    const savedLimit = process.env.YARN_NM_HOISTING_LIMITS;
+    process.env.YARN_NM_HOISTING_LIMITS = 'none';
+    try {
+      fs.writeFileSync(
+        path.join(tmpDir, 'package.json'),
+        JSON.stringify({ name: 'root', private: true, workspaces: ['packages/*'] }),
+      );
+      fs.writeFileSync(
+        path.join(tmpDir, '.yarnrc.yml'),
+        'nodeLinker: node-modules\nnmHoistingLimits: workspaces\n',
+      );
+      const libDir = path.join(tmpDir, 'packages', 'lib');
+      fs.mkdirSync(libDir, { recursive: true });
+      fs.writeFileSync(
+        path.join(libDir, 'package.json'),
+        JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+      );
+
+      rewriteMonorepoProject(libDir, PackageManager.yarn, true, true);
+
+      const libPkg = readJson(path.join(libDir, 'package.json')) as {
+        installConfig?: unknown;
+      };
+      expect(libPkg.installConfig).toBeUndefined();
+    } finally {
+      if (savedLimit === undefined) {
+        delete process.env.YARN_NM_HOISTING_LIMITS;
+      } else {
+        process.env.YARN_NM_HOISTING_LIMITS = savedLimit;
+      }
+    }
+  });
+
+  it('honours nodeLinker from the home ~/.yarnrc.yml for a repo OUTSIDE $HOME (devcontainer layout)', () => {
+    // Yarn reads the home `.yarnrc.yml` even when the project is not under $HOME
+    // (verified with Yarn 4.17). Devcontainers/Codespaces mount the repo under
+    // /workspaces while $HOME is /home/<user>, so the node-modules linker can come
+    // from the home rc while the project rc carries `nmHoistingLimits: workspaces`.
+    // The gate must still fire — resolving `nodeLinker` from the home rc — or the
+    // split is silently left behind.
+    const homeDir = path.join(tmpDir, 'home');
+    const projectRoot = path.join(tmpDir, 'workspaces', 'repo');
+    fs.mkdirSync(homeDir, { recursive: true });
+    fs.mkdirSync(projectRoot, { recursive: true });
+    // nodeLinker lives ONLY in the home rc; the project is a sibling of $HOME.
+    fs.writeFileSync(path.join(homeDir, '.yarnrc.yml'), 'nodeLinker: node-modules\n');
+    fs.writeFileSync(
+      path.join(projectRoot, 'package.json'),
+      JSON.stringify({ name: 'root', private: true, workspaces: ['packages/*'] }),
+    );
+    fs.writeFileSync(path.join(projectRoot, '.yarnrc.yml'), 'nmHoistingLimits: workspaces\n');
+    const libDir = path.join(projectRoot, 'packages', 'lib');
+    fs.mkdirSync(libDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(libDir, 'package.json'),
+      JSON.stringify({ name: '@scope/lib', devDependencies: { vitest: '^4.0.0' } }),
+    );
+
+    // Redirect the home dir to our temp home. `os.homedir()` reads `HOME` on POSIX and
+    // `USERPROFILE` on Windows, so set both; the describe-level afterEach restores them.
+    process.env.HOME = homeDir;
+    process.env.USERPROFILE = homeDir;
+    rewriteMonorepoProject(libDir, PackageManager.yarn, true, true);
+
+    const libPkg = readJson(path.join(libDir, 'package.json')) as {
+      devDependencies?: Record<string, string>;
+      installConfig?: { hoistingLimits?: string };
+    };
+    expect(libPkg.devDependencies).toHaveProperty('vite-plus');
+    // node-modules (home rc) + workspaces (project rc) -> the split is real -> opt-out.
+    expect(libPkg.installConfig?.hoistingLimits).toBe('none');
+  });
+
+  it('does not write an edgedriver/geckodriver default-deny in pnpm-workspace.yaml when webdriverio is unused (pnpm v10)', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    // edgedriver/geckodriver only reach the tree via the opt-in webdriverio provider (an
+    // OPTIONAL peer of both vite-plus and vitest, so pnpm never auto-installs it). A
+    // non-webdriverio project never installs them, so there is nothing to manage and
+    // vite-plus writes no allowBuilds block at all.
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds).toBeUndefined();
+  });
+
+  it('allows edgedriver/geckodriver builds when webdriverio is in devDependencies (pnpm v10)', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', webdriverio: '^9.0.0' },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('allows edgedriver/geckodriver builds when only @vitest/browser-webdriverio is declared (pnpm v10)', () => {
+    // The migrator keeps `@vitest/browser-webdriverio` (opt-in provider) and
+    // ensures `webdriverio: '*'` as its runtime peer, so the post-migration
+    // deps will need the driver postinstalls even though the pre-migration
+    // package.json never lists `webdriverio` directly.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: {
+          vite: '^7.0.0',
+          vitest: '^4.0.0',
+          '@vitest/browser-webdriverio': '^4.0.0',
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('allows edgedriver/geckodriver builds when @vitest/browser-webdriverio is declared only in peerDependencies (pnpm v10)', () => {
+    // Same rationale as the devDependencies case: the migrator keeps the
+    // opt-in `@vitest/browser-webdriverio` provider and ensures `webdriverio: '*'`,
+    // so the post-migration deps still need the driver postinstalls. The
+    // allow-signal scan must therefore also look at peerDependencies.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: {
+          vite: '^7.0.0',
+          vitest: '^4.0.0',
+        },
+        peerDependencies: {
+          '@vitest/browser-webdriverio': '^4.0.0',
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('preserves explicit allowBuilds entries and adds nothing else on second run (idempotent)', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      ['allowBuilds:', '  edgedriver: true', ''].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const firstPass = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    // explicit user choice survives; with no webdriverio the missing geckodriver entry is
+    // left absent (vite-plus no longer writes a default deny).
+    expect(firstPass.allowBuilds.edgedriver).toBe(true);
+    expect('geckodriver' in firstPass.allowBuilds).toBe(false);
+
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+    const secondPass = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(secondPass.allowBuilds).toEqual(firstPass.allowBuilds);
+  });
+
+  it('flips a stale edgedriver/geckodriver denial to true when webdriverio is added on a re-migration (pnpm v10)', () => {
+    // A prior WebdriverIO-less migration wrote `allowBuilds.<driver>: false`. The user
+    // then adds webdriverio and re-runs migration: the drivers are now needed, so the
+    // stale `false` MUST be overwritten with `true` — otherwise pnpm keeps the driver
+    // postinstall blocked and `vp test` browser mode breaks.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', webdriverio: '^9.0.0' },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      ['allowBuilds:', '  edgedriver: false', '  geckodriver: false', ''].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('leaves a user-authored driver denial untouched on a re-migration (pnpm v10, no webdriverio)', () => {
+    // The user directly depends on `edgedriver` and has denied its build
+    // (`allowBuilds.edgedriver: false`, e.g. their own Selenium setup, no webdriverio).
+    // vite-plus does not manage these postinstalls when webdriverio is unused, so it must
+    // leave the user's denial — and the unrelated geckodriver entry — exactly as-is
+    // rather than deleting a trust decision it never made.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', edgedriver: '^6.0.0' },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      ['allowBuilds:', '  edgedriver: false', '  geckodriver: false', ''].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(false);
+    expect(yaml.allowBuilds.geckodriver).toBe(false);
+  });
+
+  it('preserves a user-approved (true) direct-driver dep on a re-migration (pnpm-workspace.yaml v10)', () => {
+    // The user depends on `edgedriver` directly AND has already approved its build
+    // (`allowBuilds.edgedriver: true`, e.g. via `pnpm approve-builds`). Re-running
+    // migration (no webdriverio) must PRESERVE that approval untouched — vite-plus does
+    // not manage these postinstalls when webdriverio is unused.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', edgedriver: '^6.0.0' },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      ['allowBuilds:', '  edgedriver: true', '  geckodriver: false', ''].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    // user's approval survives; not-owned geckodriver denial stays
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(false);
+  });
+
+  it('leaves an anchored (&/*) driver denial untouched without crashing (pnpm-workspace.yaml v10, no webdriverio)', () => {
+    // Valid YAML can express the denial through an anchor/alias. With no webdriverio,
+    // vite-plus does not touch allowBuilds, so the anchor (`&deny false`) and its alias
+    // (`*deny`) are both preserved intact — there is no delete that could orphan the
+    // alias and abort serialization with "Unresolved alias".
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', geckodriver: '^4.0.0' },
+      }),
+    );
+    fs.writeFileSync(
+      path.join(tmpDir, 'pnpm-workspace.yaml'),
+      ['allowBuilds:', '  edgedriver: &deny false', '  geckodriver: *deny', ''].join('\n'),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    // both the anchor-owner and the alias resolve to the preserved `false`.
+    expect(yaml.allowBuilds.edgedriver).toBe(false);
+    expect(yaml.allowBuilds.geckodriver).toBe(false);
+  });
+
+  it('preserves a user-approved (true) direct-driver dep on a re-migration (package.json pnpm sink, v10)', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', geckodriver: '^4.0.0' },
+        pnpm: { allowBuilds: { edgedriver: false, geckodriver: true }, overrides: {} },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const pnpm = (readJson(path.join(tmpDir, 'package.json')).pnpm ?? {}) as {
+      allowBuilds?: Record<string, boolean>;
+    };
+    // No webdriverio -> vite-plus leaves the whole allowBuilds map untouched: the
+    // user-approved geckodriver `true` and the user's edgedriver `false` both survive.
+    expect(pnpm.allowBuilds?.geckodriver).toBe(true);
+    expect(pnpm.allowBuilds?.edgedriver).toBe(false);
+  });
+
+  it('flips a stale package.json pnpm.allowBuilds denial to true when webdriverio is added (pnpm v10)', () => {
+    // Same re-migration flip, but for the package.json `pnpm` sink (used when the
+    // pnpm config lives in package.json and there is no pnpm-workspace.yaml).
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', webdriverio: '^9.0.0' },
+        pnpm: { allowBuilds: { edgedriver: false, geckodriver: false }, overrides: {} },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const pnpm = (readJson(path.join(tmpDir, 'package.json')).pnpm ?? {}) as {
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(pnpm.allowBuilds?.edgedriver).toBe(true);
+    expect(pnpm.allowBuilds?.geckodriver).toBe(true);
+  });
+
+  it('does not write a pnpm.allowBuilds default-deny in package.json when webdriverio is unused (pnpm v10)', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0' },
+        pnpm: { overrides: {} },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    // No webdriverio -> nothing to manage -> no allowBuilds key added to the pnpm sink
+    // (the webdriverio-present case still writes `true` here — see the flip test below).
+    const pnpm = (readJson(path.join(tmpDir, 'package.json')).pnpm ?? {}) as {
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(pnpm.allowBuilds).toBeUndefined();
+  });
+
+  it('appends edgedriver/geckodriver to onlyBuiltDependencies on pnpm v9 when webdriverio is used', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', webdriverio: '^9.0.0' },
+      }),
+    );
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm);
+    workspaceInfo.downloadPackageManager.version = '9.15.0';
+    rewriteStandaloneProject(tmpDir, workspaceInfo, true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      onlyBuiltDependencies?: string[];
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(yaml.onlyBuiltDependencies).toEqual(
+      expect.arrayContaining(['edgedriver', 'geckodriver']),
+    );
+    // v10-shape key must not appear on v9 setups
+    expect(yaml.allowBuilds).toBeUndefined();
+  });
+
+  it('leaves onlyBuiltDependencies untouched on pnpm v9 when webdriverio is unused', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm);
+    workspaceInfo.downloadPackageManager.version = '9.15.0';
+    rewriteStandaloneProject(tmpDir, workspaceInfo, true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      onlyBuiltDependencies?: string[];
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(yaml.onlyBuiltDependencies).toBeUndefined();
+    expect(yaml.allowBuilds).toBeUndefined();
+  });
+
+  it('detects webdriverio in a monorepo sub-package and allows builds at the root', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'root', devDependencies: {} }),
+    );
+    fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), 'packages:\n  - apps/*\n');
+    const appDir = path.join(tmpDir, 'apps', 'e2e');
+    fs.mkdirSync(appDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(appDir, 'package.json'),
+      JSON.stringify({
+        name: '@vibe/e2e',
+        devDependencies: { webdriverio: '^9.0.0' },
+      }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@vibe/e2e', path: 'apps/e2e' }];
+    rewriteMonorepo(workspaceInfo, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('does not deny a driver the user depends on directly when webdriverio is unused (pnpm v10)', () => {
+    // Non-webdriverio Selenium setup: the user manages their own edgedriver postinstall
+    // approval. The migration writes no deny — neither for the user-owned edgedriver nor
+    // for the not-owned geckodriver (never installed without webdriverio) — so no
+    // allowBuilds block is written and pnpm keeps the user's own approval/prompt.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', edgedriver: '^6.0.0' },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds).toBeUndefined();
+  });
+
+  it('auto-allows a user direct driver dep when webdriverio is present (pnpm v10)', () => {
+    // The user depends on edgedriver directly AND uses webdriverio (which also
+    // needs the driver built). The webdriverio signal makes builds allowed, so
+    // write `allowBuilds.edgedriver = true` rather than leaving the key absent —
+    // a driver webdriverio needs built must not be left to a pnpm prompt.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: {
+          vite: '^7.0.0',
+          webdriverio: '^9.0.0',
+          edgedriver: '^6.0.0',
+        },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
+  });
+
+  it('writes no driver allowBuilds entries when no driver is a direct dep and webdriverio is unused (regression guard)', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'test', devDependencies: { vite: '^7.0.0' } }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds).toBeUndefined();
+  });
+
+  it('does not deny a driver the user depends on directly when webdriverio is unused (package.json pnpm config)', () => {
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        devDependencies: { vite: '^7.0.0', edgedriver: '^6.0.0' },
+        pnpm: { overrides: {} },
+      }),
+    );
+    rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
+
+    const pkg = readJson(path.join(tmpDir, 'package.json')) as {
+      pnpm?: { allowBuilds?: Record<string, boolean> };
+    };
+    expect(pkg.pnpm?.allowBuilds).toBeUndefined();
+  });
+
+  it('writes no workspace-yaml allowBuilds for a monorepo with a direct driver dep but no webdriverio (pnpm v10)', () => {
+    // A sub-package has its own edgedriver postinstall approval but nothing in the
+    // workspace uses webdriverio. The migration writes no deny for either driver, so the
+    // sub-package's own edgedriver approval is left to pnpm.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'root', devDependencies: {} }),
+    );
+    fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), 'packages:\n  - apps/*\n');
+    const appDir = path.join(tmpDir, 'apps', 'e2e');
+    fs.mkdirSync(appDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(appDir, 'package.json'),
+      JSON.stringify({
+        name: '@vibe/e2e',
+        devDependencies: { edgedriver: '^6.0.0' },
+      }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@vibe/e2e', path: 'apps/e2e' }];
+    rewriteMonorepo(workspaceInfo, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds).toBeUndefined();
+  });
+
+  it('writes no allowBuilds for a monorepo where the ROOT has a direct driver dep but no webdriverio (pnpm v10)', () => {
+    // The workspace root has its own geckodriver postinstall approval but nothing uses
+    // webdriverio. The migration writes no deny for either driver; the root's own
+    // geckodriver approval is left to pnpm. In non-force mode the root pnpm config is
+    // normalized into pnpm-workspace.yaml, so that is the operative allowBuilds sink.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({
+        name: 'root',
+        devDependencies: { geckodriver: '^5.0.0' },
+      }),
+    );
+    fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), 'packages:\n  - apps/*\n');
+    const appDir = path.join(tmpDir, 'apps', 'e2e');
+    fs.mkdirSync(appDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(appDir, 'package.json'),
+      JSON.stringify({ name: '@vibe/e2e', devDependencies: {} }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [{ name: '@vibe/e2e', path: 'apps/e2e' }];
+    rewriteMonorepo(workspaceInfo, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds?: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds).toBeUndefined();
+  });
+
+  it('auto-allows a direct driver dep when another workspace package uses webdriverio (monorepo, pnpm v10)', () => {
+    // Mixed workspace: package A depends on edgedriver directly while package B
+    // uses webdriverio (which also needs edgedriver/geckodriver built). The
+    // allowBuilds sink is workspace-global, so the webdriverio signal must write
+    // `true` for BOTH drivers — including the one a package depends on directly.
+    // Leaving edgedriver absent would force a pnpm prompt for a build webdriverio needs.
+    fs.writeFileSync(
+      path.join(tmpDir, 'package.json'),
+      JSON.stringify({ name: 'root', devDependencies: {} }),
+    );
+    fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), 'packages:\n  - apps/*\n');
+    const driverApp = path.join(tmpDir, 'apps', 'selenium');
+    fs.mkdirSync(driverApp, { recursive: true });
+    fs.writeFileSync(
+      path.join(driverApp, 'package.json'),
+      JSON.stringify({ name: '@vibe/selenium', devDependencies: { edgedriver: '^6.0.0' } }),
+    );
+    const wdioApp = path.join(tmpDir, 'apps', 'wdio');
+    fs.mkdirSync(wdioApp, { recursive: true });
+    fs.writeFileSync(
+      path.join(wdioApp, 'package.json'),
+      JSON.stringify({ name: '@vibe/wdio', devDependencies: { webdriverio: '^9.0.0' } }),
+    );
+
+    const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm);
+    workspaceInfo.isMonorepo = true;
+    workspaceInfo.packages = [
+      { name: '@vibe/selenium', path: 'apps/selenium' },
+      { name: '@vibe/wdio', path: 'apps/wdio' },
+    ];
+    rewriteMonorepo(workspaceInfo, true);
+
+    const yaml = readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
+      allowBuilds: Record<string, boolean>;
+    };
+    expect(yaml.allowBuilds.edgedriver).toBe(true);
+    expect(yaml.allowBuilds.geckodriver).toBe(true);
   });
 });
 
@@ -1726,7 +4237,9 @@ describe('rewriteMonorepo yarn catalog', () => {
     expect(yarnrc.nodeLinker).toBe('node-modules');
     expect(yarnrc.catalogs.vite7.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
     expect(yarnrc.catalogs.vite7.react).toBe('^18.0.0');
-    expect(yarnrc.catalogs.test.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    // vitest is now a managed override key — existing catalog entries are
+    // rewritten to the pinned vp-cli vitest version.
+    expect(yarnrc.catalogs.test.vitest).toBe('4.1.9');
     expect(yarnrc.catalogs.test.oxlint).toBeUndefined();
 
     const pkg = readJson(path.join(tmpDir, 'package.json')) as {
@@ -1735,6 +4248,9 @@ describe('rewriteMonorepo yarn catalog', () => {
     };
     expect(pkg.devDependencies.vite).toBe('catalog:vite7');
     expect(pkg.peerDependencies.vite).toBe('^7.0.0');
+    // vitest peer `catalog:test` is resolved against the pre-rewrite catalog
+    // (which still holds the user's `^4.0.0`). The peer range stays as the
+    // user wrote it; only the catalog file itself is later rewritten.
     expect(pkg.peerDependencies.vitest).toBe('^4.0.0');
   });
 });
@@ -1828,7 +4344,9 @@ describe('rewriteMonorepo bun catalog', () => {
     expect(pkg.workspaces.catalog.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
     expect(pkg.workspaces.catalog['vite-plus']).toBe('latest');
     expect(pkg.catalog.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
-    expect(pkg.catalog.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    // vitest is now a managed override key — pre-existing catalog entries are
+    // rewritten to the pinned vp-cli vitest version.
+    expect(pkg.catalog.vitest).toBe('4.1.9');
     expect(pkg.catalog.tsdown).toBeUndefined();
     expect(pkg.catalog.react).toBe('^19.0.0');
     expect(pkg.catalog['vite-plus']).toBeUndefined();
@@ -1888,11 +4406,16 @@ describe('rewriteMonorepo bun catalog', () => {
     expect(pkg.catalogs.build.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
     expect(pkg.catalogs.build.react).toBe('^19.0.0');
     expect(pkg.catalogs.build.tsdown).toBeUndefined();
-    expect(pkg.catalogs.test.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    // vitest is now a managed override key — existing catalog entries are
+    // rewritten to the pinned version and `overrides.vitest` is injected
+    // as a `catalog:` ref so bun resolves it through the catalog.
+    expect(pkg.catalogs.test.vitest).toBe('4.1.9');
     expect(pkg.overrides.vite).toBe('catalog:build');
     expect(pkg.overrides.vitest).toBe('catalog:');
     expect(pkg.devDependencies.vite).toBe('catalog:build');
     expect(pkg.peerDependencies.vite).toBe('^7.0.0');
+    // vitest peer `catalog:test` is resolved against the pre-rewrite catalog
+    // (which still holds the user's `^4.0.0`). Peer range stays as-is.
     expect(pkg.peerDependencies.vitest).toBe('^4.0.0');
   });
 
@@ -1928,7 +4451,9 @@ describe('rewriteMonorepo bun catalog', () => {
     expect(pkg.workspaces.catalog['vite-plus']).toBe('latest');
     expect(pkg.workspaces.catalogs.build.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
     expect(pkg.workspaces.catalogs.build.oxlint).toBeUndefined();
-    expect(pkg.workspaces.catalogs.test.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
+    // vitest is a managed override key — existing catalog entries are
+    // rewritten to the pinned vp-cli vitest version.
+    expect(pkg.workspaces.catalogs.test.vitest).toBe('4.1.9');
     expect(pkg.workspaces.catalogs.test.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
     expect(pkg.overrides.vite).toBe('catalog:');
   });
diff --git a/packages/cli/src/migration/bin.ts b/packages/cli/src/migration/bin.ts
index eed35af4bf..d9288bb2e8 100644
--- a/packages/cli/src/migration/bin.ts
+++ b/packages/cli/src/migration/bin.ts
@@ -29,6 +29,7 @@ import { hasVitePlusDependency, readNearestPackageJson } from '../utils/package.
 import { displayRelative } from '../utils/path.ts';
 import {
   cancelAndExit,
+  type CommandRunSummary,
   defaultInteractive,
   downloadPackageManager,
   promptGitHooks,
@@ -36,7 +37,7 @@ import {
   selectPackageManager,
   upgradeYarn,
 } from '../utils/prompts.ts';
-import { accent, log, muted, printHeader } from '../utils/terminal.ts';
+import { accent, log, muted, printHeader, warnMsg } from '../utils/terminal.ts';
 import {
   confirmBaseUrlFix,
   fixBaseUrlInTsconfig,
@@ -689,11 +690,49 @@ function formatDuration(durationMs: number) {
   return `${Math.round(durationSeconds)}s`;
 }
 
+/**
+ * Reconcile a CommandRunSummary from `runViteInstall` with the migration's
+ * duration counter and exit-code state. `runViteInstall` returns
+ * `{ status: 'failed', exitCode }` without throwing; treating that as a success
+ * (incrementing duration unconditionally) would let the migration claim
+ * "Dependencies installed" while node_modules is desynced from the just-mutated
+ * package.json. This helper centralizes the right handling: credit duration on
+ * success, warn + flip exitCode on failure, stay silent on skip.
+ */
+function handleInstallResult(
+  installSummary: CommandRunSummary,
+  rootDir: string,
+  report: MigrationReport,
+  // The pre-migration "initial" install is best-effort: the migration proceeds
+  // regardless of its outcome, and a post-migration "final" install runs with
+  // `--force` / `--no-frozen-lockfile` as the authoritative recovery. Only that
+  // final install's failure should flip `process.exitCode` so a successful
+  // recovery yields exit 0; the initial failure is still surfaced via
+  // `report.warnings` + the warn message.
+  options?: { propagateExitCode?: boolean },
+): number {
+  if (installSummary.status === 'installed') {
+    return installSummary.durationMs;
+  }
+  if (installSummary.status === 'failed') {
+    const exitCode = installSummary.exitCode ?? 1;
+    const message = `Dependency installation failed (exit code ${exitCode}). Run \`vp install\` manually in ${rootDir} to resync node_modules.`;
+    warnMsg(message);
+    report.warnings.push(message);
+    if (options?.propagateExitCode !== false) {
+      process.exitCode = exitCode;
+    }
+    return 0;
+  }
+  return 0;
+}
+
 function showMigrationSummary(options: {
   projectRoot: string;
   packageManager: string;
   packageManagerVersion: string;
   installDurationMs: number;
+  finalInstallOk: boolean;
   report: MigrationReport;
   updatedExistingVitePlus?: boolean;
 }) {
@@ -702,6 +741,7 @@ function showMigrationSummary(options: {
     packageManager,
     packageManagerVersion,
     installDurationMs,
+    finalInstallOk,
     report,
     updatedExistingVitePlus,
   } = options;
@@ -723,7 +763,11 @@ function showMigrationSummary(options: {
   log(
     `${styleText('gray', '•')} Node ${process.versions.node}  ${packageManager} ${packageManagerVersion}`,
   );
-  if (installDurationMs > 0) {
+  // Gate the green success line on the FINAL install actually succeeding.
+  // A nonzero duration could come from a successful pre-migration install
+  // followed by a failed post-migration reinstall — in that case node_modules
+  // is desynced and reporting success would mislead the user.
+  if (finalInstallOk && installDurationMs > 0) {
     log(
       `${styleText('green', '✓')} Dependencies installed in ${formatDuration(installDurationMs)}`,
     );
@@ -857,6 +901,7 @@ async function executeMigrationPlan(
   interactive: boolean,
 ): Promise<{
   installDurationMs: number;
+  finalInstallOk: boolean;
   packageManagerVersion: string;
   report: MigrationReport;
 }> {
@@ -1028,12 +1073,14 @@ async function executeMigrationPlan(
   );
 
   // 12. Reinstall after migration
-  // npm needs --force to re-resolve packages with newly added overrides,
-  // otherwise the stale lockfile prevents override resolution.
+  // The migration intentionally rewrites overrides/catalogs/deps, so the
+  // existing lockfile is guaranteed to be stale. Tell each package manager to
+  // re-resolve instead of refusing the install (pnpm/yarn default to
+  // frozen-lockfile under CI, npm/bun need an explicit --force).
   const installArgs =
     plan.packageManager === PackageManager.npm || plan.packageManager === PackageManager.bun
       ? ['--force']
-      : undefined;
+      : ['--no-frozen-lockfile'];
   updateMigrationProgress('Installing dependencies');
   const finalInstallSummary = await runViteInstall(
     workspaceInfo.rootDir,
@@ -1047,8 +1094,24 @@ async function executeMigrationPlan(
   );
 
   clearMigrationProgress();
+  // Process the initial install first so the final install's exit code "wins":
+  // if the initial install failed but the final install succeeded, the
+  // migration should still report success (exit 0). The initial call opts out
+  // of exitCode propagation; only the final call may flip process.exitCode.
+  const initialInstallDurationMs = handleInstallResult(
+    initialInstallSummary,
+    workspaceInfo.rootDir,
+    report,
+    { propagateExitCode: false },
+  );
+  const finalInstallDurationMs = handleInstallResult(
+    finalInstallSummary,
+    workspaceInfo.rootDir,
+    report,
+  );
   return {
-    installDurationMs: initialInstallSummary.durationMs + finalInstallSummary.durationMs,
+    installDurationMs: initialInstallDurationMs + finalInstallDurationMs,
+    finalInstallOk: finalInstallSummary.status === 'installed',
     packageManagerVersion: downloadResult.version,
     report,
   };
@@ -1076,6 +1139,7 @@ async function main() {
   if (hasVitePlusDependency(rootPkg) && !isForceOverrideMode()) {
     let didMigrate = false;
     let installDurationMs = 0;
+    let finalInstallOk = true;
     const report = createMigrationReport();
     const migrationProgress = options.interactive
       ? prompts.spinner({ indicator: 'timer' })
@@ -1191,7 +1255,6 @@ async function main() {
     );
 
     let needsInstall = false;
-    let forceInstall = false;
     if (vitePlusBootstrapPending) {
       const downloadResult = await ensureExistingPackageManager();
       if (downloadResult && packageManager) {
@@ -1206,9 +1269,6 @@ async function main() {
         );
         didMigrate = bootstrapResult.changed || didMigrate;
         needsInstall = bootstrapResult.changed || needsInstall;
-        forceInstall =
-          bootstrapResult.changed &&
-          (packageManager === PackageManager.npm || packageManager === PackageManager.bun);
       }
     }
 
@@ -1335,21 +1395,33 @@ async function main() {
       const installSummary = await runViteInstall(
         workspaceInfoOptional.rootDir,
         options.interactive,
-        forceInstall ? ['--force'] : undefined,
+        // Migration steps rewrote package.json/config, so the lockfile is now
+        // stale; tell each package manager to re-resolve instead of refusing
+        // (pnpm/yarn default to frozen-lockfile under CI, npm/bun need --force).
+        packageManager === PackageManager.npm || packageManager === PackageManager.bun
+          ? ['--force']
+          : ['--no-frozen-lockfile'],
         {
           silent: true,
           packageManager,
           packageManagerVersion: resolvedVersion,
         },
       );
-      installDurationMs += installSummary.durationMs;
+      // Route the install result through the shared helper (mirrors the full
+      // migration path and is enforced by install-failure-guard.spec): a failed
+      // install warns, appends to report.warnings, and flips process.exitCode
+      // rather than being silently credited as a successful migration. Clear the
+      // spinner first only on failure so the warning isn't interleaved with it;
+      // on success handleInstallResult returns durationMs, so the credited
+      // duration is unchanged.
       if (installSummary.status === 'failed') {
         clearMigrationProgress();
-        cancelAndExit(
-          'Dependency installation failed. Run `vp install` manually and re-run `vp migrate`.',
-          1,
-        );
       }
+      installDurationMs += handleInstallResult(
+        installSummary,
+        workspaceInfoOptional.rootDir,
+        report,
+      );
     }
 
     if (plan.selectedAgentTargetPaths && plan.selectedAgentTargetPaths.length > 0) {
@@ -1394,6 +1466,7 @@ async function main() {
         packageManager: packageManager ?? resolvedPackageManager,
         packageManagerVersion,
         installDurationMs,
+        finalInstallOk,
         report,
         updatedExistingVitePlus: !fullMigrationSummary,
       });
@@ -1418,6 +1491,7 @@ async function main() {
     packageManager: plan.packageManager,
     packageManagerVersion: result.packageManagerVersion,
     installDurationMs: result.installDurationMs,
+    finalInstallOk: result.finalInstallOk,
     report: result.report,
   });
 }
diff --git a/packages/cli/src/migration/migrator.ts b/packages/cli/src/migration/migrator.ts
index e6950b80da..41bf1bcc79 100644
--- a/packages/cli/src/migration/migrator.ts
+++ b/packages/cli/src/migration/migrator.ts
@@ -1,4 +1,5 @@
 import fs from 'node:fs';
+import os from 'node:os';
 import path from 'node:path';
 import { styleText } from 'node:util';
 
@@ -30,6 +31,8 @@ import {
   VITE_PLUS_NAME,
   VITE_PLUS_OVERRIDE_PACKAGES,
   VITE_PLUS_VERSION,
+  VITEST_AGE_GATE_EXEMPT_PACKAGES,
+  VITEST_VERSION,
   isForceOverrideMode,
 } from '../utils/constants.ts';
 import { editJsonFile, isJsonFile, readJsonFile } from '../utils/json.ts';
@@ -86,10 +89,359 @@ const REMOVE_PACKAGES = [
   'tsdown',
   '@vitest/browser',
   '@vitest/browser-preview',
-  '@vitest/browser-playwright',
-  '@vitest/browser-webdriverio',
 ] as const;
 
+// The opt-in browser providers. Unlike `@vitest/browser`/preview these are NOT
+// bundled by vite-plus or stripped from users (so they stay out of
+// REMOVE_PACKAGES); each drags a heavy non-optional framework peer
+// (`playwright` / `webdriverio`) that non-browser consumers must not be forced
+// to install. The migration keeps a provider the user actually targets in their
+// own deps, pinned to the bundled vitest version.
+const WEBDRIVERIO_PROVIDER = '@vitest/browser-webdriverio';
+const PLAYWRIGHT_PROVIDER = '@vitest/browser-playwright';
+
+// All opt-in browser providers handled identically by the migration: kept in
+// the user's deps (pinned to the bundled vitest), framework peer ensured, stale
+// forcing pins dropped, while their catalog entries are PRESERVED.
+const OPT_IN_BROWSER_PROVIDERS = [WEBDRIVERIO_PROVIDER, PLAYWRIGHT_PROVIDER] as const;
+
+// Provider names whose stale pnpm overrides / resolutions are dropped during
+// migration: everything vite-plus owns (REMOVE_PACKAGES) plus the user-owned
+// opt-in providers. The provider DEP is preserved, but a leftover
+// override/resolution pin to another version would WIN over the direct dep and
+// misalign the provider against the bundled vitest — so the stale forcing pin is
+// dropped while the dependency itself stays installed. NOTE: catalog deletion
+// uses REMOVE_PACKAGES (not this set) on purpose — a catalog entry is only a
+// version *definition*, and deleting it could dangle a surviving `catalog:`
+// reference (e.g. in peerDependencies) and break install.
+const PROVIDER_OVERRIDE_DROP_NAMES = [...REMOVE_PACKAGES, ...OPT_IN_BROWSER_PROVIDERS] as const;
+
+// Extract the package name an override/resolution key *targets* — i.e. the
+// package whose version would be forced. This mirrors the grammar of the real
+// package-manager parsers (verified against `@yarnpkg/parsers` parseResolution):
+//   - bare (`pkg`, `@scope/pkg`)
+//   - versioned (`pkg@1`, `@scope/pkg@1`)
+//   - pnpm parent selectors (`parent>pkg`, chained `a@1>b>@scope/pkg`)
+//   - yarn `from/target` selectors (`parent/pkg`, `parent/@scope/pkg`,
+//     `parent@1/pkg`, glob `**/pkg`)
+// For a yarn `from/target` selector the forced package is the TRAILING
+// descriptor, not the parent: `@scope/pkg@4/child` targets `child`, and an
+// npm-alias key like `@scope/pkg@npm:@other/fork@1` is parsed by yarn as
+// `from=@scope/pkg@npm:@other`, `descriptor=fork@1` — so the target is `fork`,
+// NOT `@scope/pkg`. Taking the trailing descriptor is exactly that. (Yarn
+// *rejects* keys whose range embeds a slash, e.g. `pkg@patch:…/…` or git/URL
+// ranges, so those never reach us as valid keys and need no special handling.)
+// Scoped names keep their leading `@` and internal `/`.
+function extractOverrideTargetName(key: string): string {
+  // pnpm parent selector `parent>child` (incl. chains `a>b>child`): the forced
+  // package is the deepest child. pnpm splits at a `>` whose preceding char is
+  // NOT space, `|`, or `@` — this is pnpm's own delimiter rule (DELIMITER_REGEX
+  // = /[^ |@]>/ in @pnpm/parse-overrides) — so a semver comparator range such as
+  // `pkg@>=4`, `pkg@>4`, or `>1 || >2` is NOT mistaken for a parent selector.
+  // Peel parent levels until none remain, keeping the trailing child.
+  let target = key.trim();
+  for (let delim = target.search(/[^ |@]>/); delim !== -1; delim = target.search(/[^ |@]>/)) {
+    target = target.slice(delim + 2).trim();
+  }
+  if (!target) {
+    return target;
+  }
+  // yarn `from/target` selector: drop leading parent/glob segments, keeping the
+  // trailing package descriptor (and a scoped name's own `/`).
+  if (target.includes('/')) {
+    const segments = target.split('/');
+    const last = segments[segments.length - 1];
+    const scope = segments[segments.length - 2];
+    target = scope?.startsWith('@') ? `${scope}/${last}` : last;
+  }
+  // Strip a trailing version/range suffix. The version `@` follows the name
+  // (after the `/` for a scoped name); the leading scope `@` is never a version
+  // separator.
+  const nameStart = target.startsWith('@') ? target.indexOf('/') + 1 : 0;
+  const versionAt = target.indexOf('@', nameStart);
+  if (versionAt > 0) {
+    target = target.slice(0, versionAt);
+  }
+  return target;
+}
+
+// True iff a pnpm.overrides key's target (after stripping selector and
+// version suffixes) is a provider whose stale pin must be dropped (see
+// PROVIDER_OVERRIDE_DROP_NAMES). Shared by the JSON-object and YAMLMap
+// variants below.
+function isRemovePackageOverrideKey(key: string): boolean {
+  return (PROVIDER_OVERRIDE_DROP_NAMES as readonly string[]).includes(
+    extractOverrideTargetName(key),
+  );
+}
+
+// Strip a trailing `@version`/range from a selector segment and keep its scope.
+// Mirrors the version-suffix peeling in `extractOverrideTargetName`: the version
+// `@` follows the name (after the `/` of a scoped name); the leading scope `@`
+// is never a version separator.
+function stripSegmentVersion(segment: string): string {
+  const nameStart = segment.startsWith('@') ? segment.indexOf('/') + 1 : 0;
+  const versionAt = segment.indexOf('@', nameStart);
+  return versionAt > 0 ? segment.slice(0, versionAt) : segment;
+}
+
+// True iff a single parent-NAME glob segment matches the given literal package
+// name. `*` matches any run of characters; all other glob/regex metacharacters
+// are escaped. Used for the concrete ancestor segments of a selector.
+function parentGlobMatchesName(glob: string, name: string): boolean {
+  const pattern = glob
+    .split('*')
+    .map((part) => part.replace(/[.+?^${}()|[\]\\]/g, '\\$&'))
+    .join('.*');
+  return new RegExp(`^${pattern}$`).test(name);
+}
+
+// True iff an ancestor segment (literal or glob) matches the given package name.
+function ancestorSegmentMatches(segment: string, name: string): boolean {
+  return segment.includes('*') ? parentGlobMatchesName(segment, name) : segment === name;
+}
+
+// Provider names that sit on vite-plus's OWN dependency path and can therefore
+// appear as ANCESTORS of a pin that still constrains vite-plus's provider
+// subtree: pnpm/yarn parent selectors are not root-anchored, so a chain like
+// `@vitest/browser-preview>@vitest/browser` forces the provider's child
+// everywhere that provider appears — including under vite-plus's own direct
+// provider dep. Only the vite-plus-supplied `@vitest/browser*` members of
+// REMOVE_PACKAGES qualify; the user-owned opt-in providers (webdriverio,
+// playwright) are deliberately NOT included — vite-plus no longer ships them, so
+// a `@vitest/browser-playwright>…` chain constrains the user's own provider
+// subtree, not vite-plus's (see the ACCEPTED EDGE note below).
+const OWNED_PROVIDER_ANCESTOR_NAMES = (REMOVE_PACKAGES as readonly string[]).filter((name) =>
+  name.startsWith('@vitest/'),
+);
+
+// True iff a selector's PARENT chain reaches vite-plus's OWN direct provider dep.
+// The subtree migration protects is `<root> → vite-plus → @vitest/provider → …`;
+// since vite-plus is a direct dependency of the project, a parent chain reaches
+// that subtree iff it glob-matches a path along it:
+//   - `**` segments match zero-or-more ancestors, so they are ignored here;
+//   - the FIRST remaining concrete ancestor may glob-match `vite-plus`
+//     (`vite-plus`, `vite-*`, `*`);
+//   - every OTHER concrete ancestor must glob-match a vite-plus-owned provider
+//     (`@vitest/browser*`), because un-anchored selectors such as
+//     `@vitest/browser-playwright>@vitest/browser` still constrain the
+//     provider's children under vite-plus.
+// Any chain carrying a SPECIFIC unrelated ancestor (`some-parent>vite-plus`,
+// `some-parent/**`, `some-parent/vite-*`, `some-app>@vitest/browser-playwright`)
+// constrains a different subtree and does NOT touch the root vite-plus provider,
+// so it is preserved. A chain of only `**` (`**`, `**/**`) is global and matches.
+function parentChainReachesVitePlus(segments: string[]): boolean {
+  const concrete = segments.filter((segment) => segment !== '**');
+  let index = 0;
+  if (concrete.length > 0 && ancestorSegmentMatches(concrete[0], VITE_PLUS_NAME)) {
+    index = 1;
+  }
+  for (; index < concrete.length; index += 1) {
+    const segment = concrete[index];
+    if (!OWNED_PROVIDER_ANCESTOR_NAMES.some((name) => ancestorSegmentMatches(segment, name))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Extract the ordered PARENT chain of an override/resolution key — the ancestor
+// segments above the forced TARGET — or `null` when the key has no parent
+// selector (a bare/versioned global pin). Each segment's own `@version`/range is
+// stripped and scoped names (`@scope/name`) are kept whole; glob segments (`**`,
+// `vite-*`) are preserved verbatim for `parentChainReachesVitePlus`.
+//
+// Mirrors `extractOverrideTargetName`'s grammar so target and parent stay
+// consistent (see that function for the full delimiter rationale):
+//   - pnpm `a>b>child`: every `>`-separated prefix is a parent level (`a`, `b`);
+//     pnpm has no globs, so a chain of length > 1 always carries a specific
+//     ancestor.
+//   - yarn `from/descriptor`: the descriptor is the trailing 1 (unscoped) or 2
+//     (scoped) segments; the remaining leading `/`-segments are the `from` chain,
+//     with scoped ancestors (`@scope/name`) rejoined.
+//   - bare/versioned names (`pkg`, `@scope/pkg`, `pkg@4`) have NO parent → `null`.
+function extractOverrideParentSegments(key: string): string[] | null {
+  let rest = key.trim();
+  // Peel every pnpm `>` parent level. pnpm splits at a `>` whose preceding char
+  // is NOT space, `|`, or `@` (its DELIMITER_REGEX), so semver comparators like
+  // `pkg@>=4` are not mistaken for a parent selector.
+  const pnpmParents: string[] = [];
+  for (let delim = rest.search(/[^ |@]>/); delim !== -1; delim = rest.search(/[^ |@]>/)) {
+    pnpmParents.push(stripSegmentVersion(rest.slice(0, delim + 1).trim()));
+    rest = rest.slice(delim + 2).trim();
+  }
+  if (pnpmParents.length > 0) {
+    return pnpmParents;
+  }
+  // No pnpm parent — check for a yarn `from/descriptor` selector. `rest` is the
+  // child (target) descriptor; only a `/` beyond a single scoped name leaves a
+  // leading `from` (parent) chain.
+  if (!rest.includes('/')) {
+    return null;
+  }
+  const segments = rest.split('/');
+  // The trailing descriptor occupies the last 2 segments when it is a scoped
+  // name (second-to-last segment starts with `@`), else the last 1.
+  const descriptorIsScoped = segments[segments.length - 2]?.startsWith('@') ?? false;
+  const descriptorSegmentCount = descriptorIsScoped ? 2 : 1;
+  const rawParents = segments.slice(0, segments.length - descriptorSegmentCount);
+  if (rawParents.length === 0) {
+    // The whole key was a bare scoped name (`@scope/pkg`) — no parent selector.
+    return null;
+  }
+  // Rejoin scoped ancestors (`@scope` + `name`) and strip each segment's version.
+  const parents: string[] = [];
+  for (let i = 0; i < rawParents.length; i += 1) {
+    const segment = rawParents[i];
+    if (segment.startsWith('@') && i + 1 < rawParents.length) {
+      parents.push(stripSegmentVersion(`${segment}/${rawParents[i + 1]}`));
+      i += 1;
+    } else {
+      parents.push(stripSegmentVersion(segment));
+    }
+  }
+  return parents;
+}
+
+// True iff a provider override/resolution key (target ∈
+// PROVIDER_OVERRIDE_DROP_NAMES) should be dropped because the pin would affect
+// vite-plus's OWN direct provider dep. The pin reaches that dep iff its parent
+// selector is:
+//   1. ABSENT — bare/versioned global pin (`@vitest/browser-playwright`,
+//      `@vitest/browser-playwright@4`).
+//   2. a chain that glob-matches a path along the vite-plus provider subtree: a
+//      pure glob (`**/...`, `*/...`), a name glob matching vite-plus
+//      (`vite-*/...`), the literal `vite-plus` (`vite-plus>...`, `vite-plus/...`),
+//      `**`-padded variants (`**/vite-plus/...`), or a chain whose remaining
+//      ancestors are vite-plus-owned providers — un-anchored selectors such as
+//      `@vitest/browser-preview>@vitest/browser` or nested npm
+//      `{ "@vitest/browser-preview": { "@vitest/browser": … } }` still force
+//      the provider's children under vite-plus. See
+//      `parentChainReachesVitePlus`.
+// A selector carrying a SPECIFIC unrelated ancestor anywhere in its chain
+// (`some-app>@vitest/...`, `some-parent/@vitest/...`, `a>vite-plus>@vitest/...`,
+// `some-parent/**/@vitest/...`, `some-parent/vite-*/@vitest/...`) or a mere
+// wildcard RANGE on a specific parent (`parent@*/...`) only constrains that
+// parent's subtree and is preserved. The parent chain comes from the KEY STRING
+// for flat pnpm/yarn selectors; for npm/bun NESTED objects it is accumulated from
+// the enclosing keys by `dropRemovePackageOverrideKeys` and passed in via
+// `ancestorChain`, so a nested `{ a: { vite-plus: { provider } } }` is treated
+// exactly like the flat `a>vite-plus>provider` (both preserved).
+//
+// ACCEPTED EDGE: reachability is judged from `vite-plus` only. A pnpm selector
+// whose parent is the project's OWN (root/workspace) package name — which keeps
+// an opt-in provider as a direct dep after migration, e.g.
+// `my-app>@vitest/browser-webdriverio` or `my-app>@vitest/browser-playwright` —
+// is therefore preserved even though it could re-pin that direct dep. Likewise a
+// chain parented by an opt-in provider itself (`@vitest/browser-playwright>…`)
+// constrains the USER's provider subtree, not vite-plus's, so it is preserved
+// (the opt-in providers are excluded from OWNED_PROVIDER_ANCESTOR_NAMES).
+// Dropping these would require threading importer names through this pass; per
+// PR #1588 this is left as a known, visible (the pin stays in the manifest)
+// limitation rather than risk over-deleting genuinely unrelated transitive
+// selectors (the behavior the posted P2 review asked us to keep).
+function providerKeyReachesVitePlus(key: string, ancestorChain: string[]): boolean {
+  if (!isRemovePackageOverrideKey(key)) {
+    return false;
+  }
+  const keyParents = extractOverrideParentSegments(key) ?? [];
+  return parentChainReachesVitePlus([...ancestorChain, ...keyParents]);
+}
+
+// Flat-selector entry point (no enclosing object nesting): used by the
+// pnpm-workspace YAML sweep, where each key carries its whole parent chain.
+function shouldDropProviderOverrideKey(key: string): boolean {
+  return providerKeyReachesVitePlus(key, []);
+}
+
+// The ancestor segments a key contributes when the recursion descends into its
+// object value: the key's own embedded selector parents followed by its target
+// package name (version-stripped). For a plain npm/bun nested key (`a`) this is
+// just `[a]`, so the accumulated chain mirrors a flat pnpm/yarn parent chain.
+function childChainContribution(key: string): string[] {
+  const parents = extractOverrideParentSegments(key) ?? [];
+  return [...parents, extractOverrideTargetName(key)];
+}
+
+// Drop override keys whose target is a drop-listed provider AND whose pin would
+// reach vite-plus's OWN direct provider dep — the edge `<root> → vite-plus →
+// @vitest/provider`. Covers bare, versioned, global-glob and `vite-plus`-parent
+// shapes that exact-key matching would miss. A pin scoped under a SPECIFIC
+// non-vite-plus parent (pnpm `some-app>@vitest/...`, yarn `some-parent/@vitest/...`,
+// or the npm/bun nested `{ "some-pkg": { "@vitest/...": "x" } }`) only constrains
+// that parent's subtree and is PRESERVED.
+//
+// The decision is uniform across sinks: a provider pin is dropped iff its FULL
+// ancestor chain reaches the root vite-plus edge (see `parentChainReachesVitePlus`).
+// For flat pnpm/yarn selectors the whole chain lives in the KEY STRING; for npm/bun
+// nested objects it is accumulated here from the enclosing object keys
+// (`ancestorChain`) — so `{ "a": { "vite-plus": { provider } } }` is treated like
+// the flat `a>vite-plus>provider` (both PRESERVED: vite-plus sits under `a`, not at
+// the root). A long-form provider override (`{ "@vitest/browser-playwright": { ".":
+// "x", "other": "y" } }`) has its own version pin (`.`) dropped while unrelated
+// children (`other`) are kept. A parent we EMPTY by dropping its last pin is pruned
+// so no meaningless `{}` is left; user-authored empties and untouched maps are kept.
+// (pnpm/yarn override values are flat strings, so the recursion is inert for those
+// sinks.) Returns whether any key/pin was removed.
+function dropRemovePackageOverrideKeys(
+  overrides: Record<string, unknown> | undefined,
+  ancestorChain: string[] = [],
+): boolean {
+  if (!overrides) {
+    return false;
+  }
+  let removed = false;
+  for (const key of Object.keys(overrides)) {
+    const value = overrides[key];
+    const child =
+      value !== null && typeof value === 'object' && !Array.isArray(value)
+        ? (value as Record<string, unknown>)
+        : undefined;
+    if (providerKeyReachesVitePlus(key, ancestorChain)) {
+      if (child) {
+        // Long-form provider override: drop the provider's own version pin (`.`)
+        // but keep any unrelated child overrides scoped under it; still descend
+        // (with the provider appended to the chain) for any deeper root pin.
+        let changed = false;
+        if ('.' in child) {
+          delete child['.'];
+          changed = true;
+        }
+        if (
+          dropRemovePackageOverrideKeys(child, [...ancestorChain, ...childChainContribution(key)])
+        ) {
+          changed = true;
+        }
+        if (Object.keys(child).length === 0) {
+          delete overrides[key];
+          changed = true;
+        }
+        if (changed) {
+          removed = true;
+        }
+      } else {
+        delete overrides[key];
+        removed = true;
+      }
+      continue;
+    }
+    if (child) {
+      // Not a root-vite-plus provider pin here: descend with the chain extended by
+      // this key so a deeper pin sees its full ancestor path; prune the parent only
+      // if the descent emptied it.
+      if (
+        dropRemovePackageOverrideKeys(child, [...ancestorChain, ...childChainContribution(key)])
+      ) {
+        removed = true;
+        if (Object.keys(child).length === 0) {
+          delete overrides[key];
+        }
+      }
+    }
+  }
+  return removed;
+}
+
 // When a browser provider package is removed, its runtime peer dependency
 // must be preserved in devDependencies so browser tests continue to work.
 const BROWSER_PROVIDER_PEER_DEPS: Record<string, string> = {
@@ -97,6 +449,35 @@ const BROWSER_PROVIDER_PEER_DEPS: Record<string, string> = {
   '@vitest/browser-webdriverio': 'webdriverio',
 };
 
+// Transitive packages with postinstall scripts that vite-plus's deps drag in
+// via `@vitest/browser-webdriverio` → `webdriverio` → `@wdio/utils`. pnpm v10
+// refuses to run these without explicit approval, so `vp migrate` records the
+// allow/deny decision up front: deny by default (the user isn't using
+// webdriverio), allow when the user actually depends on webdriverio.
+const BROWSER_PROVIDER_POSTINSTALL_PACKAGES = ['edgedriver', 'geckodriver'] as const;
+
+// Webdriverio is the runtime peer that drags `edgedriver` / `geckodriver` in.
+const WEBDRIVERIO_PEER_DEP = 'webdriverio';
+
+// Dependencies whose presence before migration signals the user will end up
+// with webdriverio after migration. `@vitest/browser-webdriverio` is the opt-in
+// provider vite-plus keeps in the user's deps (pinned to the bundled vitest)
+// and `webdriverio` is its runtime peer (added via `BROWSER_PROVIDER_PEER_DEPS`);
+// either one means the edgedriver/geckodriver postinstalls must be allowed.
+const WEBDRIVERIO_ALLOW_SIGNAL_DEPS = [WEBDRIVERIO_PEER_DEP, WEBDRIVERIO_PROVIDER] as const;
+
+// Browser-provider package names that, when present in the user's deps
+// before migration, signal vitest browser mode even if no source file
+// imports them. This covers config-only browser-mode setups (e.g.
+// `test.browser.provider: 'playwright'` in `vite.config.ts`) where the
+// provider package is declared in `devDependencies` but never `import`ed.
+const VITEST_BROWSER_DEP_NAMES = [
+  '@vitest/browser',
+  '@vitest/browser-preview',
+  '@vitest/browser-playwright',
+  '@vitest/browser-webdriverio',
+] as const;
+
 const PUBLIC_PEER_DEPENDENCY_FALLBACKS: Record<string, string> = {
   vite: '*',
   vitest: '*',
@@ -125,6 +506,75 @@ const OXLINT_NATIVE_PLUGINS = new Set<string>([
   'vue',
 ]);
 
+// Legacy wrapper package names that may appear as the target of override
+// aliases left over from earlier vite-plus migrations. `@voidzero-dev/vite-plus-test`
+// was deleted; any catalog/override entry still pointing at it is stale.
+const LEGACY_WRAPPER_PACKAGE_NAMES = ['@voidzero-dev/vite-plus-test'] as const;
+
+// Fallback specs used when normalizing a stale wrapper alias. Real user
+// ranges (e.g. `vitest: ^3.0.0`) are preserved — only the wrapper alias is
+// rewritten. For `vitest`, we substitute the vitest version vite-plus
+// bundles so any `catalog:` reference the user still has resolves cleanly.
+const LEGACY_WRAPPER_FALLBACK_VERSIONS: Record<string, string> = {
+  vitest: VITEST_VERSION,
+};
+
+function isLegacyWrapperSpec(value: unknown): boolean {
+  // A wrapper spec is always a flat string range; npm/bun `overrides` may hold
+  // nested object values, which can never themselves be a wrapper alias (the
+  // recursion in `pruneLegacyWrapperAliases` descends into those).
+  if (typeof value !== 'string' || !value) {
+    return false;
+  }
+  for (const name of LEGACY_WRAPPER_PACKAGE_NAMES) {
+    if (value === `npm:${name}` || value.startsWith(`npm:${name}@`)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * Rewrite or remove keys whose value points at a deleted vite-plus wrapper.
+ * When a fallback exists for the key (e.g. `vitest`), the value is replaced
+ * so existing `catalog:` references continue to resolve. Otherwise the key
+ * is dropped entirely. Returns true iff any entry was changed.
+ *
+ * npm/bun `overrides` may nest an object of scoped overrides under a parent
+ * key (e.g. `{ "some-parent": { "vitest": "npm:@voidzero-dev/vite-plus-test@latest" } }`),
+ * so object values are recursed into; a parent emptied by pruning is dropped so
+ * no `{}` is left behind. Flat maps (pnpm `overrides`, yarn `resolutions`,
+ * catalogs) hold only string values, where the recursion is inert.
+ */
+function pruneLegacyWrapperAliases(record: Record<string, unknown> | undefined): boolean {
+  if (!record) {
+    return false;
+  }
+  let mutated = false;
+  for (const key of Object.keys(record)) {
+    const value = record[key];
+    if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
+      if (pruneLegacyWrapperAliases(value as Record<string, unknown>)) {
+        mutated = true;
+        if (Object.keys(value as Record<string, unknown>).length === 0) {
+          delete record[key];
+        }
+      }
+      continue;
+    }
+    if (isLegacyWrapperSpec(value)) {
+      const fallback = LEGACY_WRAPPER_FALLBACK_VERSIONS[key];
+      if (fallback !== undefined) {
+        record[key] = fallback;
+      } else {
+        delete record[key];
+      }
+      mutated = true;
+    }
+  }
+  return mutated;
+}
+
 type PackageJsonDependencyField =
   | 'devDependencies'
   | 'dependencies'
@@ -1032,10 +1482,12 @@ export function rewriteStandaloneProject(
 
   const packageManager = workspaceInfo.packageManager;
   const catalogDependencyResolver = createCatalogDependencyResolver(projectPath, packageManager);
+  const pnpmMajorVersion = pnpmMajor(workspaceInfo.downloadPackageManager.version);
   let extractedStagedConfig: Record<string, string | string[]> | null = null;
   let remainingPnpmOverrides: Record<string, string> | undefined;
   let shouldRewritePnpmWorkspaceYaml = false;
   let shouldAddPnpmWorkspaceVitePlusOverride = false;
+  let shouldAllowBrowserProviderBuilds = false;
   // Determined inside editJsonFile callback to avoid a redundant file read
   let usePnpmWorkspaceYaml = false;
   editJsonFile<{
@@ -1052,8 +1504,26 @@ export function rewriteStandaloneProject(
         allowAny?: string[];
         allowedVersions?: Record<string, string>;
       };
+      allowBuilds?: Record<string, boolean>;
+      onlyBuiltDependencies?: string[];
     };
   }>(packageJsonPath, (pkg) => {
+    shouldAllowBrowserProviderBuilds =
+      hasOwnWebdriverioDependency(pkg) || usesWebdriverioProvider(projectPath);
+    // Strip stale `vite-plus-test` wrapper aliases before injecting new overrides
+    // so the deleted wrapper doesn't survive migration in any sink.
+    pruneLegacyWrapperAliases(pkg.resolutions);
+    pruneLegacyWrapperAliases(pkg.overrides);
+    pruneLegacyWrapperAliases(pkg.pnpm?.overrides);
+    // Drop stale provider overrides/resolutions (REMOVE_PACKAGES + the now
+    // user-owned opt-in providers, webdriverio/playwright) from the npm/bun
+    // `overrides` and yarn `resolutions` sinks before re-merging managed
+    // overrides. A leftover pin would conflict with the migrated direct
+    // `@vitest/browser-webdriverio` / `@vitest/browser-playwright` dep — npm
+    // hard-fails with EOVERRIDE, and yarn/bun would force the stale version over
+    // the bundled-vitest-aligned 4.1.9. (The pnpm sinks are pruned below.)
+    dropRemovePackageOverrideKeys(pkg.resolutions);
+    dropRemovePackageOverrideKeys(pkg.overrides);
     if (packageManager === PackageManager.yarn) {
       pkg.resolutions = {
         ...pkg.resolutions,
@@ -1064,6 +1534,19 @@ export function rewriteStandaloneProject(
         ...pkg.overrides,
         ...VITE_PLUS_OVERRIDE_PACKAGES,
       };
+      if (packageManager === PackageManager.bun) {
+        // Bun walks transitive peer-deps before resolving overrides; vitest
+        // 4.1.9 declares peer `vite ^6 || ^7 || ^8` and aborts with
+        // "vite@... failed to resolve" if `vite` isn't a direct dep somewhere
+        // in the tree, even when the override would redirect it. Mirror the
+        // override as a devDep so bun's resolver sees `vite` immediately;
+        // the override above still points it at vite-plus-core.
+        // See https://github.com/oven-sh/bun/issues/8406.
+        pkg.devDependencies = {
+          ...pkg.devDependencies,
+          vite: VITE_PLUS_OVERRIDE_PACKAGES.vite,
+        };
+      }
     } else if (packageManager === PackageManager.pnpm) {
       // If package.json already has a "pnpm" field, keep using it;
       // otherwise use pnpm-workspace.yaml.
@@ -1074,6 +1557,10 @@ export function rewriteStandaloneProject(
       }
       const overrideKeys = Object.keys(VITE_PLUS_OVERRIDE_PACKAGES);
       if (!usePnpmWorkspaceYaml) {
+        // Strip selector-shaped overrides (e.g. `parent>@vitest/browser-playwright`)
+        // whose target is a removed package, before re-merging the user's
+        // overrides into the new pnpm config.
+        dropRemovePackageOverrideKeys(pkg.pnpm?.overrides);
         // Project already has pnpm config in package.json -- keep using it.
         pkg.pnpm = {
           ...pkg.pnpm,
@@ -1107,11 +1594,18 @@ export function rewriteStandaloneProject(
       }
       // remove packages from `resolutions` field if they exist
       // https://pnpm.io/9.x/package_json#resolutions
-      for (const key of [...overrideKeys, ...REMOVE_PACKAGES]) {
+      for (const key of [...overrideKeys, ...PROVIDER_OVERRIDE_DROP_NAMES]) {
         if (pkg.resolutions?.[key]) {
           delete pkg.resolutions[key];
         }
       }
+      if (!usePnpmWorkspaceYaml && pnpmMajorVersion !== undefined && pkg.pnpm) {
+        applyBuildAllowanceToPackageJsonPnpm(
+          pkg.pnpm,
+          pnpmMajorVersion,
+          shouldAllowBrowserProviderBuilds,
+        );
+      }
     }
 
     extractedStagedConfig = rewritePackageJson(
@@ -1120,6 +1614,8 @@ export function rewriteStandaloneProject(
       usePnpmWorkspaceYaml,
       skipStagedMigration,
       catalogDependencyResolver,
+      usesVitestBrowserMode(projectPath),
+      collectProviderSourceModes(projectPath),
     );
 
     // ensure vite-plus is in devDependencies
@@ -1137,7 +1633,7 @@ export function rewriteStandaloneProject(
   });
 
   if (shouldRewritePnpmWorkspaceYaml) {
-    rewritePnpmWorkspaceYaml(projectPath);
+    rewritePnpmWorkspaceYaml(projectPath, pnpmMajorVersion, shouldAllowBrowserProviderBuilds);
   }
 
   // Move remaining non-Vite pnpm.overrides to pnpm-workspace.yaml
@@ -1153,6 +1649,8 @@ export function rewriteStandaloneProject(
 
   if (packageManager === PackageManager.yarn) {
     rewriteYarnrcYml(projectPath);
+  } else if (packageManager === PackageManager.bun) {
+    ensureBunfigPeerSuppression(projectPath);
   }
 
   // Merge extracted staged config into vite.config.ts, then remove lint-staged from package.json
@@ -1192,9 +1690,18 @@ export function rewriteMonorepo(
     workspaceInfo.rootDir,
     workspaceInfo.packageManager,
   );
+  const pnpmMajorVersion = pnpmMajor(workspaceInfo.downloadPackageManager.version);
+  const workspaceShouldAllowBrowserBuilds = workspaceUsesWebdriverio(
+    workspaceInfo.rootDir,
+    workspaceInfo.packages,
+  );
   // rewrite root workspace
   if (workspaceInfo.packageManager === PackageManager.pnpm) {
-    rewritePnpmWorkspaceYaml(workspaceInfo.rootDir);
+    rewritePnpmWorkspaceYaml(
+      workspaceInfo.rootDir,
+      pnpmMajorVersion,
+      workspaceShouldAllowBrowserBuilds,
+    );
   } else if (workspaceInfo.packageManager === PackageManager.yarn) {
     rewriteYarnrcYml(workspaceInfo.rootDir);
   } else if (workspaceInfo.packageManager === PackageManager.bun) {
@@ -1206,6 +1713,8 @@ export function rewriteMonorepo(
     skipStagedMigration,
     catalogDependencyResolver,
     workspaceInfo.packages,
+    pnpmMajorVersion,
+    workspaceShouldAllowBrowserBuilds,
   );
   // (mergeViteConfigFiles below will sanitize the merged lint config
   // against this workspace's full package set.)
@@ -1217,6 +1726,11 @@ export function rewriteMonorepo(
     rootDir: workspaceInfo.rootDir,
     packages: workspaceInfo.packages,
   };
+  // Yarn `node-modules` + an isolating `nmHoistingLimits` would give each
+  // vite-plus-receiving workspace its own physical `vitest` copy, splitting the
+  // runner across two `@vitest/runner` instances. `rewriteMonorepoProject` detects
+  // the layout per workspace (reading the root `.yarnrc.yml` itself) and auto-fixes
+  // or warns — see `applyYarnWorkspaceHoistingFix`.
   for (const pkg of workspaceInfo.packages) {
     rewriteMonorepoProject(
       path.join(workspaceInfo.rootDir, pkg.path),
@@ -1284,6 +1798,16 @@ export function rewriteMonorepoProject(
     return;
   }
 
+  // Yarn `nmHoistingLimits` for this workspace's project, found by walking up to the
+  // root `.yarnrc.yml`. Derived here (not threaded as an arg) so EVERY caller — full
+  // monorepo migration, a direct `rewriteMonorepoProject` call, and `vp create`
+  // integrating a package into an existing monorepo — is covered. undefined for
+  // non-Yarn repos.
+  const yarnHoisting =
+    packageManager === PackageManager.yarn
+      ? findYarnWorkspaceHoisting(workspaceContext?.rootDir ?? projectPath)
+      : undefined;
+
   let extractedStagedConfig: Record<string, string | string[]> | null = null;
   editJsonFile<{
     devDependencies?: Record<string, string>;
@@ -1291,6 +1815,7 @@ export function rewriteMonorepoProject(
     peerDependencies?: Record<string, string>;
     optionalDependencies?: Record<string, string>;
     scripts?: Record<string, string>;
+    installConfig?: { hoistingLimits?: string };
   }>(packageJsonPath, (pkg) => {
     // rewrite scripts in package.json
     extractedStagedConfig = rewritePackageJson(
@@ -1299,7 +1824,29 @@ export function rewriteMonorepoProject(
       true,
       skipStagedMigration,
       catalogDependencyResolver,
+      usesVitestBrowserMode(projectPath),
+      collectProviderSourceModes(projectPath),
     );
+    // If this SUB-workspace now depends on `vite-plus` and Yarn isolates its
+    // hoisting (via the root `nmHoistingLimits` OR the workspace's own
+    // `installConfig.hoistingLimits`), dedupe the bundled `vitest` family to the
+    // single shared root copy (avoids the dual-`@vitest/runner` "reading 'config'"
+    // crash), or warn when the split cannot be fixed from package.json. The monorepo
+    // root itself is skipped (`projectPath === yarnHoisting.rootDir`): its deps
+    // already hoist to the top level, so it never needs an opt-out.
+    if (
+      yarnHoisting &&
+      path.resolve(projectPath) !== yarnHoisting.rootDir &&
+      pkg.devDependencies?.[VITE_PLUS_NAME]
+    ) {
+      applyYarnWorkspaceHoistingFix(
+        pkg,
+        yarnHoisting.limit,
+        yarnHoisting.nodeLinker,
+        path.relative(yarnHoisting.rootDir, projectPath) || projectPath,
+        report,
+      );
+    }
     return pkg;
   });
 
@@ -1319,7 +1866,11 @@ export function rewriteMonorepoProject(
  * Rewrite pnpm-workspace.yaml to add vite-plus dependencies
  * @param projectPath - The path to the project
  */
-function rewritePnpmWorkspaceYaml(projectPath: string): void {
+function rewritePnpmWorkspaceYaml(
+  projectPath: string,
+  pnpmMajorVersion: number | undefined,
+  shouldAllowBrowserBuilds: boolean,
+): void {
   const pnpmWorkspaceYamlPath = path.join(projectPath, 'pnpm-workspace.yaml');
   if (!fs.existsSync(pnpmWorkspaceYamlPath)) {
     fs.writeFileSync(pnpmWorkspaceYamlPath, '');
@@ -1328,9 +1879,32 @@ function rewritePnpmWorkspaceYaml(projectPath: string): void {
   editYamlFile(pnpmWorkspaceYamlPath, (doc) => {
     // catalog
     rewriteCatalog(doc);
+    if (pnpmMajorVersion !== undefined) {
+      applyBuildAllowanceToWorkspaceYaml(doc, pnpmMajorVersion, shouldAllowBrowserBuilds);
+    }
 
     // overrides
     const overrides = doc.getIn(['overrides']);
+    pruneYamlMapLegacyWrapperAliases(overrides);
+    // Drop overrides for packages removed by migration (e.g. @vitest/browser*)
+    // so a stale workspace pin can't force an incompatible version against
+    // vite-plus's own direct dependency. Bare/versioned global pins
+    // (`pkg`, `pkg@version`), global-glob selectors (`**/pkg`), and
+    // `vite-plus`-parented selectors (`vite-plus>pkg`) all reach vite-plus's own
+    // provider dep and are removed. A selector scoped under a SPECIFIC
+    // non-vite-plus parent (e.g. `some-app>@vitest/browser-playwright`) only
+    // constrains that parent's subtree, so it is preserved — see
+    // `shouldDropProviderOverrideKey`.
+    if (overrides instanceof YAMLMap) {
+      const keysSnapshot = overrides.items.map((item) => item.key);
+      for (const keyNode of keysSnapshot) {
+        const rawKey =
+          keyNode instanceof Scalar ? String(keyNode.value ?? '') : String(keyNode ?? '');
+        if (shouldDropProviderOverrideKey(rawKey)) {
+          overrides.delete(keyNode);
+        }
+      }
+    }
     for (const key of Object.keys(VITE_PLUS_OVERRIDE_PACKAGES)) {
       const currentVersion = getYamlMapScalarStringValue(overrides, key);
       const version = getCatalogDependencySpec(
@@ -1380,7 +1954,11 @@ function rewritePnpmWorkspaceYaml(projectPath: string): void {
 
     // minimumReleaseAgeExclude
     if (doc.has('minimumReleaseAge')) {
-      // add vite-plus, @voidzero-dev/*, oxlint, oxlint-tsgolint, oxfmt to minimumReleaseAgeExclude
+      // Exempt the Vite+-managed packages from the age gate: vite-plus,
+      // @voidzero-dev/*, the ox* family, and the vitest family. Vite+ pins
+      // `vitest` to an exact (sometimes freshly published) version and the
+      // in-tree @vitest/* siblings install transitively at that version, so the
+      // age gate would otherwise quarantine them and break `vp install`.
       const excludes = [
         'vite-plus',
         '@voidzero-dev/*',
@@ -1390,6 +1968,7 @@ function rewritePnpmWorkspaceYaml(projectPath: string): void {
         '@oxlint-tsgolint/*',
         'oxfmt',
         '@oxfmt/*',
+        ...VITEST_AGE_GATE_EXEMPT_PACKAGES,
       ];
       let minimumReleaseAgeExclude = doc.getIn(['minimumReleaseAgeExclude']) as YAMLSeq<
         Scalar<string>
@@ -1422,10 +2001,13 @@ function cleanupPnpmOverridesForWorkspaceYaml(
   },
   overrideKeys: string[],
 ): Record<string, string> | undefined {
+  // Strip selector-shaped overrides (e.g. `parent>@vitest/browser-playwright`)
+  // whose target is a removed package, before the exact-key sweep below.
+  dropRemovePackageOverrideKeys(pkg.pnpm?.overrides);
   // Remove Vite-managed keys from pnpm.overrides
   const catalogOverrides: Record<string, string> = {};
   const overrides = pkg.pnpm?.overrides;
-  for (const key of [...overrideKeys, ...REMOVE_PACKAGES]) {
+  for (const key of [...overrideKeys, ...PROVIDER_OVERRIDE_DROP_NAMES]) {
     const value = overrides?.[key];
     if (value) {
       if (overrideKeys.includes(key) && value.startsWith('catalog:')) {
@@ -1483,6 +2065,161 @@ function migratePnpmOverridesToWorkspaceYaml(
   });
 }
 
+type DependencyBag = {
+  dependencies?: Record<string, string>;
+  devDependencies?: Record<string, string>;
+  optionalDependencies?: Record<string, string>;
+  peerDependencies?: Record<string, string>;
+};
+
+function hasOwnWebdriverioDependency(pkg: DependencyBag): boolean {
+  for (const name of WEBDRIVERIO_ALLOW_SIGNAL_DEPS) {
+    if (
+      pkg.dependencies?.[name] ??
+      pkg.devDependencies?.[name] ??
+      pkg.optionalDependencies?.[name] ??
+      pkg.peerDependencies?.[name]
+    ) {
+      return true;
+    }
+  }
+  return false;
+}
+
+function workspaceUsesWebdriverio(
+  rootDir: string,
+  packages: WorkspacePackage[] | undefined,
+): boolean {
+  const rootPkg = readPackageJsonIfExists(path.join(rootDir, 'package.json'));
+  if (rootPkg && hasOwnWebdriverioDependency(rootPkg)) {
+    return true;
+  }
+  // Source-only signal: a package may target the webdriverio provider purely
+  // through imports (e.g. `vite-plus/test/browser-webdriverio`) without a
+  // declared dep yet. The migration injects the provider for those, so the
+  // driver postinstalls must be allowed too.
+  if (usesWebdriverioProvider(rootDir)) {
+    return true;
+  }
+  if (!packages) {
+    return false;
+  }
+  for (const pkg of packages) {
+    const packageDir = path.join(rootDir, pkg.path);
+    const subPkg = readPackageJsonIfExists(path.join(packageDir, 'package.json'));
+    if (subPkg && hasOwnWebdriverioDependency(subPkg)) {
+      return true;
+    }
+    if (usesWebdriverioProvider(packageDir)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+function readPackageJsonIfExists(packageJsonPath: string): DependencyBag | undefined {
+  if (!fs.existsSync(packageJsonPath)) {
+    return undefined;
+  }
+  try {
+    return readJsonFile(packageJsonPath) as DependencyBag;
+  } catch {
+    return undefined;
+  }
+}
+
+// pnpm v10 introduced the map-shaped `allowBuilds` and removed the implicit
+// "build everything" default; v9 (>= 9.5) gates builds via the list-shaped
+// `onlyBuiltDependencies`. Both live in pnpm-workspace.yaml or in
+// `package.json`'s `pnpm` field — vp migrate writes to whichever sink the
+// rest of the migration is already touching.
+function pnpmMajor(version: string | undefined): number | undefined {
+  const coerced = version ? semver.coerce(version)?.version : undefined;
+  return coerced ? semver.major(coerced) : undefined;
+}
+
+function applyBuildAllowanceToPackageJsonPnpm(
+  pnpm: {
+    allowBuilds?: Record<string, boolean>;
+    onlyBuiltDependencies?: string[];
+  },
+  major: number,
+  shouldAllow: boolean,
+): void {
+  if (major >= 10) {
+    if (shouldAllow) {
+      // WebdriverIO present -> the edgedriver/geckodriver postinstall MUST run. Write
+      // `true`, OVERWRITING any stale `false` a prior WebdriverIO-less migration left
+      // behind (a re-run after adding WebdriverIO would otherwise keep the driver build
+      // blocked).
+      for (const name of BROWSER_PROVIDER_POSTINSTALL_PACKAGES) {
+        (pnpm.allowBuilds ??= {})[name] = true;
+      }
+    }
+    // No WebdriverIO -> vite-plus does NOT manage these postinstalls. edgedriver and
+    // geckodriver reach the tree only via the opt-in webdriverio provider (an OPTIONAL
+    // peer of both vite-plus and vitest, so pnpm never auto-installs it); a project that
+    // does not use it never installs them, so there is nothing to allow or deny. We
+    // write nothing and leave any user-authored allowBuilds entry (their own trust
+    // decision) untouched.
+  } else if (shouldAllow) {
+    // v9 onlyBuiltDependencies is an allow-list — omission is denial, so we
+    // only mutate when the user actually needs these packages built.
+    const list = pnpm.onlyBuiltDependencies ?? [];
+    const existing = new Set(list);
+    for (const name of BROWSER_PROVIDER_POSTINSTALL_PACKAGES) {
+      if (!existing.has(name)) {
+        list.push(name);
+        existing.add(name);
+      }
+    }
+    pnpm.onlyBuiltDependencies = list;
+  }
+}
+
+function applyBuildAllowanceToWorkspaceYaml(
+  doc: YamlDocument,
+  major: number,
+  shouldAllow: boolean,
+): void {
+  if (major >= 10) {
+    if (shouldAllow) {
+      // WebdriverIO present -> the edgedriver/geckodriver postinstall MUST run. Set
+      // `true`, OVERWRITING any stale `false` a prior WebdriverIO-less migration left
+      // behind (a re-run after adding WebdriverIO would otherwise keep the driver build
+      // blocked). Mutate an existing map in place (preserving its document position);
+      // only attach a freshly created one.
+      const existing = doc.getIn(['allowBuilds']);
+      const isNew = !(existing instanceof YAMLMap);
+      const allowBuilds = isNew
+        ? new YAMLMap<Scalar<string>, Scalar<boolean>>()
+        : (existing as YAMLMap<Scalar<string>, Scalar<boolean>>);
+      for (const name of BROWSER_PROVIDER_POSTINSTALL_PACKAGES) {
+        allowBuilds.set(scalarString(name), new Scalar(true));
+      }
+      if (isNew) {
+        doc.setIn(['allowBuilds'], allowBuilds);
+      }
+    }
+    // No WebdriverIO -> vite-plus does NOT manage these postinstalls and leaves any
+    // user-authored allowBuilds entry untouched (see the package.json sink rationale).
+    // The drivers reach the tree only via the opt-in webdriverio provider, so a project
+    // that does not use it never installs them and there is nothing to allow or deny.
+  } else if (shouldAllow) {
+    let onlyBuiltDependencies = doc.getIn(['onlyBuiltDependencies']) as YAMLSeq<Scalar<string>>;
+    if (!(onlyBuiltDependencies instanceof YAMLSeq)) {
+      onlyBuiltDependencies = new YAMLSeq<Scalar<string>>();
+    }
+    const existing = new Set(onlyBuiltDependencies.items.map((n) => n.value));
+    for (const name of BROWSER_PROVIDER_POSTINSTALL_PACKAGES) {
+      if (!existing.has(name)) {
+        onlyBuiltDependencies.add(scalarString(name));
+      }
+    }
+    doc.setIn(['onlyBuiltDependencies'], onlyBuiltDependencies);
+  }
+}
+
 /**
  * Remove only Vite-managed entries from peerDependencyRules, preserving custom ones.
  */
@@ -1517,6 +2254,239 @@ function cleanupPeerDependencyRules(
  * Rewrite .yarnrc.yml to add vite-plus dependencies
  * @param projectPath - The path to the project
  */
+// Under Yarn's `node-modules` linker, `nmHoistingLimits: workspaces` STOPS a
+// dependency from being hoisted past the workspace that declares it — so every
+// workspace that gets a direct `vite-plus` dep receives its OWN physical
+// `vitest`/`@vitest/runner` copy instead of sharing one hoisted copy at the
+// monorepo root. `vp test` resolves the Vitest runner bin ONCE from the workspace
+// root (the root copy) but spawns it with the package as cwd; Vitest's per-package
+// Vite server then serves the test graph's `@vitest/runner` from the PACKAGE's own
+// copy. The runner process initialises its (root) `@vitest/runner` module instance
+// while the test file imports `describe` from the package's DIFFERENT instance
+// whose module-level runner is undefined -> `describe(...)` -> `initSuite()` ->
+// `validateTags(runner.config, …)` -> `TypeError: Cannot read properties of
+// undefined (reading 'config')`. Yarn has no per-package "force-hoist this dep to
+// root" lever, so the only reliable dedupe is to let the affected workspaces hoist
+// normally (a per-workspace `installConfig.hoistingLimits: none`). See
+// `setYarnWorkspaceHoistingOptOut`.
+//
+// Only `workspaces` is auto-fixable. The stricter `dependencies` limit keeps a
+// dependency BELOW each dependent package even when the workspace opts out to
+// `none`, so the opt-out does NOT dedupe there — verified with Yarn 4.17: two
+// workspaces sharing a dep under root `nmHoistingLimits: dependencies` + per-
+// workspace `hoistingLimits: none` still produced two physical copies, whereas
+// the same setup under `workspaces` deduped to one root copy. For `dependencies`
+// (and for a `workspaces` root where the affected workspace already pins its own
+// isolating limit) the migration cannot fix the split from package.json, so it
+// WARNS instead of silently leaving a known-broken layout. See
+// `applyYarnWorkspaceHoistingFix`.
+
+// Read a SINGLE directory's `.yarnrc.yml` scalar value for `key` (or undefined when
+// the file/key is absent or non-string). Malformed YAML throws inside `readYamlFile`,
+// so guard with try/catch — a broken ancestor rc must not abort the migration.
+//
+// Values are taken VERBATIM: Yarn's `${VAR}` / `${VAR:-default}` string interpolation
+// is NOT evaluated. An interpolated `nmHoistingLimits`/`nodeLinker` therefore won't
+// match the literal `'workspaces'`/`'node-modules'` the caller compares against, so the
+// hoisting fix conservatively does NOTHING for it — a no-op (and never a spurious
+// mutation), the same outcome as a repo with no hoisting handling at all. Faithfully
+// evaluating Yarn interpolation would mean reimplementing Yarn's config loader (or
+// shelling out to `yarn config get`, a fragile pre-install process dependency), which
+// is out of scope for this best-effort safety net.
+//
+// The filename is the literal `.yarnrc.yml`, not Yarn's `YARN_RC_FILENAME`-renamed rc.
+// `YARN_RC_FILENAME` support is intentionally out of scope: the rest of the Yarn
+// migration (catalog/`nodeLinker`/`npmPreapprovedPackages` writes in `rewriteYarnrcYml`
+// et al.) only ever writes `.yarnrc.yml`, so reading a renamed rc here would be a
+// partial, inconsistent treatment — and a repo with `YARN_RC_FILENAME` set cannot be
+// migrated at all until the write path also honours it (a separate, larger change).
+// Keeping reads and writes on the same `.yarnrc.yml` is the consistent behaviour.
+function readYarnrcValue(dir: string, key: string): string | undefined {
+  const yarnrcYmlPath = path.join(dir, '.yarnrc.yml');
+  if (!fs.existsSync(yarnrcYmlPath)) {
+    return undefined;
+  }
+  try {
+    const doc = readYamlFile(yarnrcYmlPath) as Record<string, unknown> | null;
+    const value = doc?.[key];
+    return typeof value === 'string' ? value : undefined;
+  } catch {
+    return undefined;
+  }
+}
+
+// Resolve the EFFECTIVE value Yarn would apply for a config `key` (and its
+// `YARN_<KEY>` env override) for a project rooted at `workspaceRootDir`, matching
+// Yarn 4.17 precedence (all verified with `yarn config get`):
+//   1. the `YARN_*` environment variable wins over every `.yarnrc.yml` (e.g.
+//      `YARN_NM_HOISTING_LIMITS`, `YARN_NODE_LINKER`);
+//   2. otherwise Yarn merges `.yarnrc.yml` across the project root AND its ancestor
+//      directories, the CLOSEST file that defines the key winning — so a key set only
+//      in an ancestor rc is in effect, while a workspace-root value overrides it.
+// So check the env var, then walk UP from the workspace root, then finally the home
+// `~/.yarnrc.yml`, returning the first DEFINED value; undefined when none set it (the
+// caller applies Yarn's default). The ancestor walk starts AT the workspace root,
+// never below it — a sub-workspace's own `.yarnrc.yml` is not part of Yarn's
+// install-time config resolution and must not shadow the root.
+//
+// The home rc is consulted LAST (lowest precedence, below the project/ancestor chain
+// — verified with Yarn 4.17: a project-root value beats the home value). For a project
+// UNDER $HOME the ancestor walk already passed through $HOME, so the explicit read is
+// redundant; it matters for projects OUTSIDE $HOME (e.g. devcontainers/Codespaces
+// mount the repo under /workspaces while $HOME is /home/<user>), where Yarn still
+// reads the home rc and the ancestor walk would otherwise miss it.
+function resolveEffectiveYarnConfigValue(
+  workspaceRootDir: string,
+  key: string,
+  envVar: string,
+): string | undefined {
+  const fromEnv = process.env[envVar]?.trim();
+  if (fromEnv) {
+    return fromEnv;
+  }
+  let dir = path.resolve(workspaceRootDir);
+  for (;;) {
+    const value = readYarnrcValue(dir, key);
+    if (value !== undefined) {
+      return value;
+    }
+    const parent = path.dirname(dir);
+    if (parent === dir) {
+      break;
+    }
+    dir = parent;
+  }
+  const home = os.homedir();
+  return home ? readYarnrcValue(home, key) : undefined;
+}
+
+// True when `dir`'s package.json declares a `workspaces` field — i.e. `dir` is a
+// workspace (Yarn project) root. `workspaces` may be an array or an object
+// (`{ packages: [...] }`); both are truthy.
+function dirIsWorkspaceRoot(dir: string): boolean {
+  const pkgJsonPath = path.join(dir, 'package.json');
+  if (!fs.existsSync(pkgJsonPath)) {
+    return false;
+  }
+  try {
+    const pkg = readJsonFile(pkgJsonPath) as { workspaces?: unknown };
+    return pkg.workspaces != null;
+  } catch {
+    return false;
+  }
+}
+
+// Walk up from a workspace directory to the nearest ancestor that IS a workspace
+// root (its package.json declares `workspaces`) — the real Yarn project root — and
+// return that directory plus the EFFECTIVE `nmHoistingLimits` and `nodeLinker`
+// resolved across env + the `.yarnrc.yml` chain at and above that root. Keying on the
+// workspace-root marker (NOT the nearest `.yarnrc.yml`) is deliberate: a package-local
+// `.yarnrc.yml` written under a sub-package (e.g. by `vp create` / install) must not
+// shadow the real root's limit, while a limit set in an ancestor `.yarnrc.yml` above
+// the root is still honoured (Yarn merges the ancestor chain). This lets
+// `rewriteMonorepoProject` discover the layout for ANY caller without it being
+// threaded as an argument (the omitted-arg path was a missed-auto-fix bug class), and
+// lets the caller tell whether the workspace it is rewriting IS the root (the root's
+// deps already hoist to the top, so it must never be opted out). `nodeLinker` gates
+// the fix: `nmHoistingLimits` only splits packages under the `node-modules` linker, so
+// a PnP project (Yarn's default) is left untouched. undefined when no workspace root
+// is found up to the filesystem root.
+function findYarnWorkspaceHoisting(
+  startDir: string,
+): { rootDir: string; limit: string | undefined; nodeLinker: string | undefined } | undefined {
+  let dir = path.resolve(startDir);
+  for (;;) {
+    if (dirIsWorkspaceRoot(dir)) {
+      return {
+        rootDir: dir,
+        limit: resolveEffectiveYarnConfigValue(dir, 'nmHoistingLimits', 'YARN_NM_HOISTING_LIMITS'),
+        nodeLinker: resolveEffectiveYarnConfigValue(dir, 'nodeLinker', 'YARN_NODE_LINKER'),
+      };
+    }
+    const parent = path.dirname(dir);
+    if (parent === dir) {
+      return undefined;
+    }
+    dir = parent;
+  }
+}
+
+// Opt a single workspace OUT of the INHERITED root `nmHoistingLimits` isolation by
+// setting its own `installConfig.hoistingLimits: none`, so its `vite-plus` (and
+// thus the bundled `vitest` family) hoists to the single shared root copy the
+// runner bin resolves to. Scoped to workspaces the migration adds `vite-plus` to,
+// so unrelated workspaces are untouched. `none` is Yarn's DEFAULT hoisting
+// behaviour, so this only re-enables ordinary deduping — it never force-promotes a
+// conflicting version to root.
+//
+// Only relaxes the INHERITED root limit: if the workspace already carries an
+// EXPLICIT `installConfig.hoistingLimits` we leave it as-is. Overwriting it would
+// clobber an intentional per-workspace invariant (e.g. a React Native `example`
+// that isolates its whole tree for Metro and happens to also use Vite+ for tests),
+// and that field governs the workspace's ENTIRE dependency tree, not just the
+// vitest family. Idempotent: a no-op when any explicit value is already present.
+function setYarnWorkspaceHoistingOptOut(pkg: {
+  installConfig?: { hoistingLimits?: string };
+}): void {
+  if (pkg.installConfig?.hoistingLimits !== undefined) {
+    return;
+  }
+  pkg.installConfig = { ...pkg.installConfig, hoistingLimits: 'none' };
+}
+
+// Resolve the Yarn workspace-hoisting isolation for a workspace that now depends on
+// `vite-plus`. `rootLimit` is the effective `nmHoistingLimits` and `nodeLinker` the
+// effective linker (both undefined for non-Yarn repos or an unset key). Either
+// auto-fixes the workspace (mutating `pkg`) or, when the split cannot be fixed from
+// package.json, warns so the migration never reports success while `vp test` is still
+// known-broken.
+function applyYarnWorkspaceHoistingFix(
+  pkg: { installConfig?: { hoistingLimits?: string } },
+  rootLimit: string | undefined,
+  nodeLinker: string | undefined,
+  workspaceLabel: string,
+  report?: MigrationReport,
+): void {
+  // `nmHoistingLimits`/`installConfig.hoistingLimits` only govern the `node-modules`
+  // linker — they physically isolate copies there. Under Plug'n'Play (Yarn's DEFAULT
+  // when `nodeLinker` is unset) resolution is virtual: no duplicate `@vitest/runner`
+  // can exist, so neither the auto-fix nor the warning applies. Writing an opt-out
+  // there would be a spurious source mutation that weakens isolation if the repo later
+  // switches linkers, so skip everything unless the linker is `node-modules`.
+  if (nodeLinker !== 'node-modules') {
+    return;
+  }
+  // `workspaces` isolation with no explicit per-workspace limit is the one layout a
+  // `none` opt-out deduplicates — fix it silently.
+  if (rootLimit === 'workspaces' && pkg.installConfig?.hoistingLimits === undefined) {
+    setYarnWorkspaceHoistingOptOut(pkg);
+    return;
+  }
+  // Layouts we must NOT (or cannot) auto-fix, but which still isolate this
+  // workspace's `vitest`/`vite-plus` copy so `vp test` can crash with a split
+  // `@vitest/runner`:
+  //   - the INHERITED root `dependencies` limit (a `none` opt-out does not dedupe
+  //     it — verified), and
+  //   - the workspace's OWN explicit isolating `installConfig.hoistingLimits`
+  //     (`workspaces`/`dependencies`), which isolates it regardless of the root
+  //     value (incl. root unset or `none`) and is intentional, so it is preserved
+  //     rather than clobbered.
+  // Surface a manual step for both rather than report a silently broken migration.
+  const explicit = pkg.installConfig?.hoistingLimits;
+  const isolatedByRoot = rootLimit === 'dependencies';
+  const isolatedByWorkspace = explicit === 'workspaces' || explicit === 'dependencies';
+  if (isolatedByRoot || isolatedByWorkspace) {
+    warnMigration(
+      `Yarn workspace "${workspaceLabel}" isolates dependency hoisting ` +
+        `(hoistingLimits: ${explicit ?? rootLimit}), so it keeps its own ` +
+        `\`vitest\`/\`vite-plus\` copy and \`vp test\` may crash with a split ` +
+        `\`@vitest/runner\`. Dedupe them to a single copy — relax this workspace's ` +
+        `hoisting isolation or pin one \`vitest\` for the workspace.`,
+      report,
+    );
+  }
+}
+
 function rewriteYarnrcYml(projectPath: string): void {
   const yarnrcYmlPath = path.join(projectPath, '.yarnrc.yml');
   if (!fs.existsSync(yarnrcYmlPath)) {
@@ -1527,6 +2497,26 @@ function rewriteYarnrcYml(projectPath: string): void {
     if (!doc.has('nodeLinker')) {
       doc.set('nodeLinker', 'node-modules');
     }
+    // Vite+ pins the vitest family to exact, sometimes freshly published,
+    // versions. Yarn 4 hardened mode (auto-enabled for public-PR installs)
+    // quarantines packages younger than `npmMinimalAgeGate`, which makes
+    // `yarn install` fail on a just-released vitest pin. Preapprove the family
+    // so the Vite+-managed versions install regardless of release age; the
+    // `@vitest/*` glob also covers the optional `@vitest/browser-*` peers that
+    // are not in the override set. MERGE into any existing list (e.g. a project
+    // that already preapproves private packages) instead of skipping when set,
+    // otherwise the gate could still reject the freshly pinned vitest.
+    let npmPreapprovedPackages = doc.getIn(['npmPreapprovedPackages']) as YAMLSeq<Scalar<string>>;
+    if (!npmPreapprovedPackages) {
+      npmPreapprovedPackages = new YAMLSeq();
+    }
+    const existingPreapproved = new Set(npmPreapprovedPackages.items.map((n) => n.value));
+    for (const pkg of VITEST_AGE_GATE_EXEMPT_PACKAGES) {
+      if (!existingPreapproved.has(pkg)) {
+        npmPreapprovedPackages.add(scalarString(pkg));
+      }
+    }
+    doc.setIn(['npmPreapprovedPackages'], npmPreapprovedPackages);
     // catalog
     rewriteCatalog(doc);
   });
@@ -1657,6 +2647,29 @@ function getYamlMapScalarStringValue(map: unknown, key: string): string | undefi
   return undefined;
 }
 
+function pruneYamlMapLegacyWrapperAliases(map: unknown): void {
+  if (!(map instanceof YAMLMap)) {
+    return;
+  }
+  const stale: Array<{ key: Scalar<string>; fallback: string | undefined }> = [];
+  for (const item of map.items) {
+    const value = item.value instanceof Scalar ? item.value.value : undefined;
+    if (typeof value === 'string' && isLegacyWrapperSpec(value) && item.key instanceof Scalar) {
+      stale.push({
+        key: item.key,
+        fallback: LEGACY_WRAPPER_FALLBACK_VERSIONS[item.key.value],
+      });
+    }
+  }
+  for (const { key, fallback } of stale) {
+    if (fallback !== undefined) {
+      map.set(key, scalarString(fallback));
+    } else {
+      map.delete(key);
+    }
+  }
+}
+
 function rewriteCatalog(doc: YamlDocument): void {
   for (const [key, value] of Object.entries(VITE_PLUS_OVERRIDE_PACKAGES)) {
     // ERR_PNPM_CATALOG_IN_OVERRIDES  Could not resolve a catalog in the overrides: The entry for 'vite' in catalog 'default' declares a dependency using the 'file' protocol
@@ -1675,6 +2688,8 @@ function rewriteCatalog(doc: YamlDocument): void {
       doc.deleteIn(path);
     }
   }
+  // Drop any entry still pointing at the deleted `vite-plus-test` wrapper.
+  pruneYamlMapLegacyWrapperAliases(doc.getIn(['catalog']));
 
   const catalogs = doc.getIn(['catalogs']);
   if (!(catalogs instanceof YAMLMap)) {
@@ -1701,6 +2716,7 @@ function rewriteCatalog(doc: YamlDocument): void {
         doc.deleteIn(catalogPath);
       }
     }
+    pruneYamlMapLegacyWrapperAliases(item.value);
   }
 }
 
@@ -1725,6 +2741,42 @@ function rewriteCatalogsObject(catalogs: Record<string, Record<string, string>>)
   }
 }
 
+/**
+ * Bun rejects vitest@4.1.9's `vite^6/^7/^8` peer-dep when the user's project
+ * overrides `vite` to `@voidzero-dev/vite-plus-core` (whose package.json version
+ * does not match those ranges). pnpm/yarn/npm all tolerate this redirect; bun
+ * does not, and there is no `peerDependencyRules`-style escape hatch — only the
+ * `[install] peer = false` setting in `bunfig.toml`.
+ *
+ * `vite-plus`/`@voidzero-dev/vite-plus-core` already provide the vite surface
+ * the user needs, so disabling bun's auto-install of *missing* peers is safe in
+ * this configuration: any vitest peer that's not already pulled in transitively
+ * (jsdom, happy-dom, etc.) is marked optional upstream anyway.
+ *
+ * Writes/merges `bunfig.toml` at `projectPath` so the suppression applies on
+ * the migration's reinstall AND every subsequent `bun install` the user runs.
+ */
+function ensureBunfigPeerSuppression(projectPath: string): void {
+  const bunfigPath = path.join(projectPath, 'bunfig.toml');
+  const block = '[install]\npeer = false\n';
+  if (!fs.existsSync(bunfigPath)) {
+    fs.writeFileSync(bunfigPath, block);
+    return;
+  }
+  const existing = fs.readFileSync(bunfigPath, 'utf8');
+  // Already configured? Leave the user's setting alone — they may have set
+  // `peer = true` deliberately for some other reason and we shouldn't override.
+  if (/^\s*peer\s*=\s*(true|false)\s*$/m.test(existing)) {
+    return;
+  }
+  // Append under existing [install] section, or add a new section.
+  const installSectionRe = /^\[install\][^[]*/m;
+  const next = installSectionRe.test(existing)
+    ? existing.replace(installSectionRe, (section) => `${section.trimEnd()}\npeer = false\n`)
+    : `${existing.trimEnd()}\n\n${block}`;
+  fs.writeFileSync(bunfigPath, next);
+}
+
 /**
  * Write catalog entries to root package.json for bun.
  * Bun stores catalogs in package.json under the `catalog` key,
@@ -1754,34 +2806,53 @@ function rewriteBunCatalog(projectPath: string): void {
     };
 
     rewriteCatalogObject(catalog, true);
+    pruneLegacyWrapperAliases(catalog);
 
     if (useWorkspacesCatalog) {
       workspacesObj.catalog = catalog;
       if (pkg.catalog) {
         rewriteCatalogObject(pkg.catalog, false);
+        pruneLegacyWrapperAliases(pkg.catalog);
       }
     } else {
       pkg.catalog = catalog;
       if (workspacesObj?.catalog) {
         rewriteCatalogObject(workspacesObj.catalog, false);
+        pruneLegacyWrapperAliases(workspacesObj.catalog);
       }
     }
     if (workspacesObj?.catalogs) {
       rewriteCatalogsObject(workspacesObj.catalogs);
+      for (const named of Object.values(workspacesObj.catalogs)) {
+        pruneLegacyWrapperAliases(named);
+      }
     }
     if (pkg.catalogs) {
       rewriteCatalogsObject(pkg.catalogs);
+      for (const named of Object.values(pkg.catalogs)) {
+        pruneLegacyWrapperAliases(named);
+      }
     }
 
     // bun overrides support catalog: references
     const overrides: Record<string, string> = { ...pkg.overrides };
+    pruneLegacyWrapperAliases(overrides);
     for (const [key, value] of Object.entries(VITE_PLUS_OVERRIDE_PACKAGES)) {
-      overrides[key] = getCatalogDependencySpec(overrides[key], value, true);
+      const current = overrides[key] as unknown;
+      // A nested object value is a user override scoped under this managed key,
+      // not a version pin — leave it intact (getCatalogDependencySpec expects a
+      // string and would otherwise clobber it / throw on `.startsWith`).
+      if (current !== undefined && typeof current !== 'string') {
+        continue;
+      }
+      overrides[key] = getCatalogDependencySpec(current, value, true);
     }
     pkg.overrides = overrides;
 
     return pkg;
   });
+
+  ensureBunfigPeerSuppression(projectPath);
 }
 
 /**
@@ -1797,6 +2868,8 @@ function rewriteRootWorkspacePackageJson(
   // sanitizer can see hoisted deps in sibling workspace packages, not
   // just the root's own `package.json`.
   packages?: WorkspacePackage[],
+  pnpmMajorVersion?: number,
+  shouldAllowBrowserBuilds = false,
 ): void {
   const packageJsonPath = path.join(projectPath, 'package.json');
   if (!fs.existsSync(packageJsonPath)) {
@@ -1817,8 +2890,24 @@ function rewriteRootWorkspacePackageJson(
         allowAny?: string[];
         allowedVersions?: Record<string, string>;
       };
+      allowBuilds?: Record<string, boolean>;
+      onlyBuiltDependencies?: string[];
     };
   }>(packageJsonPath, (pkg) => {
+    // Strip stale `vite-plus-test` wrapper aliases before injecting new overrides
+    // so the deleted wrapper doesn't survive migration in any sink.
+    pruneLegacyWrapperAliases(pkg.resolutions);
+    pruneLegacyWrapperAliases(pkg.overrides);
+    pruneLegacyWrapperAliases(pkg.pnpm?.overrides);
+    // Drop stale provider overrides/resolutions (REMOVE_PACKAGES + the now
+    // user-owned opt-in providers, webdriverio/playwright) from the npm/bun
+    // `overrides` and yarn `resolutions` sinks before re-merging managed
+    // overrides. A leftover pin would conflict with the migrated direct
+    // `@vitest/browser-webdriverio` / `@vitest/browser-playwright` dep — npm
+    // hard-fails with EOVERRIDE, and yarn/bun would force the stale version over
+    // the bundled-vitest-aligned 4.1.9. (The pnpm sinks are pruned below.)
+    dropRemovePackageOverrideKeys(pkg.resolutions);
+    dropRemovePackageOverrideKeys(pkg.overrides);
     if (packageManager === PackageManager.yarn) {
       pkg.resolutions = {
         ...pkg.resolutions,
@@ -1833,9 +2922,26 @@ function rewriteRootWorkspacePackageJson(
       };
     } else if (packageManager === PackageManager.bun) {
       // bun overrides are handled in rewriteBunCatalog() with catalog: references
+      // Bun walks transitive peer-deps before resolving overrides; vitest 4.1.9
+      // declares peer `vite ^6 || ^7 || ^8` and aborts unless `vite` is a direct
+      // dep at the workspace root. Mirror the override as a devDep; the override
+      // configured in rewriteBunCatalog still redirects it to vite-plus-core.
+      // See https://github.com/oven-sh/bun/issues/8406.
+      pkg.devDependencies = {
+        ...pkg.devDependencies,
+        vite: getCatalogDependencySpec(
+          pkg.devDependencies?.vite,
+          VITE_PLUS_OVERRIDE_PACKAGES.vite,
+          true,
+        ),
+      };
     } else if (packageManager === PackageManager.pnpm) {
       const overrideKeys = Object.keys(VITE_PLUS_OVERRIDE_PACKAGES);
       if (isForceOverrideMode()) {
+        // Strip selector-shaped overrides (e.g. `parent>@vitest/browser-playwright`)
+        // whose target is a removed package, before re-merging the user's
+        // overrides into the new pnpm config.
+        dropRemovePackageOverrideKeys(pkg.pnpm?.overrides);
         // In force-override mode, keep overrides in package.json pnpm.overrides
         // because pnpm ignores pnpm-workspace.yaml overrides when pnpm.overrides
         // exists in package.json (even with unrelated entries like rollup).
@@ -1848,7 +2954,7 @@ function rewriteRootWorkspacePackageJson(
           },
         };
       } else {
-        for (const key of [...overrideKeys, ...REMOVE_PACKAGES]) {
+        for (const key of [...overrideKeys, ...PROVIDER_OVERRIDE_DROP_NAMES]) {
           if (pkg.resolutions?.[key]) {
             delete pkg.resolutions[key];
           }
@@ -1864,6 +2970,9 @@ function rewriteRootWorkspacePackageJson(
           }
         }
       }
+      if (pnpmMajorVersion !== undefined && pkg.pnpm) {
+        applyBuildAllowanceToPackageJsonPnpm(pkg.pnpm, pnpmMajorVersion, shouldAllowBrowserBuilds);
+      }
     }
 
     // ensure vite-plus is in devDependencies
@@ -2062,6 +3171,14 @@ function isSemanticVitePlusOverrideSpec(dependencyName: string, spec: string | u
   if (!spec) {
     return false;
   }
+  // A spec still pointing at the deleted `@voidzero-dev/vite-plus-test` wrapper
+  // is stale, NOT satisfied: this release ships upstream vitest directly, so the
+  // wrapper must be rewritten/pruned to the bundled vitest rather than accepted
+  // (otherwise `detectVitePlusBootstrapPending` skips writing the new
+  // `vitest: VITEST_VERSION` and the override keeps installing the dead wrapper).
+  if (isLegacyWrapperSpec(spec)) {
+    return false;
+  }
   if (spec === VITE_PLUS_OVERRIDE_PACKAGES[dependencyName]) {
     return true;
   }
@@ -2449,7 +3566,12 @@ export function ensureVitePlusBootstrap(
         ) ||
         !pnpmPeerDependencyRulesSatisfyVitePlus(readPnpmWorkspacePeerDependencyRules(projectPath))
       ) {
-        rewritePnpmWorkspaceYaml(projectPath);
+        // Bootstrap only completes the catalog / overrides / peer rules for a
+        // project that already uses Vite+. Build-script allowance stays owned
+        // by the full migration paths, so pass an undefined pnpm major to skip
+        // it (mirrors the single-arg call this path used before the signature
+        // grew the build-allowance parameters).
+        rewritePnpmWorkspaceYaml(projectPath, undefined, false);
       }
       if (fs.existsSync(pnpmWorkspaceYamlPath)) {
         ensurePnpmWorkspacePackages(projectPath, workspaceInfo.workspacePatterns);
@@ -2485,6 +3607,232 @@ export function ensureVitePlusBootstrap(
   return result;
 }
 
+// Specifier fragments that signal vitest browser mode. Matched as substrings
+// against source (see `sourceTreeReferencesAny`), so subpath imports are
+// covered too. Each indicates the package drives vitest's browser runner:
+//   - `@vitest/browser`         upstream, pre-migration (incl. `/context`,
+//                               `/client`, … subpaths)
+//   - `vite-plus/test/browser`  migrated (re-run on an already-migrated
+//                               project); also covers `…/browser/context` and
+//                               the `…/browser/providers/*` provider forms
+//   - `vite-plus/test/{client,context,locators,matchers,utils}`  the published
+//                               bare browser shims (`build.ts`
+//                               `createBareBrowserShims`): each re-exports
+//                               `@vitest/browser/<sub>` but DROPS the `browser`
+//                               segment, so they carry no `browser` substring.
+//                               The import rewriter flattens
+//                               `@vitest/browser/{client,locators,matchers,
+//                               utils}` to four of these in already-migrated
+//                               source; `vite-plus/test/context` is reachable
+//                               as the published bare export (the rewriter
+//                               instead routes `@vitest/browser/context` to
+//                               `vite-plus/test/browser/context`, already
+//                               covered above). All five are browser-only
+//                               re-exports, so they never collide with a
+//                               non-browser vitest export.
+//   - `vite-plus/test/plugins/browser`  prefix for the generated plugin shims
+//                               (`build.ts` `PLUGIN_SHIM_ENTRIES`:
+//                               `plugins/browser`, `plugins/browser-context`,
+//                               `plugins/browser-client`, `plugins/browser-
+//                               locators`, `plugins/browser-playwright`,
+//                               `plugins/browser-preview`, `plugins/browser-
+//                               webdriverio`), which re-export `@vitest/browser*`
+//                               under a `/plugins/` segment that the
+//                               `vite-plus/test/browser` hint does not match.
+//                               One prefix covers the whole family.
+//   - `vite-plus/test/internal/browser`  the published internal browser shim
+//                               (`./test/internal/browser`, re-exports
+//                               `vitest/internal/browser`) — also a `/browser`
+//                               surface with no `vite-plus/test/browser`
+//                               substring.
+// Without a matching hint a package importing only one of these published
+// browser surfaces (with no `@vitest/browser*` dep) would miss browser mode and
+// skip pinning the direct `vitest` the browser optimizer needs resolvable from
+// the package root under pnpm strict / Yarn PnP. This set is verified complete
+// against every browser-surface `./test/*` export in package.json (those that
+// re-export `@vitest/browser*` or `vitest/internal/browser`).
+const VITEST_BROWSER_SPECIFIER_HINTS = [
+  '@vitest/browser',
+  'vite-plus/test/browser',
+  'vite-plus/test/plugins/browser',
+  'vite-plus/test/internal/browser',
+  'vite-plus/test/client',
+  'vite-plus/test/context',
+  'vite-plus/test/locators',
+  'vite-plus/test/matchers',
+  'vite-plus/test/utils',
+] as const;
+
+// Specifier fragments that signal the WEBDRIVERIO provider specifically. Each
+// is a prefix, matched as a substring, so subpath imports (`/context`,
+// `/provider`, …) are covered too:
+//   - `@vitest/browser-webdriverio`            pre-migration (incl. `/provider`,
+//                                              `/context` subpaths)
+//   - `vite-plus/test/browser-webdriverio`     migrated (re-run); covers
+//                                              `…/context`
+//   - `vite-plus/test/browser/providers/webdriverio`  migrated provider-subpath
+//                                              form — the import rewriter maps
+//                                              `@vitest/browser-webdriverio/provider`
+//                                              here, so an already-migrated
+//                                              project can contain it. Without
+//                                              this hint a re-run would skip the
+//                                              provider injection and the import
+//                                              would break under pnpm strict /
+//                                              Yarn PnP once the provider is no
+//                                              longer a vite-plus runtime dep.
+//   - `vite-plus/test/plugins/browser-webdriverio`  generated plugin shim that
+//                                              re-exports `@vitest/browser-
+//                                              webdriverio` wholesale; importing
+//                                              it pulls in the (now opt-in)
+//                                              provider, so it signals usage too.
+const WEBDRIVERIO_PROVIDER_SPECIFIER_HINTS = [
+  '@vitest/browser-webdriverio',
+  'vite-plus/test/browser-webdriverio',
+  'vite-plus/test/browser/providers/webdriverio',
+  'vite-plus/test/plugins/browser-webdriverio',
+] as const;
+
+// Specifier fragments that signal the PLAYWRIGHT provider specifically — the
+// playwright analogue of WEBDRIVERIO_PROVIDER_SPECIFIER_HINTS (same prefix /
+// substring matching for `/provider`, `/context` subpaths). Playwright is opt-in
+// just like webdriverio: vite-plus no longer bundles `@vitest/browser-playwright`
+// at runtime, so a source-only user (e.g. `vite.config.ts` importing the
+// provider via a `vite-plus/test/browser-playwright` shim with no declared dep)
+// must still have the provider kept/injected for the rewritten import to resolve.
+const PLAYWRIGHT_PROVIDER_SPECIFIER_HINTS = [
+  '@vitest/browser-playwright',
+  'vite-plus/test/browser-playwright',
+  'vite-plus/test/browser/providers/playwright',
+  'vite-plus/test/plugins/browser-playwright',
+] as const;
+
+// Per-provider source-scan hint lists, used to build the `providerSourceModes`
+// map passed to `rewritePackageJson`.
+const BROWSER_PROVIDER_SPECIFIER_HINTS: Record<string, readonly string[]> = {
+  [WEBDRIVERIO_PROVIDER]: WEBDRIVERIO_PROVIDER_SPECIFIER_HINTS,
+  [PLAYWRIGHT_PROVIDER]: PLAYWRIGHT_PROVIDER_SPECIFIER_HINTS,
+};
+
+// TypeScript/JavaScript source extensions scanned for browser-mode hints.
+const VITEST_SCAN_EXTENSIONS = new Set([
+  '.ts',
+  '.mts',
+  '.cts',
+  '.tsx',
+  '.js',
+  '.mjs',
+  '.cjs',
+  '.jsx',
+]);
+
+// Directories never worth scanning for browser-mode hints — generated output,
+// installed deps, VCS metadata. Skipped at every recursion level.
+const VITEST_SCAN_SKIP_DIRS = new Set([
+  'node_modules',
+  'dist',
+  'build',
+  'out',
+  'coverage',
+  '.git',
+  '.next',
+  '.nuxt',
+  '.svelte-kit',
+  '.vite',
+  '.cache',
+]);
+
+/**
+ * Detect whether a package uses vitest's browser mode.
+ *
+ * Upstream `@vitest/browser` injects `optimizeDeps.include` entries of the form
+ * `vitest > expect-type` (and `vitest > @vitest/snapshot > magic-string`,
+ * `vitest > @vitest/expect > chai`). Vite resolves the leading `vitest` segment
+ * from the Vite config root, so `vitest` MUST be resolvable as a package from
+ * the consuming package's directory. In a pnpm strict (non-hoisted) layout,
+ * `vitest` pulled in only transitively via `vite-plus` is NOT reachable from the
+ * package root — the optimizer then fails with `Failed to resolve dependency`
+ * and the browser test page hangs forever.
+ *
+ * When this returns true the migration adds `vitest` as a direct
+ * devDependency so it is hoisted next to the package and the optimizer chain
+ * resolves. The signal is any of the package's TS/JS files (config, workspace
+ * config under any name, or test file) referencing `@vitest/browser*` or
+ * `vite-plus/test/browser*`. The scan recurses through the package directory
+ * (skipping `node_modules`, build output, VCS metadata) so browser config in a
+ * non-standard filename or browser imports in test files are all caught.
+ *
+ * Recursion stops at nested `package.json` boundaries: a workspace sub-package
+ * is a separate package that the migration scans on its own pass, so the root
+ * package must not inherit a browser-mode signal from a sub-package.
+ */
+function sourceTreeReferencesAny(projectPath: string, hints: readonly string[]): boolean {
+  const matchesHint = (content: string): boolean => hints.some((hint) => content.includes(hint));
+
+  const scanDir = (dir: string, isRoot: boolean): boolean => {
+    let entries: fs.Dirent[];
+    try {
+      entries = fs.readdirSync(dir, { withFileTypes: true });
+    } catch {
+      return false;
+    }
+    // A nested package.json marks a separate workspace package — it is migrated
+    // (and scanned) on its own pass, so don't let its files leak into this one.
+    if (!isRoot && entries.some((e) => e.isFile() && e.name === 'package.json')) {
+      return false;
+    }
+    for (const entry of entries) {
+      const entryPath = path.join(dir, entry.name);
+      if (entry.isDirectory()) {
+        if (VITEST_SCAN_SKIP_DIRS.has(entry.name)) {
+          continue;
+        }
+        if (scanDir(entryPath, false)) {
+          return true;
+        }
+      } else if (entry.isFile() && VITEST_SCAN_EXTENSIONS.has(path.extname(entry.name))) {
+        try {
+          if (matchesHint(fs.readFileSync(entryPath, 'utf8'))) {
+            return true;
+          }
+        } catch {
+          // Unreadable file — ignore and keep scanning.
+        }
+      }
+    }
+    return false;
+  };
+
+  return scanDir(projectPath, true);
+}
+
+function usesVitestBrowserMode(projectPath: string): boolean {
+  return sourceTreeReferencesAny(projectPath, VITEST_BROWSER_SPECIFIER_HINTS);
+}
+
+// Source-only signal that a package targets the WEBDRIVERIO provider — used to
+// allow the edgedriver/geckodriver builds even when no dep is declared yet (the
+// webdriverio-specific postinstall hazard; playwright has no such drivers). See
+// `usesVitestBrowserMode` for the shared traversal semantics (extensions, skip
+// dirs, nested-package boundary).
+function usesWebdriverioProvider(projectPath: string): boolean {
+  return sourceTreeReferencesAny(projectPath, WEBDRIVERIO_PROVIDER_SPECIFIER_HINTS);
+}
+
+// Source-scan signal per opt-in browser provider, used to inject the (opt-in,
+// no-longer-bundled) provider + its framework peer even when no dep is declared
+// yet (e.g. a `vite.config.ts` importing the provider via a `vite-plus/test`
+// shim). Mirrors `usesWebdriverioProvider`'s scan for each provider.
+function collectProviderSourceModes(projectPath: string): Record<string, boolean> {
+  const modes: Record<string, boolean> = {};
+  for (const provider of OPT_IN_BROWSER_PROVIDERS) {
+    modes[provider] = sourceTreeReferencesAny(
+      projectPath,
+      BROWSER_PROVIDER_SPECIFIER_HINTS[provider],
+    );
+  }
+  return modes;
+}
+
 export function rewritePackageJson(
   pkg: {
     scripts?: Record<string, string>;
@@ -2498,6 +3846,11 @@ export function rewritePackageJson(
   isMonorepo?: boolean,
   skipStagedMigration?: boolean,
   catalogDependencyResolver?: CatalogDependencyResolver,
+  vitestBrowserMode?: boolean,
+  // Source-scan signal per opt-in browser provider name (e.g.
+  // `@vitest/browser-webdriverio` → true). A provider with no dep declared but
+  // imported in source still gets kept/injected.
+  providerSourceModes?: Partial<Record<string, boolean>>,
 ): Record<string, string | string[]> | null {
   if (pkg.scripts) {
     const updated = rewriteScripts(
@@ -2528,6 +3881,14 @@ export function rewritePackageJson(
     { dependencyField: 'peerDependencies', dependencies: pkg.peerDependencies },
     { dependencyField: 'optionalDependencies', dependencies: pkg.optionalDependencies },
   ];
+  // Scrub stale `npm:@voidzero-dev/vite-plus-test@...` aliases left over from
+  // earlier vite-plus migrations — the wrapper package no longer exists, so
+  // these entries would break `pnpm install`. Real user ranges are preserved.
+  for (const { dependencies } of dependencyGroups) {
+    if (pruneLegacyWrapperAliases(dependencies)) {
+      needVitePlus = true;
+    }
+  }
   for (const [key, version] of Object.entries(VITE_PLUS_OVERRIDE_PACKAGES)) {
     for (const { dependencyField, dependencies } of dependencyGroups) {
       if (dependencies?.[key]) {
@@ -2541,6 +3902,29 @@ export function rewritePackageJson(
       }
     }
   }
+  // Force-override mode (ecosystem CI / `vp create` E2E) must re-pin any
+  // pre-existing `vite-plus` range to the local tgz. Otherwise pnpm reads the
+  // published vite-plus metadata for transitive dep resolution (e.g.
+  // `@voidzero-dev/vite-plus-test`) even though the override replaces the
+  // vite-plus package itself, dragging the stale wrapper into node_modules.
+  if (isForceOverrideMode()) {
+    for (const { dependencies } of dependencyGroups) {
+      if (dependencies?.[VITE_PLUS_NAME]) {
+        dependencies[VITE_PLUS_NAME] = VITE_PLUS_VERSION;
+        needVitePlus = true;
+      }
+    }
+  }
+  // Capture browser-mode signal from the original deps BEFORE the removal loop
+  // strips them. A package can drive vitest browser mode purely through config
+  // (`test.browser.provider: 'playwright'` in `vite.config.ts`) without ever
+  // importing `@vitest/browser*` in source — the provider package is listed in
+  // devDependencies but vitest loads it by name. The source-scan signal
+  // (`usesVitestBrowserMode`) misses this case; the dep declaration is the
+  // authoritative intent signal.
+  const hasBrowserDepSignal = VITEST_BROWSER_DEP_NAMES.some((name) =>
+    dependencyGroups.some(({ dependencies }) => dependencies?.[name] !== undefined),
+  );
   // remove packages that are replaced with vite-plus
   for (const name of REMOVE_PACKAGES) {
     let wasRemoved = false;
@@ -2567,6 +3951,94 @@ export function rewritePackageJson(
       pkg.devDependencies[peerDep] = '*';
     }
   }
+  // The browser providers (webdriverio, playwright) are opt-in: vite-plus no
+  // longer bundles them at runtime (each drags a heavy non-optional framework
+  // peer), so a user targeting a provider must own it themselves for the
+  // rewritten `vite-plus/test/browser-<provider>` import to resolve. Unlike the
+  // rest of the `@vitest/*` family they are deliberately NOT in
+  // VITE_PLUS_OVERRIDE_PACKAGES (so projects not using a provider stay
+  // untouched), which means the normalization loop above does not pin them. We
+  // pin each used provider here, to a CONCRETE version (no catalog entry is
+  // written for an opt-in provider) so it self-resolves and stays aligned with
+  // the bundled vitest, and we ensure its runtime framework peer
+  // (`webdriverio` / `playwright`). (`@vitest/browser`/preview stay bundled +
+  // stripped, handled in the REMOVE_PACKAGES loop above.)
+  let usesAnyOptInProvider = false;
+  for (const provider of OPT_IN_BROWSER_PROVIDERS) {
+    const usesProvider =
+      providerSourceModes?.[provider] ||
+      dependencyGroups.some(({ dependencies }) => dependencies?.[provider] !== undefined);
+    if (!usesProvider) {
+      continue;
+    }
+    usesAnyOptInProvider = true;
+    // The provider must be INSTALLED (in deps/devDeps/optionalDeps, not merely a
+    // peer) for the rewritten `vite-plus/test/browser-<provider>` import to
+    // resolve. Normalize an existing install-group declaration to the bundled
+    // vitest version in place (the override loop above no longer pins it);
+    // otherwise — a source-only or peer-only user — inject it into devDeps.
+    const installGroup = [pkg.dependencies, pkg.devDependencies, pkg.optionalDependencies].find(
+      (deps) => deps?.[provider] !== undefined,
+    );
+    if (installGroup) {
+      installGroup[provider] = VITEST_VERSION;
+    } else {
+      pkg.devDependencies ??= {};
+      pkg.devDependencies[provider] = VITEST_VERSION;
+    }
+    const peer = BROWSER_PROVIDER_PEER_DEPS[provider]; // 'webdriverio' / 'playwright'
+    const peerPresent =
+      pkg.dependencies?.[peer] ??
+      pkg.devDependencies?.[peer] ??
+      pkg.peerDependencies?.[peer] ??
+      pkg.optionalDependencies?.[peer];
+    if (peer && !peerPresent) {
+      pkg.devDependencies ??= {};
+      pkg.devDependencies[peer] = '*';
+    }
+    needVitePlus = true;
+  }
+  // An opt-in browser provider drags in its OWN `@vitest/browser → @vitest/mocker`
+  // subtree that is distinct from the one vite-plus bundles, so npm's flat
+  // node_modules cannot dedupe the two and leaves several nested `@vitest/mocker`
+  // copies. `@vitest/mocker/dist/node.js` statically `import`s `vite` (its `vite`
+  // peer is optional, so install never errors), and the `vite` override only lands
+  // deep inside the `vitest` subtree — unreachable from the nested provider chain.
+  // The result is `ERR_MODULE_NOT_FOUND: Cannot find package 'vite'` when loading
+  // the browser config. Mirror the override as a direct `vite` devDep (as the bun
+  // branch already does for its own resolver) so npm hoists a single top-level
+  // `node_modules/vite` that every nested `@vitest/mocker` resolves. Gated on
+  // provider usage so non-browser (node-mode) projects — which dedupe cleanly and
+  // need no direct `vite` — stay untouched. pnpm/yarn use symlink/PnP layouts that
+  // already expose the override to the provider subtree, so this is npm-only.
+  if (usesAnyOptInProvider && packageManager === PackageManager.npm) {
+    const viteOverride = VITE_PLUS_OVERRIDE_PACKAGES.vite;
+    const viteAlreadyDirect =
+      pkg.dependencies?.vite ?? pkg.devDependencies?.vite ?? pkg.optionalDependencies?.vite;
+    if (viteOverride && !viteAlreadyDirect) {
+      pkg.devDependencies ??= {};
+      pkg.devDependencies.vite = viteOverride;
+      needVitePlus = true;
+    }
+  }
+  // Promote dep-derived signal to the same flag the source-scan feeds, so the
+  // downstream "add direct `vitest`" branch fires for config-only browser-mode
+  // setups too.
+  const effectiveBrowserMode = vitestBrowserMode || hasBrowserDepSignal;
+  // Trigger vite-plus install when a project has a vitest-adjacent package
+  // (e.g. `vitest-browser-svelte`) that declares vitest as a peer dep — even
+  // if the project has no vite/oxlint/tsdown dep to migrate. The peer dep is
+  // satisfied by the upstream vitest that vite-plus bundles as a direct dep.
+  // Note: peerDependencies count as "adjacent signal" but NOT as installed.
+  const installableNames = [
+    ...Object.keys(pkg.dependencies ?? {}),
+    ...Object.keys(pkg.devDependencies ?? {}),
+    ...Object.keys(pkg.optionalDependencies ?? {}),
+  ];
+  const adjacentSignals = [...installableNames, ...Object.keys(pkg.peerDependencies ?? {})];
+  const isVitestAdjacent =
+    !installableNames.includes('vitest') &&
+    adjacentSignals.some((name) => name !== 'vitest' && name.includes('vitest'));
   // Normalize a pre-existing pinned vite-plus so sub-packages don't drift
   // from siblings: in catalog-supporting monorepos that's `catalog:`, under
   // force-override (file:) it's the tgz path. Preserve protocol-prefixed
@@ -2581,19 +4053,42 @@ export function rewritePackageJson(
     supportCatalog &&
     existingVitePlus !== canonicalVitePlusSpec &&
     !isProtocolPinnedSpec(existingVitePlus);
+  // vitest-adjacent / browser-mode signals only trigger a vite-plus INSTALL when the
+  // project doesn't already have vite-plus — otherwise vite-plus is already present and
+  // re-adding it would be churn. (The direct `vitest` pin those signals also require is
+  // decided separately below, independent of whether vite-plus is present.)
+  if (!existingVitePlus && (isVitestAdjacent || effectiveBrowserMode)) {
+    needVitePlus = true;
+  }
+  // Browser mode AND a vitest-adjacent dep (e.g. `vitest-browser-svelte`, which
+  // declares a non-optional `vitest` peer) both need a direct `vitest` pin INDEPENDENT
+  // of whether `vite-plus` is already present: that peer must resolve from the package's
+  // OWN root under pnpm strict / Yarn PnP, where `vite-plus`'s transitive `vitest` is not
+  // visible. Tracked separately from `needVitePlus` so the pin is added without re-adding
+  // an already-present `vite-plus` — e.g. a monorepo root, where
+  // `rewriteRootWorkspacePackageJson` injects `vite-plus` BEFORE this runs (so
+  // `existingVitePlus` is already truthy here), or a re-migration of a project that
+  // already owns it. The guard below still no-ops when a direct `vitest` already exists,
+  // so a genuine normalize pass of an already-correct project mutates nothing.
+  const needDirectVitest = needVitePlus || effectiveBrowserMode || isVitestAdjacent;
   if (needVitePlus || shouldNormalizeExistingVitePlus) {
     pkg.devDependencies = {
       ...pkg.devDependencies,
       [VITE_PLUS_NAME]: canonicalVitePlusSpec,
     };
   }
-  // Add vitest to devDependencies when a remaining dependency likely peer-depends
-  // on vitest (e.g., vitest-browser-svelte). Without this, pnpm resolves the real
-  // vitest for peer deps instead of @voidzero-dev/vite-plus-test, causing
-  // third-party type augmentations to target the wrong module. Gated by
-  // needVitePlus (something actually changed) — a pure normalize pass must not
+  // Add `vitest` as a direct devDependency when:
+  //  - a remaining dependency likely peer-depends on vitest (e.g.
+  //    vitest-browser-svelte), OR
+  //  - the package runs vitest browser mode (`@vitest/browser` needs
+  //    `vitest` resolvable from the package root — see usesVitestBrowserMode).
+  // Vite-plus already bundles upstream vitest as a direct dep, but a strict
+  // pnpm / yarn Plug'n'Play layout will not expose that transitive `vitest`
+  // to the package. Pinning it here points the dep at the same upstream
+  // version vite-plus ships with. Gated by needDirectVitest (browser-mode /
+  // vitest-adjacent, or some other change) — a pure normalize pass must not
   // mutate the project beyond the vite-plus spec.
-  if (needVitePlus) {
+  if (needDirectVitest) {
     const installableDeps = {
       ...pkg.dependencies,
       ...pkg.devDependencies,
@@ -2601,11 +4096,14 @@ export function rewritePackageJson(
     };
     if (
       !installableDeps.vitest &&
-      Object.keys(installableDeps).some((name) => name.includes('vitest'))
+      (effectiveBrowserMode || Object.keys(installableDeps).some((name) => name.includes('vitest')))
     ) {
-      const ver = VITE_PLUS_OVERRIDE_PACKAGES.vitest;
       pkg.devDependencies ??= {};
-      pkg.devDependencies.vitest = getCatalogDependencySpec(undefined, ver, supportCatalog);
+      pkg.devDependencies.vitest = getCatalogDependencySpec(
+        undefined,
+        VITEST_VERSION,
+        supportCatalog,
+      );
     }
   }
   return extractedStagedConfig;
diff --git a/packages/cli/src/oxlint-plugin.ts b/packages/cli/src/oxlint-plugin.ts
index f71e1f94db..25ca9c2983 100644
--- a/packages/cli/src/oxlint-plugin.ts
+++ b/packages/cli/src/oxlint-plugin.ts
@@ -6,6 +6,22 @@ import {
   VITE_PLUS_OXLINT_PLUGIN_NAME,
 } from './oxlint-plugin-config.ts';
 
+// `declare module 'vitest…'` and `declare module '@vitest/browser…'` are
+// intentionally preserved by `vp migrate` (see migration's import_rewriter and
+// docs/guide/migrate.md) — `vite-plus/test*` is a thin re-export of upstream
+// `vitest*`, so type augmentations have to target the upstream module identity
+// to merge correctly. Autofixing those module declarations here would split the
+// augmentation away from what imports actually resolve through.
+function isVitestFamilyDeclareModuleSpecifier(specifier: string): boolean {
+  return (
+    specifier === 'vitest' ||
+    specifier.startsWith('vitest/') ||
+    specifier === '@vitest/browser' ||
+    specifier.startsWith('@vitest/browser/') ||
+    specifier.startsWith('@vitest/browser-')
+  );
+}
+
 function rewriteVitePlusImportSpecifier(specifier: string): string | null {
   if (specifier === 'vite') {
     return 'vite-plus';
@@ -23,6 +39,17 @@ function rewriteVitePlusImportSpecifier(specifier: string): string | null {
     return 'vite-plus/test';
   }
 
+  // `vitest/package.json` is a metadata-access pattern (reading the vitest
+  // version) and `vite-plus`'s generated exports map deliberately omits
+  // `./test/package.json` (see `syncTestPackageExports()` in build.ts, which
+  // skips upstream's `./package.json`). Rewriting it would yield
+  // `vite-plus/test/package.json`, which fails with ERR_PACKAGE_PATH_NOT_EXPORTED.
+  // The original specifier still resolves through the installed `vitest`. This
+  // mirrors the migrate rewriter's exclusion in import_rewriter.rs.
+  if (specifier === 'vitest/package.json') {
+    return null;
+  }
+
   if (specifier.startsWith('vitest/')) {
     return `vite-plus/test/${specifier.slice('vitest/'.length)}`;
   }
@@ -31,10 +58,15 @@ function rewriteVitePlusImportSpecifier(specifier: string): string | null {
     return 'vite-plus/test/browser';
   }
 
+  // `@vitest/browser/context` keeps the nested path (vite-plus exports
+  // `./test/browser/context`); the remaining subpaths are exposed only at the
+  // bare `./test/<name>` surface, so the `/browser/` segment is dropped.
   const browserSubpathRewrites: Record<string, string> = {
     '@vitest/browser/context': 'vite-plus/test/browser/context',
     '@vitest/browser/client': 'vite-plus/test/client',
     '@vitest/browser/locators': 'vite-plus/test/locators',
+    '@vitest/browser/matchers': 'vite-plus/test/matchers',
+    '@vitest/browser/utils': 'vite-plus/test/utils',
   };
   if (specifier in browserSubpathRewrites) {
     return browserSubpathRewrites[specifier];
@@ -129,7 +161,15 @@ export const preferVitePlusImportsRule = defineRule({
         if (node.global) {
           return;
         }
-        maybeReportLiteral(context, node.id);
+        const id = node.id;
+        if (
+          id?.type === 'Literal' &&
+          typeof id.value === 'string' &&
+          isVitestFamilyDeclareModuleSpecifier(id.value)
+        ) {
+          return;
+        }
+        maybeReportLiteral(context, id);
       },
     };
   },
diff --git a/packages/cli/src/pack-bin.ts b/packages/cli/src/pack-bin.ts
index f5bd6da041..03c2e8f56c 100644
--- a/packages/cli/src/pack-bin.ts
+++ b/packages/cli/src/pack-bin.ts
@@ -13,43 +13,60 @@ import { cac } from 'cac';
 
 import { resolveViteConfig } from './resolve-vite-config.ts';
 
+// Matches a `.d.ts` / `.d.mts` / `.d.cts` importer.
+const RE_DTS = /\.d\.[cm]?ts$/;
+// Bare specifier for postcss / lightningcss / vitest / `@vitest/*` /
+// vite-plus (root or any subpath).
+const EXTERNAL_DTS_PKG_RE = /^(?:postcss|lightningcss|vitest|@vitest\/[^/]+|vite-plus)(?:\/|$)/;
+
 /**
- * Rolldown plugin that transforms value imports/exports to type-only in external
- * packages' .d.ts files. Some packages (e.g. postcss, lightningcss) use
- * `import { X }` and `export { X } from` instead of their type-only equivalents,
- * which causes MISSING_EXPORT warnings from the DTS bundler.
+ * Rolldown plugin that keeps `postcss` / `lightningcss` / `vitest` / `@vitest/*`
+ * / `vite-plus` external to the DTS bundle.
+ *
+ * The DTS bundler resolves these packages and tries to inline their `.d.ts`
+ * files. postcss ships its public types as a CJS `export = postcss` over a
+ * `declare namespace postcss { export { AtRule, ... } }` (see
+ * postcss/lib/postcss.d.ts), and its ESM types entry (postcss.d.mts) re-exports
+ * those names with `export { AtRule, ... } from './postcss.js'`. The bundler
+ * cannot map named imports onto an `export =`'d namespace's members, so every
+ * consumer `import type { AtRule } from 'postcss'` becomes a MISSING_EXPORT
+ * error.
+ *
+ * vitest fails for a different reason: `vitest@4.1.9`'s `dist/index.d.ts`
+ * re-exports `ExpectPollOptions` from `@vitest/expect`, but `@vitest/expect`
+ * does not actually export that name. `tsc` tolerates the dangling re-export
+ * because it only resolves re-exports lazily on use, but the DTS bundler
+ * eagerly resolves every re-export while inlining and so hits the missing
+ * export. `@vitest/browser/matchers.d.ts` then re-imports the same name from
+ * `vitest`, propagating the failure. Established vite-plus projects reach
+ * these files through the `vite-plus/test*` shims, which re-export `vitest` /
+ * `@vitest/*` from declaration files.
  *
- * Since .d.ts files contain only type information, all imports/exports are
- * inherently type-only, so this transformation is always safe.
+ * `vite-plus` itself (root and `vite-plus/test*` shims) is kept external so the
+ * PUBLIC specifier survives in the emitted declarations. If the shim were
+ * inlined instead, its private `vitest` / `@vitest/*` re-exports would be the
+ * ones externalized above and the published `.d.ts` would carry bare `vitest`
+ * specifiers — unresolvable for consumers under strict pnpm / Yarn PnP layouts,
+ * where `vitest` is a dependency of `vite-plus` and not of the packed package.
+ *
+ * Marking these packages external for `.d.ts` importers leaves the import
+ * untouched in the emitted declarations (`import type { AtRule } from 'postcss'`),
+ * which is how third-party packages should be treated in a DTS bundle anyway.
+ * They are only externalized when imported *from a declaration file*, so runtime
+ * bundling is unaffected.
  */
-const EXTERNAL_DTS_INTERNAL_RE = /node_modules\/(postcss|lightningcss)\/.*\.d\.(ts|mts|cts)$/;
-// Match consumer .d.ts files that import from postcss/lightningcss.
-// In CI (installed from tgz): node_modules/vite-plus-core/dist/...
-// In local development (symlinked workspace): packages/core/dist/...
-const EXTERNAL_DTS_CONSUMER_RE =
-  /(?:vite-plus-core|packages\/core)\/.*lightningcssOptions\.d\.ts$|(?:vite-plus-core|packages\/core)\/dist\/.*\.d\.ts$/;
-const EXTERNAL_DTS_FIX_RE = new RegExp(
-  `${EXTERNAL_DTS_INTERNAL_RE.source}|${EXTERNAL_DTS_CONSUMER_RE.source}`,
-);
-
 function externalDtsTypeOnlyPlugin() {
   return {
     name: 'vite-plus:external-dts-type-only',
-    transform: {
-      filter: { id: { include: [EXTERNAL_DTS_FIX_RE] } },
-      handler(code: string, rawId: string) {
+    resolveId: {
+      order: 'pre' as const,
+      handler(id: string, importer: string | undefined) {
         // Normalize Windows backslash paths to forward slashes for regex matching
-        const id = rawId.replaceAll('\\', '/');
-        if (EXTERNAL_DTS_INTERNAL_RE.test(id)) {
-          // postcss/lightningcss internal files: transform imports only
-          // (exports may include value re-exports like `export const Features`)
-          return code.replace(/^(import\s+)(?!type\s)/gm, 'import type ');
+        const normalizedImporter = importer?.replaceAll('\\', '/');
+        if (normalizedImporter && RE_DTS.test(normalizedImporter) && EXTERNAL_DTS_PKG_RE.test(id)) {
+          return { id, external: true };
         }
-        // Consumer files: only transform imports from postcss/lightningcss
-        return code.replace(
-          /^(import\s+)(?!type\s)(.+from\s+['"](?:postcss|lightningcss)['"])/gm,
-          'import type $2',
-        );
+        return undefined;
       },
     },
   };
@@ -143,8 +160,7 @@ cli
         : [viteConfig.pack ?? {}];
       for (const packConfig of packConfigs) {
         const merged = { ...packConfig, ...flags };
-        // Inject plugin to fix MISSING_EXPORT warnings from external .d.ts files
-        // (postcss, lightningcss use `import`/`export` instead of `import type`/`export type`)
+        // Keep postcss/lightningcss external to the dts bundle (see plugin doc)
         if (merged.dts) {
           const existingPlugins = Array.isArray(merged.plugins) ? merged.plugins : [];
           merged.plugins = [...existingPlugins, externalDtsTypeOnlyPlugin()];
diff --git a/packages/cli/src/resolve-test.ts b/packages/cli/src/resolve-test.ts
index 8c547c8d3a..42e4d6f850 100644
--- a/packages/cli/src/resolve-test.ts
+++ b/packages/cli/src/resolve-test.ts
@@ -2,15 +2,22 @@
  * Vitest tool resolver for the vite-plus CLI.
  *
  * This module exports a function that resolves the Vitest binary path
- * to the bundled Vitest in the CLI distribution. The resolved path is
- * passed back to the Rust core, which then executes Vitest for running tests.
+ * to the vitest package shipped with the CLI (falling back to the user's
+ * project copy only if the bundled one is unreachable). The resolved path
+ * is passed back to the Rust core, which then executes Vitest for running
+ * tests.
  *
  * Used for: `vite-plus test` command
  */
 
+import { readFileSync } from 'node:fs';
 import { dirname, join } from 'node:path';
 
-import { DEFAULT_ENVS, resolve } from './utils/constants.ts';
+import { DEFAULT_ENVS, resolveBundled } from './utils/constants.ts';
+
+interface VitestPackageJson {
+  bin?: string | Record<string, string>;
+}
 
 /**
  * Resolves the Vitest binary path and environment variables.
@@ -21,13 +28,24 @@ import { DEFAULT_ENVS, resolve } from './utils/constants.ts';
  *
  * Vitest is Vite's testing framework that provides a Jest-compatible
  * testing experience with Vite's fast HMR and transformation pipeline.
- * The function points to the bundled Vitest in the CLI's dist directory.
+ * The function resolves the bundled vitest shipped with the CLI first,
+ * so the runner matches the Vitest that `vite-plus/test*` imports resolve
+ * to; it falls back to the project copy only if the bundled one is
+ * unreachable. See `resolveBundled` for the rationale (avoiding dual-copy
+ * Vitest internal-state / mock-hoisting mismatches).
  */
 export async function test(): Promise<{
   binPath: string;
   envs: Record<string, string>;
 }> {
-  const binPath = join(dirname(resolve('@voidzero-dev/vite-plus-test')), 'dist', 'cli.js');
+  const pkgJsonPath = resolveBundled('vitest/package.json');
+  const pkgRoot = dirname(pkgJsonPath);
+  const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as VitestPackageJson;
+  const binRel = typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.vitest;
+  if (!binRel) {
+    throw new Error(`Could not find 'vitest' bin entry in ${pkgJsonPath}`);
+  }
+  const binPath = join(pkgRoot, binRel);
 
   return {
     binPath,
diff --git a/packages/cli/src/utils/constants.ts b/packages/cli/src/utils/constants.ts
index 595a1e70fb..2ae6ba9c52 100644
--- a/packages/cli/src/utils/constants.ts
+++ b/packages/cli/src/utils/constants.ts
@@ -3,13 +3,40 @@ import { createRequire } from 'node:module';
 export const VITE_PLUS_NAME = 'vite-plus';
 export const VITE_PLUS_VERSION = process.env.VP_VERSION || 'latest';
 
+export const VITEST_VERSION = '4.1.9';
+
 export const VITE_PLUS_OVERRIDE_PACKAGES: Record<string, string> = process.env.VP_OVERRIDE_PACKAGES
   ? JSON.parse(process.env.VP_OVERRIDE_PACKAGES)
   : {
       vite: 'npm:@voidzero-dev/vite-plus-core@latest',
-      vitest: 'npm:@voidzero-dev/vite-plus-test@latest',
+      // Pin `vitest` only. The `@vitest/*` family (expect, runner, snapshot, spy,
+      // utils, mocker, pretty-format) are EXACT (`4.1.9`) dependencies of `vitest`
+      // itself, so a single `vitest` override cascades one consistent version to
+      // the whole tree — overriding the indirect deps individually is redundant.
+      // Coverage providers (`@vitest/coverage-v8` / `-istanbul`) are vitest PEER
+      // deps the project installs and versions itself — vite-plus never adds,
+      // pins, or overrides them. The runtime guard in `define-config.ts` only
+      // fail-fasts when an installed provider's version skews from the bundled
+      // vitest (Vitest would otherwise silently run mixed versions).
+      vitest: VITEST_VERSION,
     };
 
+/**
+ * Package-name patterns the migrator exempts from a package manager's
+ * "minimum release age" gate (pnpm `minimumReleaseAgeExclude` / Yarn
+ * `npmPreapprovedPackages`).
+ *
+ * Vite+ pins `vitest` to an exact, sometimes freshly published version, and the
+ * in-tree `@vitest/*` siblings install transitively at that same version, so an
+ * age gate would otherwise quarantine the Vite+-managed family and break
+ * `vp install`. The `@vitest/*` glob also covers the optional `@vitest/browser-*`
+ * peers the migrator pins for browser projects. This does NOT pin or manage any
+ * package — it only lets the chosen versions through the user's gate, including
+ * the `@vitest/coverage-*` version the coverage guard asks the user to align to
+ * the bundled vitest.
+ */
+export const VITEST_AGE_GATE_EXEMPT_PACKAGES = ['vitest', '@vitest/*'] as const;
+
 /**
  * When VP_FORCE_MIGRATE is set, force full dependency rewriting
  * even for projects already using vite-plus. Used by ecosystem CI to
@@ -27,6 +54,26 @@ export function resolve(path: string) {
   });
 }
 
+/**
+ * Like {@link resolve}, but prefers the copy shipped with the CLI
+ * (`import.meta.dirname`) over the project's (`process.cwd()`).
+ *
+ * Use this for runtime modules that MUST match what `vite-plus/test*`
+ * imports resolve to — chiefly the Vitest runner binary. The `vite-plus/test`
+ * shims `export * from 'vitest'`, which Node resolves to vite-plus's own
+ * bundled (pinned) Vitest. If `vp test` instead spawned a project-local
+ * Vitest (a different physical copy/version), the runner and the imported
+ * `vi`/`expect`/runner internals would come from two distinct Vitest
+ * modules — a classic source of Vitest internal-state and mock-hoisting
+ * mismatches. `process.cwd()` stays as a fallback for layouts where the
+ * bundled copy is somehow unreachable, so this is never worse than {@link resolve}.
+ */
+export function resolveBundled(path: string) {
+  return require.resolve(path, {
+    paths: [import.meta.dirname, process.cwd()],
+  });
+}
+
 export const BASEURL_TSCONFIG_WARNING =
   'Skipped typeAware/typeCheck: a tsconfig file contains baseUrl which is not yet supported by the oxlint type checker.\n' +
   '  Run `vp dlx @andrewbranch/ts5to6 --fixBaseUrl <tsconfig path>` to remove baseUrl from your tsconfig.';
diff --git a/packages/test/.gitignore b/packages/test/.gitignore
deleted file mode 100644
index 7f727e288b..0000000000
--- a/packages/test/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*.d.ts
-*.d.cts
-/LICENSE
-LICENSE.md
-*.cjs
-*.mjs
-browser/
-dist/
diff --git a/packages/test/BUNDLING.md b/packages/test/BUNDLING.md
deleted file mode 100644
index 4866da58e1..0000000000
--- a/packages/test/BUNDLING.md
+++ /dev/null
@@ -1,427 +0,0 @@
-# Test Package Bundling Architecture
-
-This document explains how `@voidzero-dev/vite-plus-test` bundles vitest and its dependencies.
-
-## Overview
-
-The test package uses a **hybrid bundling strategy**:
-
-1. **COPY** all `@vitest/*` packages (preserves browser/Node.js separation)
-2. **BUNDLE** only leaf dependencies like `chai`, `pathe` (reduces install size)
-3. **Separate entries** (`index.js` vs `index-node.js`) prevent Node.js code from loading in browsers
-
-This approach avoids the critical issue of Rolldown creating shared chunks that mix Node.js-only code (like `__vite__injectQuery`) with browser code, which causes runtime crashes.
-
-## Dependencies Classification
-
-### Copied Packages (`dist/@vitest/`)
-
-These 11 `@vitest/*` packages are **copied** (not bundled) to preserve their original file structure:
-
-| Package                       | Purpose                                              |
-| ----------------------------- | ---------------------------------------------------- |
-| `@vitest/runner`              | Test runner core                                     |
-| `@vitest/utils`               | Utilities (source-map, error, display, timers, etc.) |
-| `@vitest/spy`                 | Spy/mock implementation                              |
-| `@vitest/expect`              | Assertion library                                    |
-| `@vitest/snapshot`            | Snapshot testing                                     |
-| `@vitest/mocker`              | Module mocking (node, browser, automock)             |
-| `@vitest/pretty-format`       | Output formatting                                    |
-| `@vitest/browser`             | Browser testing support                              |
-| `@vitest/browser-playwright`  | Playwright integration                               |
-| `@vitest/browser-webdriverio` | WebdriverIO integration                              |
-| `@vitest/browser-preview`     | Preview (testing-library) integration                |
-
-**Why copy instead of bundle?** Bundling would create shared chunks that mix browser-safe and Node.js-only code. Copying preserves the original separation.
-
-### Bundled Leaf Dependencies (`dist/vendor/`)
-
-These packages are bundled using Rolldown into `dist/vendor/*.mjs`:
-
-| Package               | Purpose                             |
-| --------------------- | ----------------------------------- |
-| `chai`                | Assertion library (core of expect)  |
-| `pathe`               | Path utilities                      |
-| `tinyrainbow`         | Terminal colors                     |
-| `magic-string`        | String manipulation for source maps |
-| `estree-walker`       | AST traversal                       |
-| `why-is-node-running` | Debug tool for hanging processes    |
-
-These were moved from `dependencies` to `devDependencies` since they're bundled.
-
-### Runtime Dependencies (NOT Bundled)
-
-These remain in `dependencies` and are installed with the package:
-
-| Package           | Reason Not Bundled                            |
-| ----------------- | --------------------------------------------- |
-| `sirv`            | Static file server - complex runtime behavior |
-| `ws`              | WebSocket server - native bindings            |
-| `pixelmatch`      | Image comparison - optional feature           |
-| `pngjs`           | PNG handling - optional feature               |
-| `es-module-lexer` | ESM parsing - small, used at runtime          |
-| `expect-type`     | Type testing - small                          |
-| `obug`            | Debugging - small                             |
-| `picomatch`       | Glob matching - small                         |
-| `std-env`         | Environment detection - small                 |
-| `tinybench`       | Benchmarking - optional feature               |
-| `tinyexec`        | Command execution - small                     |
-| `tinyglobby`      | File globbing - small                         |
-
-### External Blocklist (Must NOT Bundle)
-
-These packages are explicitly kept external in `EXTERNAL_BLOCKLIST` during the Rolldown build:
-
-| Package                 | Reason                                    |
-| ----------------------- | ----------------------------------------- |
-| `playwright`            | Native bindings, user must install        |
-| `webdriverio`           | Native bindings, user must install        |
-| `debug`                 | Environment detection breaks when bundled |
-| `happy-dom`             | Optional peer dependency                  |
-| `jsdom`                 | Optional peer dependency                  |
-| `@edge-runtime/vm`      | Optional peer dependency                  |
-| `@standard-schema/spec` | Types-only import from @vitest/expect     |
-| `msw`, `msw/*`          | Optional peer dependency for mocking      |
-
-### Browser Plugin Exclude List
-
-Additionally, these packages are added to the **browser plugin's exclude list** (in `patchVitestBrowserPackage`), which prevents Vite's optimizer from bundling them during browser tests:
-
-| Package               | Reason                                           |
-| --------------------- | ------------------------------------------------ |
-| `lightningcss`        | Native bindings                                  |
-| `@tailwindcss/oxide`  | Native bindings                                  |
-| `tailwindcss`         | Pulls in @tailwindcss/oxide                      |
-| `@vitest/browser`     | Needs vendor-aliases plugin resolution           |
-| `@vitest/ui`          | Optional peer dependency                         |
-| `@vitest/mocker/node` | Imports @voidzero-dev/vite-plus-core (Node-only) |
-
-This is a different mechanism than `EXTERNAL_BLOCKLIST` - it controls runtime optimization, not build-time bundling.
-
----
-
-## Migration Guide
-
-For maintainers developing the vitest/vite migration feature, here are the transformations needed.
-
-### Import Rewrites
-
-| Original Import                      | Rewritten Import                                          |
-| ------------------------------------ | --------------------------------------------------------- |
-| `from "@vitest/browser-playwright"`  | `from "@voidzero-dev/vite-plus-test/browser-playwright"`  |
-| `from "@vitest/browser-webdriverio"` | `from "@voidzero-dev/vite-plus-test/browser-webdriverio"` |
-| `from "@vitest/browser-preview"`     | `from "@voidzero-dev/vite-plus-test/browser-preview"`     |
-| `from "vite"`                        | `from "@voidzero-dev/vite-plus-core"`                     |
-| `from "vite/module-runner"`          | `from "@voidzero-dev/vite-plus-core/module-runner"`       |
-| `import('vitest')`                   | `import('@voidzero-dev/vite-plus-test')`                  |
-
-**Note**: `@voidzero-dev/vite-plus-core` is the bundled version of upstream vite (Vite v8 beta). See [Core Package Bundling](../core/BUNDLING.md) for details on what it contains.
-
-**Note**: The `import('vitest')` → `import('@voidzero-dev/vite-plus-test')` rewrite is critical for `globals.d.ts`, which declares global types like `typeof import('vitest')['test']`. Without this rewrite, `vitest` is not resolvable from the `@voidzero-dev/vite-plus-test` package context in pnpm's strict `node_modules` layout. TypeScript silently treats unresolved dynamic type imports as `any`, but oxlint's type-aware linting treats them as `error` types, causing `no-unsafe-call` errors. The rewrite turns this into a self-reference that resolves correctly via Node.js package self-referencing.
-
-**Note:** When using pnpm overrides, you have three options for browser provider imports:
-
-- `vitest/browser-playwright` (or `vitest/browser-webdriverio`, `vitest/browser-preview`) - works when `vitest` is overridden to our package (Recommended)
-- `@voidzero-dev/vite-plus-test/browser-playwright` - direct import from test package
-- `vite-plus/test/plugins/browser-playwright` - direct import from CLI package
-
-Importing from `@vitest/browser-*` packages directly requires additional overrides for those specific packages.
-
-### package.json Changes
-
-**Remove these devDependencies** (now bundled):
-
-```json
-{
-  "devDependencies": {
-    "@vitest/browser": "...", // Remove
-    "@vitest/browser-playwright": "...", // Remove (if using playwright)
-    "@vitest/browser-webdriverio": "...", // Remove (if using webdriverio)
-    "@vitest/browser-preview": "...", // Remove (if using testing-library)
-    "@vitest/ui": "..." // Remove (peer dep, not bundled but optional)
-  }
-}
-```
-
-**Add pnpm overrides**:
-
-```yaml
-# pnpm-workspace.yaml
-overrides:
-  vite: 'file:path/to/vite-plus-core.tgz'
-  vitest: 'file:path/to/vite-plus-test.tgz'
-  '@vitest/browser': 'file:path/to/vite-plus-test.tgz'
-  '@vitest/browser-playwright': 'file:path/to/vite-plus-test.tgz'
-  '@vitest/browser-webdriverio': 'file:path/to/vite-plus-test.tgz'
-  '@vitest/browser-preview': 'file:path/to/vite-plus-test.tgz'
-```
-
-Or using npm package names:
-
-```yaml
-overrides:
-  vite: 'npm:@voidzero-dev/vite-plus-core'
-  vitest: 'npm:@voidzero-dev/vite-plus-test'
-  '@vitest/browser': 'npm:@voidzero-dev/vite-plus-test'
-  '@vitest/browser-playwright': 'npm:@voidzero-dev/vite-plus-test'
-  '@vitest/browser-webdriverio': 'npm:@voidzero-dev/vite-plus-test'
-  '@vitest/browser-preview': 'npm:@voidzero-dev/vite-plus-test'
-```
-
-### Config File Updates
-
-```typescript
-// Before (playwright)
-import { playwright } from '@vitest/browser-playwright';
-
-// After - Option 1 (Recommended): Via vitest subpath (works when vitest is overridden)
-import { playwright } from 'vitest/browser-playwright';
-
-// After - Option 2: Direct import from test package
-import { playwright } from '@voidzero-dev/vite-plus-test/browser-playwright';
-
-// After - Option 3: Direct import from CLI package
-import { playwright } from 'vite-plus/test/plugins/browser-playwright';
-```
-
-Similarly for WebdriverIO:
-
-```typescript
-import { webdriverio } from 'vitest/browser-webdriverio';
-```
-
-And for Preview (testing-library):
-
-```typescript
-import { preview } from 'vitest/browser-preview';
-```
-
-### Plugin Exports for pnpm Overrides
-
-The package provides `./plugins/*` exports to enable pnpm overrides for all `@vitest/*` packages:
-
-```
-@vitest/runner              -> @voidzero-dev/vite-plus-test/plugins/runner
-@vitest/utils               -> @voidzero-dev/vite-plus-test/plugins/utils
-@vitest/utils/error         -> @voidzero-dev/vite-plus-test/plugins/utils-error
-@vitest/spy                 -> @voidzero-dev/vite-plus-test/plugins/spy
-@vitest/expect              -> @voidzero-dev/vite-plus-test/plugins/expect
-@vitest/snapshot            -> @voidzero-dev/vite-plus-test/plugins/snapshot
-@vitest/mocker              -> @voidzero-dev/vite-plus-test/plugins/mocker
-@vitest/pretty-format       -> @voidzero-dev/vite-plus-test/plugins/pretty-format
-@vitest/browser             -> @voidzero-dev/vite-plus-test/plugins/browser
-@vitest/browser-playwright  -> @voidzero-dev/vite-plus-test/plugins/browser-playwright
-@vitest/browser-webdriverio -> @voidzero-dev/vite-plus-test/plugins/browser-webdriverio
-@vitest/browser-preview     -> @voidzero-dev/vite-plus-test/plugins/browser-preview
-```
-
----
-
-## Ensuring Bundle Stability
-
-### Build-time Validation
-
-The build script includes `validateExternalDeps()` which:
-
-1. Scans all bundled JS files using `oxc-parser`
-2. Extracts all external import specifiers
-3. Verifies every external dependency is declared in `dependencies` or `peerDependencies`
-4. Reports undeclared externals that would fail at runtime
-
-If this validation fails, the build will report which packages need to be added.
-
-### Testing Strategy
-
-| Test Type                                                       | What It Validates                                  |
-| --------------------------------------------------------------- | -------------------------------------------------- |
-| **Snap tests** (`packages/cli/snap-tests/vitest-browser-mode/`) | Browser mode works after bundling                  |
-| **Ecosystem CI** (`ecosystem-ci/`)                              | Real-world projects work with bundled vitest       |
-| **CI workflows**                                                | Multi-platform validation (Ubuntu, Windows, macOS) |
-
-### Vitest Upgrade Checklist
-
-When upgrading the vitest version:
-
-1. **Update version** in `packages/test/package.json`:
-
-   ```json
-   {
-     "devDependencies": {
-       "vitest-dev": "^NEW_VERSION",
-       "@vitest/runner": "NEW_VERSION",
-       "@vitest/utils": "NEW_VERSION"
-       // ... all @vitest/* packages
-     }
-   }
-   ```
-
-2. **Run build**:
-
-   ```bash
-   pnpm -C packages/test build
-   ```
-
-3. **Check for new externals**: If `validateExternalDeps()` reports new undeclared dependencies:
-   - Add to `dependencies` if it should be installed at runtime
-   - Add to `EXTERNAL_BLOCKLIST` if it should remain external (native bindings, optional)
-   - If it's a new leaf dep, it will be automatically bundled
-
-4. **Run tests**:
-
-   ```bash
-   pnpm test
-   ```
-
-5. **Run ecosystem CI**:
-   ```bash
-   pnpm -C ecosystem-ci test
-   ```
-
-### Common Upgrade Issues
-
-| Issue                   | Cause                          | Solution                                           |
-| ----------------------- | ------------------------------ | -------------------------------------------------- |
-| New undeclared external | New vitest dependency          | Add to `dependencies` or `EXTERNAL_BLOCKLIST`      |
-| Browser test crashes    | Node.js code leaked to browser | Check import rewriting in `rewriteVitestImports()` |
-| Missing export          | New @vitest/\* subpath export  | Add to `VITEST_PACKAGE_TO_PATH`                    |
-| pnpm override fails     | New plugin export needed       | Add to `createPluginExports()`                     |
-
----
-
-## Technical Reference
-
-### Build Flow
-
-```
-1. bundleVitest()              Copy vitest-dev dist/ -> dist/
-2. copyVitestPackages()        Copy @vitest/* -> dist/@vitest/
-3. convertTabsToSpaces()       Normalize formatting for patches
-4. collectLeafDependencies()   Parse imports with oxc-parser
-5. bundleLeafDeps()            Bundle chai, pathe, etc -> dist/vendor/
-6. rewriteVitestImports()      Rewrite @vitest/*, vitest/*, vite
-7. patchVitestPkgRootPaths()   Fix distRoot for relocated files
-8. patchVitestBrowserPackage() Inject vendor-aliases plugin
-9. patchBrowserProviderLocators() Fix browser-safe imports
-10. Post-processing:
-    - patchVendorPaths()
-    - createBrowserCompatShim()
-    - createModuleRunnerStub()   Browser-safe stub
-    - createNodeEntry()          index-node.js with browser-provider
-    - copyBrowserClientFiles()
-    - createBrowserEntryFiles()  browser/ entry files at package root
-    - patchModuleAugmentations() Rewrite @vitest/expect, @vitest/runner augmentations
-    - patchChaiTypeReference()   Add @types/chai triple-slash reference
-    - createPluginExports()      dist/plugins/* for pnpm overrides
-    - mergePackageJson()
-    - validateExternalDeps()
-```
-
-### Output Structure
-
-```
-browser/                       # Entry files for ./browser export
-├── context.js                 # Runtime guard (throws if not in browser)
-└── context.d.ts               # Re-exports from dist/@vitest/browser/context.d.ts
-dist/
-├── @vitest/                    # Copied packages (browser/Node.js safe)
-│   ├── runner/
-│   ├── utils/
-│   ├── spy/
-│   ├── expect/
-│   ├── snapshot/
-│   ├── mocker/
-│   ├── pretty-format/
-│   ├── browser/
-│   └── browser-playwright/
-├── vendor/                     # Bundled leaf dependencies
-│   ├── chai.mjs
-│   ├── pathe.mjs
-│   ├── tinyrainbow.mjs
-│   ├── magic-string.mjs
-│   ├── estree-walker.mjs
-│   ├── why-is-node-running.mjs
-│   └── vitest_*.mjs            # Browser stubs
-├── plugins/                    # Shims for pnpm overrides
-│   ├── runner.mjs
-│   ├── utils.mjs
-│   └── ... (33+ files)
-├── chunks/                     # Vitest core chunks
-├── client/                     # Browser client files
-├── index.js                    # Browser-safe entry
-├── index-node.js               # Node.js entry (includes browser-provider)
-├── module-runner-stub.js       # Browser-safe module-runner
-└── browser-compat.js           # @vitest/browser compatibility shim
-```
-
-### Browser/Node.js Separation
-
-The critical design decision is maintaining separation between browser and Node.js code:
-
-| Entry Point          | Used By               | Contains                          |
-| -------------------- | --------------------- | --------------------------------- |
-| `dist/index.js`      | Browser tests         | No Node.js-only code              |
-| `dist/index-node.js` | Node.js (config, CLI) | Includes browser-provider exports |
-
-This is achieved through:
-
-1. Conditional exports in package.json (`"node": "./dist/index-node.js"`)
-2. Browser-safe stubs for `module-runner`
-3. Import rewriting to prevent Node.js code from being pulled into browser bundles
-4. `vendor-aliases` plugin injection to resolve imports at runtime:
-   - Handles `@vitest/*` imports → resolves to copied `dist/@vitest/` files
-   - Handles `vitest/*` subpaths → resolves to dist files (enables `vitest/browser-playwright` usage)
-   - Handles `vitest/browser-playwright`, `vitest/browser-webdriverio`, `vitest/browser-preview` → resolves to bundled browser providers
-   - Handles `@voidzero-dev/vite-plus-test/*` subpaths → maps to equivalent vitest paths
-   - Handles `vite-plus/test/*` subpaths → maps to equivalent vitest paths (CLI package)
-   - Intercepts `vitest/browser`, `@voidzero-dev/vite-plus-test/browser`, `vite-plus/test/browser` → returns virtual module ID for BrowserContext plugin
-
-### Key Constants
-
-```typescript
-// Packages copied to dist/@vitest/
-const VITEST_PACKAGES_TO_COPY = [
-  '@vitest/runner',
-  '@vitest/utils',
-  '@vitest/spy',
-  '@vitest/expect',
-  '@vitest/snapshot',
-  '@vitest/mocker',
-  '@vitest/pretty-format',
-  '@vitest/browser',
-  '@vitest/browser-playwright',
-  '@vitest/browser-webdriverio',
-  '@vitest/browser-preview',
-];
-
-// Packages that must NOT be bundled (from build.ts lines 131-158)
-const EXTERNAL_BLOCKLIST = new Set([
-  // Our own packages - resolved at runtime
-  '@voidzero-dev/vite-plus-core',
-  '@voidzero-dev/vite-plus-core/module-runner',
-  'vite',
-  'vitest',
-
-  // Peer dependencies - consumers must provide these
-  '@edge-runtime/vm',
-  '@opentelemetry/api',
-  '@standard-schema/spec', // Types-only import from @vitest/expect
-  'happy-dom',
-  'jsdom',
-
-  // Optional dependencies with bundling issues or native bindings
-  'debug', // environment detection broken when bundled
-  'playwright', // native bindings
-  'webdriverio', // native bindings
-
-  // Runtime deps (in package.json dependencies) - not bundled, resolved at install time
-  'sirv',
-  'ws',
-  'pixelmatch',
-  'pngjs',
-
-  // MSW (Mock Service Worker) - optional peer dep of @vitest/mocker
-  'msw',
-  'msw/browser',
-  'msw/core/http',
-]);
-```
diff --git a/packages/test/__tests__/build-artifacts.spec.ts b/packages/test/__tests__/build-artifacts.spec.ts
deleted file mode 100644
index 641b98260c..0000000000
--- a/packages/test/__tests__/build-artifacts.spec.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * Verify that the @voidzero-dev/vite-plus-test build output (dist/)
- * contains the expected files and that patches applied during the build
- * (in build.ts) produce correct artifacts.
- *
- * These tests run against the already-built dist/ directory, ensuring
- * that re-packaging patches produce correct artifacts.
- */
-import fs from 'node:fs';
-import path from 'node:path';
-import url from 'node:url';
-
-import { describe, expect, it } from 'vitest';
-
-const testPkgDir = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..');
-const distDir = path.join(testPkgDir, 'dist');
-
-function findCliApiChunk(): string {
-  const chunksDir = path.join(distDir, 'chunks');
-  const files = fs.readdirSync(chunksDir);
-  const chunk = files.find((f) => f.startsWith('cli-api.') && f.endsWith('.js'));
-  if (!chunk) {
-    throw new Error('cli-api chunk not found in dist/chunks/');
-  }
-  return path.join(chunksDir, chunk);
-}
-
-describe('build artifacts', () => {
-  describe('@vitest/browser/context.js', () => {
-    const contextPath = path.join(distDir, '@vitest/browser/context.js');
-
-    it('should exist', () => {
-      expect(fs.existsSync(contextPath), `${contextPath} should exist`).toBe(true);
-    });
-
-    it('should export page, cdp, and utils', () => {
-      const content = fs.readFileSync(contextPath, 'utf-8');
-      expect(content).toMatch(/export\s*\{[^}]*page[^}]*\}/);
-      expect(content).toMatch(/export\s*\{[^}]*cdp[^}]*\}/);
-      expect(content).toMatch(/export\s*\{[^}]*utils[^}]*\}/);
-    });
-  });
-
-  /**
-   * The vitest:vendor-aliases plugin must NOT resolve @vitest/browser/context
-   * to the static file. If it does, the BrowserContext plugin's virtual module
-   * (which provides the `server` export) is bypassed.
-   *
-   * See: https://github.com/voidzero-dev/vite-plus/issues/1086
-   */
-  describe('vitest:vendor-aliases plugin (regression test for #1086)', () => {
-    const browserIndexPath = path.join(distDir, '@vitest/browser/index.js');
-
-    it('should not map @vitest/browser/context in vendorMap', () => {
-      const content = fs.readFileSync(browserIndexPath, 'utf-8');
-      // The vendorMap inside vitest:vendor-aliases should NOT contain
-      // '@vitest/browser/context' — it must be left for BrowserContext
-      // plugin to resolve as a virtual module.
-      const vendorAliasesMatch = content.match(
-        /name:\s*['"]vitest:vendor-aliases['"][\s\S]*?const vendorMap\s*=\s*\{([\s\S]*?)\}/,
-      );
-      expect(vendorAliasesMatch, 'vitest:vendor-aliases plugin should exist').toBeTruthy();
-      const vendorMapContent = vendorAliasesMatch![1];
-      expect(vendorMapContent).not.toContain("'@vitest/browser/context'");
-    });
-  });
-
-  /**
-   * `convertTabsToSpaces()` in build.ts must not touch tabs inside string
-   * literals. Upstream `@vitest/snapshot` decides multi-line snapshot
-   * indentation via `indent.includes("\t")` — where `"\t"` is a literal
-   * tab byte in the bundled source. A blanket tab→spaces rewrite turned
-   * this into `indent.includes("  ")`, so every 2-space indent matched
-   * and the tab-appending branch always ran, producing tab-indented
-   * snapshots in 2-space files.
-   *
-   * See: https://github.com/voidzero-dev/vite-plus/issues/1553
-   */
-  describe('snapshot indent check (regression test for #1553)', () => {
-    const snapshotIndexPath = path.join(distDir, '@vitest/snapshot/index.js');
-
-    it('preserves the literal tab byte inside the indent.includes string', () => {
-      const content = fs.readFileSync(snapshotIndexPath, 'utf-8');
-      expect(content).toContain('indent.includes("\t")');
-      expect(content).not.toMatch(/indent\.includes\("  "\)/);
-    });
-  });
-
-  /**
-   * Third-party packages that call `expect.extend()` internally
-   * (e.g., @testing-library/jest-dom) break under npm override because
-   * the vitest module instance is split, causing matchers to be registered
-   * on a different `chai` instance than the test runner uses.
-   *
-   * The build patches vitest's ModuleRunnerTransform plugin to auto-add
-   * these packages to `server.deps.inline`, so they are processed through
-   * Vite's transform pipeline and share the same module instance.
-   *
-   * See: https://github.com/voidzero-dev/vite-plus/issues/897
-   */
-  describe('server.deps.inline auto-inline (regression test for #897)', () => {
-    it('should contain the expected auto-inline packages', () => {
-      const content = fs.readFileSync(findCliApiChunk(), 'utf-8');
-      expect(content).toContain('Auto-inline packages');
-      expect(content).toContain('"@testing-library/jest-dom"');
-      expect(content).toContain('"@storybook/test"');
-      expect(content).toContain('"jest-extended"');
-    });
-
-    it('should not override user inline config when set to true', () => {
-      const content = fs.readFileSync(findCliApiChunk(), 'utf-8');
-      expect(content).toContain('server.deps.inline !== true');
-    });
-  });
-});
diff --git a/packages/test/build.ts b/packages/test/build.ts
deleted file mode 100644
index dd313749cf..0000000000
--- a/packages/test/build.ts
+++ /dev/null
@@ -1,2649 +0,0 @@
-// Build Script for @voidzero-dev/vite-plus-test
-//
-// Bundles vitest and @vitest/* dependencies with browser/Node.js separation.
-//
-// ┌─────────────────────────────────────────────────────────────────────┐
-// │                          BUILD FLOW                                 │
-// ├─────────────────────────────────────────────────────────────────────┤
-// │  1. bundleVitest()           Copy vitest-dev → dist/                │
-// │  2. copyVitestPackages()     Copy @vitest/* → dist/@vitest/         │
-// │  3. collectLeafDependencies() Parse imports with oxc-parser         │
-// │  4. bundleLeafDeps()         Bundle chai, pathe, etc → dist/vendor/ │
-// │  5. rewriteVitestImports()   Rewrite @vitest/*, vitest/*, vite      │
-// │  6. patchVitestPkgRootPaths() Fix distRoot for relocated files      │
-// │  7. patchVitestBrowserPackage() Inject vendor-aliases plugin        │
-// │  8. patchBrowserProviderLocators() Fix browser-safe imports         │
-// │  9. Post-processing:                                                │
-// │     - patchVendorPaths()                                            │
-// │     - createBrowserCompatShim()                                     │
-// │     - createModuleRunnerStub()   Browser-safe stub                  │
-// │     - createNodeEntry()          index-node.js with browser-provider│
-// │     - copyBrowserClientFiles()                                      │
-// │     - createPluginExports()      dist/plugins/* for pnpm overrides  │
-// │     - mergePackageJson()                                            │
-// │     - validateExternalDeps()                                        │
-// └─────────────────────────────────────────────────────────────────────┘
-//
-// Output Structure:
-//   dist/@vitest/*     - Copied packages (browser/Node.js safe)
-//   dist/vendor/*      - Bundled leaf dependencies
-//   dist/plugins/*     - Shims for pnpm overrides
-//   dist/index.js      - Browser-safe entry
-//   dist/index-node.js - Node.js entry (includes browser-provider)
-//
-// Key Design:
-//   - COPY @vitest/* to preserve browser/Node.js separation
-//   - BUNDLE only leaf deps (chai, etc.) to reduce install size
-//   - Separate entries prevent __vite__injectQuery errors in browser
-
-import { existsSync } from 'node:fs';
-import {
-  copyFile,
-  glob as fsGlob,
-  mkdir,
-  readFile,
-  readdir,
-  rm,
-  stat,
-  writeFile,
-} from 'node:fs/promises';
-import { builtinModules } from 'node:module';
-import { basename, join, parse, resolve, dirname, relative } from 'node:path';
-import { fileURLToPath } from 'node:url';
-
-import { parseSync } from 'oxc-parser';
-import { format } from 'oxfmt';
-import { build } from 'rolldown';
-import { dts } from 'rolldown-plugin-dts';
-
-import { generateLicenseFile } from '../../scripts/generate-license.js';
-import pkg from './package.json' with { type: 'json' };
-
-const projectDir = dirname(fileURLToPath(import.meta.url));
-const vitestSourceDir = resolve(projectDir, 'node_modules/vitest-dev');
-const distDir = resolve(projectDir, 'dist');
-const vendorDir = resolve(distDir, 'vendor');
-
-const CORE_PACKAGE_NAME = '@voidzero-dev/vite-plus-core';
-const TEST_PACKAGE_NAME = '@voidzero-dev/vite-plus-test';
-
-// @vitest/* packages to copy (not bundle) to preserve browser/Node.js separation
-// These are copied from node_modules to dist/@vitest/ to avoid shared chunks
-// that mix Node.js-only code with browser code
-const VITEST_PACKAGES_TO_COPY = [
-  '@vitest/runner',
-  '@vitest/utils',
-  '@vitest/spy',
-  '@vitest/expect',
-  '@vitest/snapshot',
-  '@vitest/mocker',
-  '@vitest/pretty-format',
-  '@vitest/browser',
-  '@vitest/browser-playwright',
-  '@vitest/browser-webdriverio',
-  '@vitest/browser-preview',
-] as const;
-
-// Mapping from @vitest/* package specifiers to their paths within dist/@vitest/
-// Used for import rewriting and vendor-aliases plugin
-const VITEST_PACKAGE_TO_PATH: Record<string, string> = {
-  // @vitest/runner
-  '@vitest/runner': '@vitest/runner/index.js',
-  '@vitest/runner/utils': '@vitest/runner/utils.js',
-  '@vitest/runner/types': '@vitest/runner/types.js',
-  // @vitest/utils
-  '@vitest/utils': '@vitest/utils/index.js',
-  '@vitest/utils/source-map': '@vitest/utils/source-map.js',
-  '@vitest/utils/source-map/node': '@vitest/utils/source-map/node.js',
-  '@vitest/utils/error': '@vitest/utils/error.js',
-  '@vitest/utils/helpers': '@vitest/utils/helpers.js',
-  '@vitest/utils/display': '@vitest/utils/display.js',
-  '@vitest/utils/timers': '@vitest/utils/timers.js',
-  '@vitest/utils/highlight': '@vitest/utils/highlight.js',
-  '@vitest/utils/offset': '@vitest/utils/offset.js',
-  '@vitest/utils/resolver': '@vitest/utils/resolver.js',
-  '@vitest/utils/serialize': '@vitest/utils/serialize.js',
-  '@vitest/utils/constants': '@vitest/utils/constants.js',
-  '@vitest/utils/diff': '@vitest/utils/diff.js',
-  // @vitest/spy
-  '@vitest/spy': '@vitest/spy/index.js',
-  // @vitest/expect
-  '@vitest/expect': '@vitest/expect/index.js',
-  // @vitest/snapshot
-  '@vitest/snapshot': '@vitest/snapshot/index.js',
-  '@vitest/snapshot/environment': '@vitest/snapshot/environment.js',
-  '@vitest/snapshot/manager': '@vitest/snapshot/manager.js',
-  // @vitest/mocker
-  '@vitest/mocker': '@vitest/mocker/index.js',
-  '@vitest/mocker/node': '@vitest/mocker/node.js',
-  '@vitest/mocker/browser': '@vitest/mocker/browser.js',
-  '@vitest/mocker/redirect': '@vitest/mocker/redirect.js',
-  '@vitest/mocker/transforms': '@vitest/mocker/transforms.js',
-  '@vitest/mocker/automock': '@vitest/mocker/automock.js',
-  '@vitest/mocker/register': '@vitest/mocker/register.js',
-  // @vitest/pretty-format
-  '@vitest/pretty-format': '@vitest/pretty-format/index.js',
-  // @vitest/browser
-  '@vitest/browser': '@vitest/browser/index.js',
-  '@vitest/browser/context': '@vitest/browser/context.js',
-  '@vitest/browser/client': '@vitest/browser/client.js',
-  '@vitest/browser/locators': '@vitest/browser/locators.js',
-  // @vitest/browser-playwright
-  '@vitest/browser-playwright': '@vitest/browser-playwright/index.js',
-  // @vitest/browser-webdriverio
-  '@vitest/browser-webdriverio': '@vitest/browser-webdriverio/index.js',
-  // @vitest/browser-preview
-  '@vitest/browser-preview': '@vitest/browser-preview/index.js',
-};
-
-// Packages that should NOT be bundled into dist/vendor/ (remain external at runtime)
-// There are two categories:
-// 1. Runtime deps (also in package.json dependencies) - installed with the package, not bundled
-// 2. Peer/optional deps (also in peerDependencies) - users must install themselves
-const EXTERNAL_BLOCKLIST = new Set([
-  // Our own packages - resolved at runtime
-  CORE_PACKAGE_NAME,
-  `${CORE_PACKAGE_NAME}/module-runner`,
-  'vite',
-  'vitest',
-
-  // Peer dependencies - consumers must provide these
-  '@edge-runtime/vm',
-  '@opentelemetry/api',
-  '@standard-schema/spec', // Types-only import from @vitest/expect
-  'happy-dom',
-  'jsdom',
-
-  // Optional dependencies with bundling issues or native bindings
-  'debug', // environment detection broken when bundled
-  'playwright', // native bindings
-  'webdriverio', // native bindings
-
-  // Runtime deps (in package.json dependencies) - not bundled, resolved at install time
-  'sirv',
-  'ws',
-  'pixelmatch',
-  'pngjs',
-
-  // MSW (Mock Service Worker) - optional peer dep of @vitest/mocker
-  'msw',
-  'msw/browser',
-  'msw/core/http',
-]);
-
-// CJS packages that need their default export destructured to named exports
-const CJS_REEXPORT_PACKAGES = new Set(['expect-type']);
-
-// Node built-in modules (including node: prefix variants)
-const NODE_BUILTINS = new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)]);
-
-// Step 1: Copy vitest-dev dist files (rewriting vite -> core package)
-await bundleVitest();
-
-// Step 1.5: Rebrand vitest CLI output as "vp test" with vite-plus version
-await brandVitest();
-
-// Step 2: Copy @vitest/* packages from node_modules to dist/@vitest/
-// This preserves the original file structure to maintain browser/Node.js separation
-await copyVitestPackages();
-
-// Step 2.5: Convert tabs to spaces in all copied JS files for consistent formatting
-await convertTabsToSpaces();
-
-// Step 3: Collect leaf dependencies from copied @vitest/* files
-// These are external packages like tinyrainbow, pathe, chai, etc.
-const leafDeps = await collectLeafDependencies();
-
-// Step 4: Bundle only leaf dependencies into dist/vendor/
-// Unlike bundling @vitest/* directly, this avoids shared chunks that mix browser/Node.js code
-const leafDepToVendorPath = await bundleLeafDeps(leafDeps);
-
-// Step 5: Rewrite imports in copied @vitest/* and vitest-dev files
-// - @vitest/* -> relative paths to dist/@vitest/
-// - leaf deps -> relative paths to dist/vendor/
-// - vite -> @voidzero-dev/vite-plus-core
-await rewriteVitestImports(leafDepToVendorPath);
-
-// Step 6: Fix pkgRoot resolution in all @vitest/* packages
-// Files are now at dist/@vitest/*/index.js, so "../.." needs to become "../../.."
-await patchVitestPkgRootPaths();
-
-// Step 7: Patch @vitest/browser package (vendor-aliases plugin, exclude list)
-await patchVitestBrowserPackage();
-
-// Step 8: Patch browser provider locators.js files for browser-safe imports
-await patchBrowserProviderLocators();
-
-// Step 9: Post-processing
-await patchVendorPaths();
-await patchVitestCoreResolver();
-await createBrowserCompatShim();
-await createModuleRunnerStub();
-await createNodeEntry();
-await copyBrowserClientFiles();
-await createBrowserEntryFiles();
-await patchModuleAugmentations();
-await patchChaiTypeReference();
-await patchMockerHoistedModule();
-await patchServerDepsInline();
-const pluginExports = await createPluginExports();
-await mergePackageJson(pluginExports);
-generateLicenseFile({
-  title: 'Vite-Plus test license',
-  packageName: 'Vite-Plus',
-  outputPath: join(projectDir, 'LICENSE'),
-  coreLicensePath: join(projectDir, '..', '..', 'LICENSE'),
-  bundledPaths: [distDir],
-  resolveFrom: [projectDir, join(projectDir, '..', '..')],
-  extraPackages: [
-    { packageDir: vitestSourceDir },
-    ...VITEST_PACKAGES_TO_COPY.map((packageName) => ({
-      packageDir: resolve(projectDir, 'node_modules', packageName),
-    })),
-  ],
-});
-if (!existsSync(join(projectDir, 'LICENSE'))) {
-  throw new Error('LICENSE was not generated during build');
-}
-await validateExternalDeps();
-
-async function mergePackageJson(pluginExports: Array<{ exportPath: string; shimFile: string }>) {
-  const vitestPackageJsonPath = join(vitestSourceDir, 'package.json');
-  const destPackageJsonPath = resolve(projectDir, 'package.json');
-
-  const vitestPkg = JSON.parse(await readFile(vitestPackageJsonPath, 'utf-8'));
-  const destPkg = JSON.parse(await readFile(destPackageJsonPath, 'utf-8'));
-
-  // Fields to merge from vitest-dev package.json (excluding dependencies since we bundle them)
-  const fieldsToMerge = [
-    'imports',
-    'exports',
-    'main',
-    'module',
-    'types',
-    'engines',
-    'peerDependencies',
-    'peerDependenciesMeta',
-  ] as const;
-
-  for (const field of fieldsToMerge) {
-    if (vitestPkg[field] !== undefined) {
-      destPkg[field] = vitestPkg[field];
-    }
-  }
-
-  // Remove bundled @vitest/* packages from peerDependencies
-  // These browser provider packages are now bundled, so users don't need to install them
-  const bundledPeerDeps = [
-    '@vitest/browser-playwright',
-    '@vitest/browser-webdriverio',
-    '@vitest/browser-preview',
-  ];
-  if (destPkg.peerDependencies) {
-    for (const dep of bundledPeerDeps) {
-      delete destPkg.peerDependencies[dep];
-    }
-  }
-  if (destPkg.peerDependenciesMeta) {
-    for (const dep of bundledPeerDeps) {
-      delete destPkg.peerDependenciesMeta[dep];
-    }
-  }
-
-  destPkg.bundledVersions = {
-    ...destPkg.bundledVersions,
-    vitest: vitestPkg.version,
-  };
-
-  // Add @vitest/browser compatible export (for when this package overrides @vitest/browser)
-  // The main "." export is what's used when code imports from @vitest/browser
-  if (destPkg.exports) {
-    // Add conditional Node.js export to the main entry
-    // Node.js code (like @vitest/browser-playwright) uses index-node.js which includes
-    // browser-provider exports. Browser code uses index.js which is safe.
-    // This separation prevents Node.js-only code (like __vite__injectQuery) from being
-    // loaded in the browser, which would cause "Identifier already declared" errors.
-    //
-    // IMPORTANT: The 'browser' condition must come BEFORE 'node' because vitest passes
-    // custom --conditions (like 'browser') to worker processes when frameworks like Nuxt
-    // set edge/cloudflare presets. Without the 'browser' condition here, Node.js would
-    // match 'node' first, loading index-node.js which imports @vitest/browser/index.js,
-    // which imports 'ws'. With --conditions browser active, 'ws' resolves to its browser
-    // stub (ws/browser.js) that doesn't export WebSocketServer, causing a SyntaxError.
-    // See: https://github.com/voidzero-dev/vite-plus/issues/831
-    if (destPkg.exports['.'] && destPkg.exports['.'].import) {
-      destPkg.exports['.'].import = {
-        types: destPkg.exports['.'].import.types,
-        browser: destPkg.exports['.'].import.default,
-        node: './dist/index-node.js',
-        default: destPkg.exports['.'].import.default,
-      };
-    }
-
-    destPkg.exports['./browser-compat'] = {
-      default: './dist/browser-compat.js',
-    };
-
-    // Add @vitest/browser-compatible subpath exports
-    // These are needed when this package is used as a pnpm override for @vitest/browser
-    // Files are copied to dist/ (not dist/vendor/) to match path resolution in bundled code
-    destPkg.exports['./client'] = {
-      default: './dist/client.js',
-    };
-    // Point to @vitest/browser/context.js so that tests and init scripts share the same module
-    // This is critical: the init script (locators.js) calls page.extend() on this module,
-    // and tests must use the SAME module instance to see the extended methods
-    destPkg.exports['./context'] = {
-      types: './browser/context.d.ts',
-      default: './dist/@vitest/browser/context.js',
-    };
-    // Also export ./browser/context for users importing vite-plus/test/browser/context
-    destPkg.exports['./browser/context'] = {
-      types: './browser/context.d.ts',
-      default: './dist/@vitest/browser/context.js',
-    };
-    destPkg.exports['./locators'] = {
-      default: './dist/locators.js',
-    };
-    destPkg.exports['./matchers'] = {
-      default: './dist/dummy.js', // Placeholder
-    };
-    destPkg.exports['./utils'] = {
-      default: './dist/dummy.js', // Placeholder
-    };
-
-    // Add @vitest/browser-playwright compatible export
-    // Users can import { playwright } from 'vitest/browser-playwright'
-    destPkg.exports['./browser-playwright'] = {
-      types: './dist/@vitest/browser-playwright/index.d.ts',
-      default: './dist/@vitest/browser-playwright/index.js',
-    };
-
-    // Add @vitest/browser-webdriverio compatible export
-    // Users can import { webdriverio } from 'vitest/browser-webdriverio'
-    destPkg.exports['./browser-webdriverio'] = {
-      types: './dist/@vitest/browser-webdriverio/index.d.ts',
-      default: './dist/@vitest/browser-webdriverio/index.js',
-    };
-
-    // Add @vitest/browser-preview compatible export
-    // Users can import { preview } from 'vitest/browser-preview'
-    destPkg.exports['./browser-preview'] = {
-      types: './dist/@vitest/browser-preview/index.d.ts',
-      default: './dist/@vitest/browser-preview/index.js',
-    };
-
-    // Add browser/providers/* alias exports for compatibility
-    // Some vitest examples use the nested path format
-    destPkg.exports['./browser/providers/playwright'] = {
-      types: './dist/@vitest/browser-playwright/index.d.ts',
-      default: './dist/@vitest/browser-playwright/index.js',
-    };
-    destPkg.exports['./browser/providers/webdriverio'] = {
-      types: './dist/@vitest/browser-webdriverio/index.d.ts',
-      default: './dist/@vitest/browser-webdriverio/index.js',
-    };
-    destPkg.exports['./browser/providers/preview'] = {
-      types: './dist/@vitest/browser-preview/index.d.ts',
-      default: './dist/@vitest/browser-preview/index.js',
-    };
-
-    // Add plugin exports for all bundled @vitest/* packages
-    // This allows pnpm overrides to redirect: @vitest/runner -> vitest/plugins/runner
-    for (const { exportPath, shimFile } of pluginExports) {
-      destPkg.exports[exportPath] = {
-        default: shimFile,
-      };
-    }
-  }
-
-  // Merge vitest dependencies into devDependencies (since we bundle them)
-  // Skip packages that are already in dependencies (runtime deps)
-  if (vitestPkg.dependencies) {
-    destPkg.devDependencies = destPkg.devDependencies || {};
-    for (const [dep, version] of Object.entries(vitestPkg.dependencies)) {
-      // Skip vite - we use our own core package
-      if (dep === 'vite') {
-        continue;
-      }
-      // Skip packages already in dependencies (they're runtime deps, not dev-only)
-      if (destPkg.dependencies && destPkg.dependencies[dep]) {
-        continue;
-      }
-      // Don't override existing devDependencies
-      if (!destPkg.devDependencies[dep]) {
-        destPkg.devDependencies[dep] = version;
-      }
-    }
-  }
-
-  const { code, errors } = await format(
-    destPackageJsonPath,
-    JSON.stringify(destPkg, null, 2) + '\n',
-    {
-      experimentalSortPackageJson: true,
-    },
-  );
-  if (errors.length > 0) {
-    for (const error of errors) {
-      console.error(error);
-    }
-    process.exit(1);
-  }
-  await writeFile(destPackageJsonPath, code);
-}
-
-async function bundleVitest() {
-  const vitestDestDir = projectDir;
-
-  await mkdir(vitestDestDir, { recursive: true });
-
-  // Get all vitest files excluding node_modules and package.json
-  const vitestFiles = fsGlob(join(vitestSourceDir, '**/*'), {
-    exclude: [
-      join(vitestSourceDir, 'node_modules/**'),
-      join(vitestSourceDir, 'package.json'),
-      join(vitestSourceDir, 'README.md'),
-      join(vitestSourceDir, 'LICENSE.md'),
-    ],
-  });
-
-  for await (const file of vitestFiles) {
-    const stats = await stat(file);
-    if (!stats.isFile()) {
-      continue;
-    }
-
-    const relativePath = file.replace(vitestSourceDir, '');
-    const destPath = join(vitestDestDir, relativePath);
-
-    await mkdir(parse(destPath).dir, { recursive: true });
-
-    // Rewrite vite imports in .js, .mjs, and .cjs files
-    if (
-      file.endsWith('.js') ||
-      file.endsWith('.mjs') ||
-      file.endsWith('.cjs') ||
-      file.endsWith('.d.ts') ||
-      file.endsWith('.d.cts')
-    ) {
-      let content = await readFile(file, 'utf-8');
-      content = content
-        .replaceAll(/from ['"]vite['"]/g, `from '${CORE_PACKAGE_NAME}'`)
-        .replaceAll(/import\(['"]vite['"]\)/g, `import('${CORE_PACKAGE_NAME}')`)
-        .replaceAll(/require\(['"]vite['"]\)/g, `require('${CORE_PACKAGE_NAME}')`)
-        .replaceAll(/require\("vite"\)/g, `require("${CORE_PACKAGE_NAME}")`)
-        .replaceAll(`import 'vite';`, `import '${CORE_PACKAGE_NAME}';`)
-        .replaceAll(`'vite/module-runner'`, `'${CORE_PACKAGE_NAME}/module-runner'`)
-        .replaceAll(`declare module "vite"`, `declare module "${CORE_PACKAGE_NAME}"`)
-        .replaceAll(/import\(['"]vitest['"]\)/g, `import('${TEST_PACKAGE_NAME}')`);
-      console.log(`Replaced vite imports in ${destPath}`);
-      await writeFile(destPath, content, 'utf-8');
-    } else {
-      await copyFile(file, destPath);
-    }
-  }
-}
-
-/**
- * Rebrand vitest CLI output as "vp test" with Vite+ banner styling.
- * Patches bundled chunks to replace vitest branding and align banner output.
- */
-async function brandVitest() {
-  const chunksDir = resolve(projectDir, 'dist/chunks');
-  const cacFiles: string[] = [];
-  for await (const file of fsGlob(join(chunksDir, 'cac.*.js'))) {
-    cacFiles.push(file);
-  }
-  if (cacFiles.length === 0) {
-    throw new Error('brandVitest: no cac chunk found in dist/chunks/');
-  }
-  for (const cacFile of cacFiles) {
-    let content = await readFile(cacFile, 'utf-8');
-
-    function patchString(label: string, search: string | RegExp, replacement: string) {
-      const before = content;
-      content =
-        typeof search === 'string'
-          ? content.replace(search, replacement)
-          : content.replace(search, replacement);
-      if (content === before) {
-        throw new Error(
-          `brandVitest: failed to patch "${label}" — pattern not found in ${cacFile}`,
-        );
-      }
-    }
-
-    // 1. CLI name: cac("vitest") → cac("vp test")
-    patchString('cac name', 'cac("vitest")', 'cac("vp test")');
-
-    // 2. Version: var version = "<semver>" → use VP_VERSION env var with fallback
-    patchString(
-      'version',
-      /var version = "(\d+\.\d+\.\d+[^"]*)"/,
-      'var version = process.env.VP_VERSION || "$1"',
-    );
-
-    // 3. Banner regex: /^vitest\/\d+\.\d+\.\d+$/ → /^vp test\/[\d.]+$/
-    patchString('banner regex', '/^vitest\\/\\d+\\.\\d+\\.\\d+$/', '/^vp test\\/[\\d.]+$/');
-
-    // 4. Help text: $ vitest --help → $ vp test --help
-    patchString('help text', '$ vitest --help --expand-help', '$ vp test --help --expand-help');
-
-    await writeFile(cacFile, content, 'utf-8');
-    console.log(`Branded vitest → vp test in ${cacFile}`);
-  }
-
-  const cliApiFiles: string[] = [];
-  for await (const file of fsGlob(join(chunksDir, 'cli-api.*.js'))) {
-    cliApiFiles.push(file);
-  }
-  if (cliApiFiles.length === 0) {
-    throw new Error('brandVitest: no cli-api chunk found in dist/chunks/');
-  }
-
-  for (const cliApiFile of cliApiFiles) {
-    let content = await readFile(cliApiFile, 'utf-8');
-
-    function patchString(label: string, search: string | RegExp, replacement: string) {
-      const before = content;
-      content =
-        typeof search === 'string'
-          ? content.replace(search, replacement)
-          : content.replace(search, replacement);
-      if (content === before) {
-        throw new Error(
-          `brandVitest: failed to patch "${label}" — pattern not found in ${cliApiFile}`,
-        );
-      }
-    }
-
-    // Remove one extra leading newline before DEV/RUN banner.
-    patchString(
-      'banner leading newline',
-      /printBanner\(\) \{\n\t\tthis\.log\(\);\n/,
-      'printBanner() {\n',
-    );
-
-    // Use a blue badge for both DEV and RUN.
-    patchString(
-      'banner color',
-      /const color = this\.ctx\.config\.watch \? "blue" : "[a-z]+";\n\t\tconst mode = this\.ctx\.config\.watch \? "DEV" : "RUN";/,
-      'const mode = this.ctx.config.watch ? "DEV" : "RUN";\n\t\tconst label = c.bold(c.inverse(c.blue(` ${mode} `)));',
-    );
-
-    // Remove the version from the banner line and render a high-contrast label.
-    patchString(
-      'banner version text',
-      /this\.log\(withLabel\(color, mode, (?:""|`v\$\{this\.ctx\.version\} `)\) \+ c\.gray\(this\.ctx\.config\.root\)\);/,
-      'this.log(`${label} ${c.gray(this.ctx.config.root)}`);',
-    );
-
-    await writeFile(cliApiFile, content, 'utf-8');
-    console.log(`Branded vitest banner in ${cliApiFile}`);
-  }
-}
-
-/**
- * Copy @vitest/* packages from node_modules to dist/@vitest/
- * This preserves the original file structure to maintain browser/Node.js separation.
- * Unlike bundling with Rolldown, copying avoids creating shared chunks that mix
- * Node.js-only code with browser code.
- */
-async function copyVitestPackages() {
-  console.log('\nCopying @vitest/* packages to dist/@vitest/...');
-
-  const vitestDir = resolve(distDir, '@vitest');
-  await rm(vitestDir, { recursive: true, force: true });
-  await mkdir(vitestDir, { recursive: true });
-
-  let totalCopied = 0;
-
-  for (const pkg of VITEST_PACKAGES_TO_COPY) {
-    const pkgName = pkg.replace('@vitest/', '');
-    const srcDir = resolve(projectDir, `node_modules/${pkg}/dist`);
-    const destPkgDir = resolve(vitestDir, pkgName);
-
-    try {
-      await stat(srcDir);
-    } catch {
-      console.log(`  Warning: ${pkg} not installed, skipping`);
-      continue;
-    }
-
-    console.log(`  Copying ${pkg}...`);
-    const copied = await copyDirRecursive(srcDir, destPkgDir);
-    totalCopied += copied;
-    console.log(`    -> ${copied} files`);
-
-    // Copy root .d.ts files from @vitest/browser package directory.
-    // These are type definitions that live at the package root (not in dist/),
-    // e.g. context.d.ts, matchers.d.ts, aria-role.d.ts, utils.d.ts.
-    // Dynamically scan instead of hardcoding to handle future upstream additions.
-    if (pkg === '@vitest/browser') {
-      const pkgRoot = resolve(projectDir, `node_modules/${pkg}`);
-      try {
-        const pkgEntries = await readdir(pkgRoot);
-        for (const entry of pkgEntries) {
-          if (entry.endsWith('.d.ts')) {
-            await copyFile(join(pkgRoot, entry), join(destPkgDir, entry));
-            console.log(`    + copied ${entry}`);
-            totalCopied++;
-          }
-        }
-      } catch {
-        // Package root not readable, skip
-      }
-    }
-  }
-
-  console.log(`\nCopied ${totalCopied} files to dist/@vitest/`);
-}
-
-/**
- * Recursively copy a directory
- */
-async function copyDirRecursive(srcDir: string, destDir: string): Promise<number> {
-  await mkdir(destDir, { recursive: true });
-  const entries = await readdir(srcDir, { withFileTypes: true });
-  let count = 0;
-
-  for (const entry of entries) {
-    const srcPath = join(srcDir, entry.name);
-    const destPath = join(destDir, entry.name);
-
-    if (entry.isDirectory()) {
-      count += await copyDirRecursive(srcPath, destPath);
-    } else if (entry.isFile()) {
-      await copyFile(srcPath, destPath);
-      count++;
-    }
-  }
-
-  return count;
-}
-
-/**
- * Collect leaf dependencies from copied @vitest/* files AND vitest core dist files.
- * These are external packages that should be bundled (tinyrainbow, pathe, chai, expect-type, etc.)
- * but NOT @vitest/*, vitest/*, vite/*, node built-ins, or blocklisted packages.
- */
-async function collectLeafDependencies(): Promise<Set<string>> {
-  console.log('\nCollecting leaf dependencies from dist/...');
-
-  const leafDeps = new Set<string>();
-  const vitestDir = resolve(distDir, '@vitest');
-
-  // Scan both @vitest/* packages AND vitest core dist files
-  const jsFiles = fsGlob([
-    join(vitestDir, '**/*.js'),
-    join(distDir, '*.js'),
-    join(distDir, 'chunks/*.js'),
-  ]);
-
-  for await (const file of jsFiles) {
-    const content = await readFile(file, 'utf-8');
-    const result = parseSync(file, content, { sourceType: 'module' });
-
-    // Collect ESM static imports
-    for (const imp of result.module.staticImports) {
-      const specifier = imp.moduleRequest.value;
-      if (isLeafDependency(specifier)) {
-        leafDeps.add(specifier);
-      }
-    }
-
-    // Collect ESM static exports (re-exports)
-    for (const exp of result.module.staticExports) {
-      for (const entry of exp.entries) {
-        if (entry.moduleRequest) {
-          const specifier = entry.moduleRequest.value;
-          if (isLeafDependency(specifier)) {
-            leafDeps.add(specifier);
-          }
-        }
-      }
-    }
-
-    // Collect dynamic imports (only string literals)
-    for (const dynImp of result.module.dynamicImports) {
-      const rawText = content.slice(dynImp.moduleRequest.start, dynImp.moduleRequest.end);
-      if (
-        (rawText.startsWith("'") && rawText.endsWith("'")) ||
-        (rawText.startsWith('"') && rawText.endsWith('"'))
-      ) {
-        const specifier = rawText.slice(1, -1);
-        if (isLeafDependency(specifier)) {
-          leafDeps.add(specifier);
-        }
-      }
-    }
-  }
-
-  console.log(`Found ${leafDeps.size} leaf dependencies:`);
-  for (const dep of leafDeps) {
-    console.log(`  - ${dep}`);
-  }
-
-  return leafDeps;
-}
-
-/**
- * Check if a specifier is a leaf dependency that should be bundled.
- * Leaf deps are external packages that are NOT:
- * - @vitest/* (we copy these)
- * - vitest or vitest/* (we copy vitest-dev)
- * - vite or vite/* (we use our core package)
- * - Node.js built-ins
- * - Blocklisted packages
- * - Relative paths
- */
-function isLeafDependency(specifier: string): boolean {
-  // Relative paths
-  if (specifier.startsWith('.') || specifier.startsWith('/')) {
-    return false;
-  }
-  // @vitest/* packages (we copy these)
-  if (specifier.startsWith('@vitest/')) {
-    return false;
-  }
-  // vitest or vitest/* (we copy vitest-dev)
-  if (specifier === 'vitest' || specifier.startsWith('vitest/')) {
-    return false;
-  }
-  // vite or vite/* (we use our core package)
-  if (specifier === 'vite' || specifier.startsWith('vite/')) {
-    return false;
-  }
-  // Node.js built-ins
-  if (NODE_BUILTINS.has(specifier)) {
-    return false;
-  }
-  // Blocklisted packages
-  if (EXTERNAL_BLOCKLIST.has(specifier)) {
-    return false;
-  }
-  // Node.js subpath imports (#module-evaluator, etc.)
-  if (specifier.startsWith('#')) {
-    return false;
-  }
-  // Invalid specifiers
-  if (!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*/.test(specifier)) {
-    return false;
-  }
-  return true;
-}
-
-/**
- * Bundle only leaf dependencies into dist/vendor/.
- * Only bundles non-@vitest deps (tinyrainbow, pathe, chai, etc.)
- * to avoid shared chunks that mix Node.js and browser code.
- */
-async function bundleLeafDeps(leafDeps: Set<string>): Promise<Map<string, string>> {
-  console.log('\nBundling leaf dependencies...');
-
-  await rm(vendorDir, { recursive: true, force: true });
-  await mkdir(vendorDir, { recursive: true });
-
-  const specifierToVendorPath = new Map<string, string>();
-
-  if (leafDeps.size === 0) {
-    console.log('  No leaf dependencies to bundle.');
-    return specifierToVendorPath;
-  }
-
-  // Build input object with all leaf deps
-  const input: Record<string, string> = {};
-  for (const dep of leafDeps) {
-    const safeName = safeFileName(dep);
-    input[safeName] = dep;
-  }
-
-  try {
-    await build({
-      input,
-      output: {
-        dir: vendorDir,
-        format: 'esm',
-        entryFileNames: '[name].mjs',
-        chunkFileNames: 'shared-[hash].mjs',
-      },
-      platform: 'neutral',
-      treeshake: false,
-      external: [
-        // Keep node built-ins external
-        ...NODE_BUILTINS,
-        // Keep blocklisted packages external
-        ...EXTERNAL_BLOCKLIST,
-        // Keep @vitest/* external (we copy them)
-        /@vitest\//,
-        // Keep vitest external (we copy it)
-        /^vitest(\/.*)?$/,
-        // Keep vite external (we use core package)
-        /^vite(\/.*)?$/,
-      ],
-      resolve: {
-        conditionNames: ['node', 'import', 'default'],
-        mainFields: ['module', 'main'],
-      },
-      logLevel: 'warn',
-    });
-
-    const dtsInput = { ...input };
-
-    for (const name of Object.keys(dtsInput)) {
-      const vendorDtsPath = join(vendorDir, `vendor_${name}.d.ts`);
-      dtsInput[name] = vendorDtsPath;
-      await writeFile(vendorDtsPath, `export * from '${name}';`, 'utf-8');
-    }
-
-    await build({
-      input: dtsInput,
-      output: {
-        dir: vendorDir,
-        format: 'esm',
-        entryFileNames: '[name].mts',
-      },
-      plugins: [
-        dts({
-          dtsInput: true,
-          oxc: true,
-          resolver: 'oxc',
-          emitDtsOnly: true,
-          tsconfig: false,
-        }),
-      ],
-    });
-
-    for (const p of Object.values(dtsInput)) {
-      await rm(p);
-    }
-
-    // Register all specifiers
-    for (const dep of leafDeps) {
-      const safeName = safeFileName(dep);
-      const vendorFilePath = join(vendorDir, `${safeName}.mjs`);
-      specifierToVendorPath.set(dep, vendorFilePath);
-      console.log(`  -> vendor/${safeName}.mjs`);
-
-      // Fix CJS packages that need named exports extracted from default
-      if (CJS_REEXPORT_PACKAGES.has(dep)) {
-        await fixCjsNamedExports(vendorFilePath, dep);
-      }
-    }
-  } catch (error) {
-    console.error('Failed to bundle leaf dependencies:', error);
-    throw error;
-  }
-
-  console.log(`\nBundled ${specifierToVendorPath.size} leaf dependencies.`);
-  return specifierToVendorPath;
-}
-
-/**
- * Rewrite imports in all copied @vitest/* files and vitest-dev dist files.
- * This handles:
- * - @vitest/* -> relative paths to dist/@vitest/
- * - vitest/* -> relative paths to dist/
- * - vite -> @voidzero-dev/vite-plus-core
- * - leaf deps -> relative paths to dist/vendor/
- */
-async function rewriteVitestImports(leafDepToVendorPath: Map<string, string>) {
-  console.log('\nRewriting imports in @vitest/* and vitest core files...');
-
-  const vitestDir = resolve(distDir, '@vitest');
-  let rewrittenCount = 0;
-
-  // Scan both @vitest/* packages AND vitest core dist files
-  // Include .d.ts files so TypeScript type imports also get rewritten
-  const jsFiles = fsGlob([
-    join(vitestDir, '**/*.js'),
-    join(vitestDir, '**/*.d.ts'),
-    join(distDir, '*.js'),
-    join(distDir, '*.d.ts'),
-    join(distDir, 'chunks/*.js'),
-    join(distDir, 'chunks/*.d.ts'),
-  ]);
-
-  for await (const file of jsFiles) {
-    let content = await readFile(file, 'utf-8');
-    const fileDir = dirname(file);
-
-    // Build specifier map for this file
-    const specifierMap = new Map<string, string>();
-
-    // Add @vitest/* mappings (relative paths)
-    for (const [pkg, destPath] of Object.entries(VITEST_PACKAGE_TO_PATH)) {
-      const absoluteDest = resolve(distDir, destPath);
-      let relativePath = relative(fileDir, absoluteDest);
-      relativePath = relativePath.split('\\').join('/'); // Windows fix
-      if (!relativePath.startsWith('.')) {
-        relativePath = './' + relativePath;
-      }
-      specifierMap.set(pkg, absoluteDest);
-    }
-
-    // Add vitest/* mappings (relative to dist/)
-    const vitestSubpathRewrites: Record<string, string> = {
-      vitest: resolve(distDir, 'index.js'),
-      'vitest/node': resolve(distDir, 'node.js'),
-      'vitest/config': resolve(distDir, 'config.js'),
-      // vitest/browser exports page, server, CDPSession, BrowserCommands, etc from @vitest/browser/context
-      // This matches vitest's own package.json exports: "./browser" -> "./browser/context.d.ts"
-      'vitest/browser': resolve(distDir, '@vitest/browser/context.js'),
-      // vitest/internal/browser exports browser-safe __INTERNAL and stringify (NOT @vitest/browser/index.js which has Node.js code)
-      'vitest/internal/browser': resolve(distDir, 'browser.js'),
-      'vitest/runners': resolve(distDir, 'runners.js'),
-      'vitest/suite': resolve(distDir, 'suite.js'),
-      'vitest/environments': resolve(distDir, 'environments.js'),
-      'vitest/coverage': resolve(distDir, 'coverage.js'),
-      'vitest/reporters': resolve(distDir, 'reporters.js'),
-      'vitest/snapshot': resolve(distDir, 'snapshot.js'),
-      'vitest/mocker': resolve(distDir, 'mocker.js'),
-    };
-    for (const [specifier, absolutePath] of Object.entries(vitestSubpathRewrites)) {
-      specifierMap.set(specifier, absolutePath);
-    }
-
-    // Add leaf dep mappings (relative to vendor/)
-    for (const [specifier, vendorPath] of leafDepToVendorPath) {
-      specifierMap.set(specifier, vendorPath);
-    }
-
-    // For files inside @vitest/browser/, preserve 'vitest/browser' as a bare specifier.
-    // These files run in browser context where the vitest:vendor-aliases plugin
-    // resolves 'vitest/browser' to the virtual module '\0vitest/browser',
-    // which provides browser-safe context API (page, server, userEvent, utils).
-    // Without this, 'vitest/browser' gets rewritten to './index.js' which resolves
-    // to the Node.js server file (~9000 lines of node:fs, ws, etc.)
-    if (file.includes('@vitest/browser') || file.includes('@vitest\\browser')) {
-      specifierMap.delete('vitest/browser');
-    }
-
-    // Rewrite using AST
-    const rewritten = rewriteImportsWithAst(content, file, false, specifierMap);
-
-    // Also rewrite vite -> core package (simple string replacement since it's a package name)
-    let finalContent = rewritten
-      .replaceAll(/from ['"]vite['"]/g, `from '${CORE_PACKAGE_NAME}'`)
-      .replaceAll(/import\(['"]vite['"]\)/g, `import('${CORE_PACKAGE_NAME}')`)
-      .replaceAll(`'vite/module-runner'`, `'${CORE_PACKAGE_NAME}/module-runner'`);
-
-    // Special handling for @vitest/mocker entry files that have redundant side-effect imports
-    // The original files have: import 'magic-string'; export {...} from './chunk-automock.js'; import 'estree-walker';
-    // This is problematic because:
-    // 1. Side-effect imports are redundant (chunk files already import what they need)
-    // 2. Having imports after exports can confuse some module parsers
-    // Fix: Remove redundant side-effect imports from vendor deps in entry files
-    if (file.includes('@vitest/mocker') || file.includes('@vitest\\mocker')) {
-      // Get the base filename
-      const baseName = file.split(/[/\\]/).pop();
-      // Only process entry files (not chunk files)
-      if (baseName && !baseName.startsWith('chunk-')) {
-        // Remove side-effect imports from vendor deps (these are redundant since chunk files import them)
-        finalContent = finalContent.replace(/import\s*['"][^'"]*vendor[^'"]*\.mjs['"];?\s*/g, '');
-      }
-    }
-
-    if (finalContent !== content) {
-      await writeFile(file, finalContent, 'utf-8');
-      rewrittenCount++;
-    }
-  }
-
-  console.log(`  Rewrote imports in ${rewrittenCount} files`);
-
-  // Also rewrite imports in the main vitest-dev dist files
-  console.log('\nRewriting imports in vitest-dev dist files...');
-  let mainRewrittenCount = 0;
-
-  const mainJsFiles = fsGlob(join(distDir, '*.js'));
-  const chunksJsFiles = fsGlob(join(distDir, 'chunks', '*.js'));
-  const workersJsFiles = fsGlob(join(distDir, 'workers', '*.js'));
-
-  for await (const file of mainJsFiles) {
-    const rewritten = await rewriteDistFile(file, leafDepToVendorPath);
-    if (rewritten) {
-      mainRewrittenCount++;
-    }
-  }
-  for await (const file of chunksJsFiles) {
-    const rewritten = await rewriteDistFile(file, leafDepToVendorPath);
-    if (rewritten) {
-      mainRewrittenCount++;
-    }
-  }
-  for await (const file of workersJsFiles) {
-    const rewritten = await rewriteDistFile(file, leafDepToVendorPath);
-    if (rewritten) {
-      mainRewrittenCount++;
-    }
-  }
-
-  console.log(`  Rewrote imports in ${mainRewrittenCount} dist files`);
-}
-
-/**
- * Rewrite imports in a vitest-dev dist file.
- * Returns true if the file was modified.
- */
-async function rewriteDistFile(
-  file: string,
-  leafDepToVendorPath: Map<string, string>,
-): Promise<boolean> {
-  let content = await readFile(file, 'utf-8');
-
-  // Build specifier map
-  const specifierMap = new Map<string, string>();
-
-  // Add @vitest/* mappings
-  for (const [pkg, destPath] of Object.entries(VITEST_PACKAGE_TO_PATH)) {
-    const absoluteDest = resolve(distDir, destPath);
-    specifierMap.set(pkg, absoluteDest);
-  }
-
-  // Add leaf dep mappings
-  for (const [specifier, vendorPath] of leafDepToVendorPath) {
-    specifierMap.set(specifier, vendorPath);
-  }
-
-  // Add vitest/* subpath mappings
-  // NOTE: Do NOT include 'vitest/browser' - it must be handled by
-  // the vitest:browser:virtual-module:context plugin at runtime
-  const vitestSubpathRewrites: Record<string, string> = {
-    vitest: resolve(distDir, 'index.js'),
-    'vitest/node': resolve(distDir, 'node.js'),
-    'vitest/config': resolve(distDir, 'config.js'),
-    // 'vitest/browser' - intentionally omitted, handled by virtual module plugin
-    'vitest/internal/browser': resolve(distDir, 'browser.js'),
-    'vitest/runners': resolve(distDir, 'runners.js'),
-    'vitest/suite': resolve(distDir, 'suite.js'),
-    'vitest/environments': resolve(distDir, 'environments.js'),
-    'vitest/coverage': resolve(distDir, 'coverage.js'),
-    'vitest/reporters': resolve(distDir, 'reporters.js'),
-    'vitest/snapshot': resolve(distDir, 'snapshot.js'),
-    'vitest/mocker': resolve(distDir, 'mocker.js'),
-  };
-  for (const [specifier, absolutePath] of Object.entries(vitestSubpathRewrites)) {
-    specifierMap.set(specifier, absolutePath);
-  }
-
-  // Add mappings for ./vendor/vitest_*.mjs relative imports
-  // These are vitest-dev's bundled @vitest/* packages that we've copied to dist/@vitest/
-  const vendorToVitest: Record<string, string> = {
-    './vendor/vitest_runner.mjs': resolve(distDir, '@vitest/runner/index.js'),
-    './vendor/vitest_runners.mjs': resolve(distDir, 'runners.js'),
-    './vendor/vitest_browser.mjs': resolve(distDir, '@vitest/browser/context.js'),
-    './vendor/vitest_internal_browser.mjs': resolve(distDir, 'browser.js'),
-    './vendor/vitest_utils.mjs': resolve(distDir, '@vitest/utils/index.js'),
-    './vendor/vitest_spy.mjs': resolve(distDir, '@vitest/spy/index.js'),
-    './vendor/vitest_snapshot.mjs': resolve(distDir, '@vitest/snapshot/index.js'),
-    './vendor/vitest_expect.mjs': resolve(distDir, '@vitest/expect/index.js'),
-  };
-  for (const [vendorPath, destPath] of Object.entries(vendorToVitest)) {
-    specifierMap.set(vendorPath, destPath);
-  }
-
-  let rewritten = rewriteImportsWithAst(content, file, false, specifierMap);
-
-  // Strip module-runner side-effect import from index.js
-  // This import is Node.js-only and causes browser tests to hang when vitest/index.js
-  // is loaded in browser context (to get describe, it, expect, etc.)
-  // The module-runner contains Node.js code (process.platform, etc.) that browsers can't execute
-  if (basename(file) === 'index.js') {
-    rewritten = rewritten.replace(
-      /import\s*['"]@voidzero-dev\/vite-plus-core\/module-runner['"];?\s*/g,
-      '',
-    );
-  }
-
-  if (rewritten !== content) {
-    await writeFile(file, rewritten, 'utf-8');
-    return true;
-  }
-  return false;
-}
-
-/**
- * Rewrite imports using oxc-parser AST for precise replacements
- */
-function rewriteImportsWithAst(
-  content: string,
-  filePath: string,
-  isCjs: boolean,
-  specifierToVendorPath: Map<string, string>,
-): string {
-  // Use Map to deduplicate replacements by start position
-  const replacementMap = new Map<number, [number, number, string]>();
-
-  // Helper to get relative path for a specifier
-  const getRelativePath = (specifier: string): string | null => {
-    const vendorPath = specifierToVendorPath.get(specifier);
-    if (!vendorPath) {
-      return null;
-    }
-    let relativePath = relative(dirname(filePath), vendorPath);
-    // Normalize to forward slashes for ES module imports (Windows uses backslashes)
-    relativePath = relativePath.split('\\').join('/');
-    if (!relativePath.startsWith('.')) {
-      relativePath = './' + relativePath;
-    }
-    return relativePath;
-  };
-
-  // Helper to add replacement (deduplicates by start position)
-  const addReplacement = (start: number, end: number, newValue: string) => {
-    if (!replacementMap.has(start)) {
-      replacementMap.set(start, [start, end, newValue]);
-    }
-  };
-
-  // Parse with oxc-parser
-  const result = parseSync(filePath, content, {
-    sourceType: isCjs ? 'script' : 'module',
-  });
-
-  // Collect ESM static imports
-  for (const imp of result.module.staticImports) {
-    const specifier = imp.moduleRequest.value;
-    const relativePath = getRelativePath(specifier);
-    if (relativePath) {
-      // Replace the module request (including quotes)
-      addReplacement(imp.moduleRequest.start, imp.moduleRequest.end, `'${relativePath}'`);
-    }
-  }
-
-  // Collect ESM static exports (re-exports)
-  for (const exp of result.module.staticExports) {
-    for (const entry of exp.entries) {
-      if (entry.moduleRequest) {
-        const specifier = entry.moduleRequest.value;
-        const relativePath = getRelativePath(specifier);
-        if (relativePath) {
-          addReplacement(entry.moduleRequest.start, entry.moduleRequest.end, `'${relativePath}'`);
-        }
-      }
-    }
-  }
-
-  // Collect dynamic imports (only string literals)
-  for (const dynImp of result.module.dynamicImports) {
-    const rawText = content.slice(dynImp.moduleRequest.start, dynImp.moduleRequest.end);
-    if (
-      (rawText.startsWith("'") && rawText.endsWith("'")) ||
-      (rawText.startsWith('"') && rawText.endsWith('"'))
-    ) {
-      const specifier = rawText.slice(1, -1);
-      const relativePath = getRelativePath(specifier);
-      if (relativePath) {
-        addReplacement(dynImp.moduleRequest.start, dynImp.moduleRequest.end, `'${relativePath}'`);
-      }
-    }
-  }
-
-  // For CJS files, also handle require() calls using regex (oxc-parser doesn't track these)
-  if (isCjs) {
-    const requireRegex = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
-    let match;
-    while ((match = requireRegex.exec(content)) !== null) {
-      const specifier = match[2];
-      const relativePath = getRelativePath(specifier);
-      if (relativePath) {
-        // Calculate the position of just the string literal (including quotes)
-        const stringStart = match.index + match[0].indexOf(match[1]);
-        const stringEnd = stringStart + match[1].length + specifier.length + match[1].length;
-        addReplacement(stringStart, stringEnd, `'${relativePath}'`);
-      }
-    }
-  }
-
-  // Sort replacements in reverse order (end to start) to preserve positions
-  // eslint-disable-next-line unicorn/no-array-sort -- safe: sorting a fresh spread copy
-  const replacements = [...replacementMap.values()].sort((a, b) => b[0] - a[0]);
-
-  // Apply replacements
-  let result_content = content;
-  for (const [start, end, newValue] of replacements) {
-    result_content = result_content.slice(0, start) + newValue + result_content.slice(end);
-  }
-
-  return result_content;
-}
-
-/**
- * Fix CJS packages that only export default - extract named exports from the default export
- */
-async function fixCjsNamedExports(vendorFilePath: string, specifier: string) {
-  let content = await readFile(vendorFilePath, 'utf-8');
-
-  // Match pattern like: export default require_xxx();
-  // and: export {  };
-  const defaultExportMatch = content.match(/export default (require_\w+)\(\);/);
-
-  if (defaultExportMatch) {
-    const requireFn = defaultExportMatch[1];
-    console.log(`      Fixing CJS named exports for ${specifier}...`);
-
-    const emptyExportMatch = content.match(/export \{\s*\};/);
-    if (emptyExportMatch) {
-      // Pattern: export default require_xxx();\nexport {  };
-      content = content.replace(
-        /export default (require_\w+)\(\);\s*\nexport \{\s*\};/,
-        `const __cjs_export__ = ${requireFn}();\nexport const { expectTypeOf } = __cjs_export__;\nexport default __cjs_export__;`,
-      );
-    } else {
-      // Pattern: export default require_xxx(); (no empty export block)
-      content = content.replace(
-        /export default (require_\w+)\(\);/,
-        `const __cjs_export__ = ${requireFn}();\nexport const { expectTypeOf } = __cjs_export__;\nexport default __cjs_export__;`,
-      );
-    }
-
-    await writeFile(vendorFilePath, content, 'utf-8');
-  }
-}
-
-/**
- * Create a safe filename from a specifier
- */
-function safeFileName(specifier: string): string {
-  return specifier.replace(/[@/]/g, '_').replace(/^_/, '');
-}
-
-/**
- * Patch pkgRoot/distRoot paths in vendor files.
- * The bundled code assumes files are in dist/, but vendor files are in dist/vendor/
- * So "../.." needs to become "../../.." to correctly resolve to package root.
- * Also patches relative file references like "context.js" to "../context.js".
- */
-async function patchVendorPaths() {
-  console.log('\nPatching vendor paths...');
-
-  // Patterns that need one more level up due to vendor subdirectory
-  const pathPatterns = [
-    // Package root calculation: "../.." -> "../../.."
-    {
-      original: `resolve$1(fileURLToPath(import.meta.url), "../..")`,
-      fixed: `resolve$1(fileURLToPath(import.meta.url), "../../..")`,
-    },
-    // context.js reference: "context.js" -> "../context.js"
-    // This is used in browser server to resolve the vitest/browser/context export
-    {
-      original: `resolve$1(__dirname$1, "context.js")`,
-      fixed: `resolve$1(__dirname$1, "../context.js")`,
-    },
-  ];
-
-  const vendorFiles = fsGlob(join(vendorDir, '*.mjs'));
-  let patchedCount = 0;
-
-  for await (const file of vendorFiles) {
-    let content = await readFile(file, 'utf-8');
-    let modified = false;
-
-    for (const { original, fixed } of pathPatterns) {
-      if (content.includes(original)) {
-        content = content.replaceAll(original, fixed);
-        modified = true;
-      }
-    }
-
-    if (modified) {
-      await writeFile(file, content, 'utf-8');
-      console.log(`  Patched paths in ${relative(distDir, file)}`);
-      patchedCount++;
-    }
-  }
-
-  if (patchedCount === 0) {
-    console.log('  No vendor files needed path patching');
-  } else {
-    console.log(`  Successfully patched ${patchedCount} file(s)`);
-  }
-}
-
-/**
- * Patch VitestCoreResolver to resolve vite-plus/test directly.
- *
- * Problem: CLI's `export * from '@voidzero-dev/vite-plus-test'` creates a re-export
- * chain that breaks module identity in Vite's SSR transform. expect.extend()
- * mutations aren't visible through the re-export.
- *
- * Fix: Make VitestCoreResolver resolve both vite-plus/test and
- * @voidzero-dev/vite-plus-test directly to dist/index.js, bypassing re-exports.
- */
-async function patchVitestCoreResolver() {
-  console.log('\nPatching VitestCoreResolver for CLI package alias...');
-
-  let cliApiChunk: string | undefined;
-  for await (const chunk of fsGlob(join(distDir, 'chunks/cli-api.*.js'))) {
-    cliApiChunk = chunk;
-    break;
-  }
-
-  if (!cliApiChunk) {
-    throw new Error('cli-api chunk not found');
-  }
-  let content = await readFile(cliApiChunk, 'utf8');
-
-  // Find the VitestCoreResolver resolveId function and add our package aliases
-  const oldPattern = `async resolveId(id) {
-      if (id === "vitest") return resolve(distDir, "index.js");
-      if (id.startsWith("@vitest/") || id.startsWith("vitest/"))`;
-
-  const newCode = `async resolveId(id) {
-      if (id === "vitest") return resolve(distDir, "index.js");
-      // Resolve CLI test path and test package directly to dist/index.js
-      // This bypasses the re-export chain and ensures module identity is preserved
-      if (id === "vite-plus/test" || id === "@voidzero-dev/vite-plus-test") {
-        return resolve(distDir, "index.js");
-      }
-      // Handle subpaths: vite-plus/test/* -> vitest/*
-      if (id.startsWith("vite-plus/test/")) {
-        const subpath = id.slice("vite-plus/test/".length);
-        return this.resolve("vitest/" + subpath, join(ctx.config.root, "index.html"), { skipSelf: true });
-      }
-      // Handle subpaths: @voidzero-dev/vite-plus-test/* -> vitest/*
-      if (id.startsWith("@voidzero-dev/vite-plus-test/")) {
-        const subpath = id.slice("@voidzero-dev/vite-plus-test/".length);
-        return this.resolve("vitest/" + subpath, join(ctx.config.root, "index.html"), { skipSelf: true });
-      }
-      if (id.startsWith("@vitest/") || id.startsWith("vitest/"))`;
-
-  if (!content.includes(oldPattern)) {
-    throw new Error(
-      'Could not find VitestCoreResolver pattern to patch in ' +
-        cliApiChunk +
-        '. ' +
-        'This likely means vitest code has changed and the patch needs to be updated.',
-    );
-  }
-
-  content = content.replace(oldPattern, newCode);
-  await writeFile(cliApiChunk, content);
-  console.log('  Patched VitestCoreResolver to resolve vite-plus/test directly');
-}
-
-/**
- * Convert leading tabs to spaces in all JS files in dist/ for consistent
- * formatting. This allows our patching code to use space-based patterns
- * instead of tabs.
- *
- * Only leading whitespace is rewritten — tabs inside string or template
- * literals are semantically meaningful (e.g. `indent.includes("\t")` in
- * @vitest/snapshot picks the snapshot indent style by checking for a
- * literal tab byte) and must be preserved.
- *
- * See: https://github.com/voidzero-dev/vite-plus/issues/1553
- */
-async function convertTabsToSpaces() {
-  console.log('\nConverting leading tabs to spaces in dist/...');
-
-  let convertedCount = 0;
-
-  for await (const file of fsGlob(resolve(distDir, '**/*.js'))) {
-    const content = await readFile(file, 'utf-8');
-    const converted = content.replace(/^\t+/gm, (match) => '  '.repeat(match.length));
-    if (converted !== content) {
-      await writeFile(file, converted);
-      convertedCount++;
-    }
-  }
-
-  console.log(`  Converted ${convertedCount} files`);
-}
-
-/**
- * Fix pkgRoot path resolution in all `@vitest/*` packages.
- * The original packages use resolve(import.meta.url, "../..") to find their package root.
- * But our files are at `dist/@vitest/star/index.js`, so we need to go up 3 levels, not 2.
- */
-async function patchVitestPkgRootPaths() {
-  console.log('\nFixing distRoot paths in @vitest/* packages...');
-
-  const vitestDir = resolve(distDir, '@vitest');
-  let patchedCount = 0;
-
-  for (const pkg of VITEST_PACKAGES_TO_COPY) {
-    const pkgName = pkg.replace('@vitest/', '');
-    const indexPath = join(vitestDir, pkgName, 'index.js');
-
-    try {
-      await stat(indexPath);
-    } catch {
-      continue;
-    }
-
-    let content = await readFile(indexPath, 'utf-8');
-
-    // The original @vitest/browser had index.js in the dist/ folder, so:
-    //   pkgRoot = resolve(import.meta.url, "../..") -> @vitest/browser
-    //   distRoot = resolve(pkgRoot, "dist") -> @vitest/browser/dist
-    // But our file is at dist/@vitest/browser/index.js, so distRoot should just be
-    // the directory containing index.js (not pkgRoot/dist)
-    // Replace both lines with just making distRoot = dirname of index.js
-    // Use regex to handle both top-level and indented occurrences
-    const oldPattern =
-      /( *)const pkgRoot = resolve\(fileURLToPath\(import\.meta\.url\), "\.\.\/\.\."\);\n\1const distRoot = resolve\(pkgRoot, "dist"\);/g;
-    const newContent = content.replace(
-      oldPattern,
-      '$1const distRoot = dirname(fileURLToPath(import.meta.url));',
-    );
-
-    if (newContent !== content) {
-      await writeFile(indexPath, newContent, 'utf-8');
-      const matchCount = (content.match(oldPattern) || []).length;
-      console.log(`  Fixed ${pkg}/index.js (${matchCount} occurrences)`);
-      patchedCount++;
-    }
-  }
-
-  console.log(`  Patched ${patchedCount} packages`);
-}
-
-/**
- * Patch the copied @vitest/browser package to:
- * 1. Inject vitest:vendor-aliases plugin for @vitest/* resolution
- * 2. Add native deps to the exclude list
- * 3. Remove include patterns for bundled deps
- */
-async function patchVitestBrowserPackage() {
-  console.log('\nPatching @vitest/browser package...');
-
-  const browserIndexPath = join(distDir, '@vitest/browser/index.js');
-
-  try {
-    await stat(browserIndexPath);
-  } catch {
-    console.log('  Warning: @vitest/browser not found in dist, skipping');
-    return;
-  }
-
-  let content = await readFile(browserIndexPath, 'utf-8');
-
-  // 1. Inject vitest:vendor-aliases plugin into BrowserPlugin return array
-  // This allows imports like @vitest/runner to be resolved to our copied @vitest files
-  // Exclude @vitest/browser/context from vendor-aliases so that BrowserContext
-  // plugin's resolveId can intercept the bare specifier and return the virtual
-  // module (which includes the dynamically generated `server` export).
-  // Without this, vendor-aliases resolves the bare specifier to the static
-  // context.js file (which has no `server`), bypassing BrowserContext entirely.
-  // See: https://github.com/voidzero-dev/vite-plus/issues/1086
-  const VENDOR_ALIASES_EXCLUDE = new Set(['@vitest/browser/context']);
-
-  const mappingEntries = Object.entries(VITEST_PACKAGE_TO_PATH)
-    .filter(([pkg]) => pkg.startsWith('@vitest/') && !VENDOR_ALIASES_EXCLUDE.has(pkg))
-    .map(([pkg, file]) => `'${pkg}': resolve(packageRoot, '${file}')`)
-    .join(',\n      ');
-
-  // distRoot is @vitest/browser/ so we need to go up two levels to reach the actual dist root
-  const vendorAliasesPlugin = `{
-    name: 'vitest:vendor-aliases',
-    enforce: 'pre',
-    resolveId(id) {
-      // distRoot is @vitest/browser/, packageRoot is the actual dist/ directory
-      const packageRoot = resolve(distRoot, '../..');
-      // Resolve module-runner to a browser-safe stub
-      // This is critical: module-runner contains Node.js-only code (process.platform, etc.)
-      // that causes browsers to hang when loaded
-      if (id === '${CORE_PACKAGE_NAME}/module-runner' || id === 'vite/module-runner') {
-        return resolve(packageRoot, 'module-runner-stub.js');
-      }
-      // Mark vite/core as external to prevent Node.js-only code from being bundled
-      // This prevents __vite__injectQuery duplication errors in browser tests
-      if (id === '${CORE_PACKAGE_NAME}' || id === 'vite') {
-        return { id, external: true };
-      }
-      // Handle vitest/browser and package aliases
-      // Return virtual module ID so BrowserContext plugin can load it
-      // Supports: vitest/browser, @voidzero-dev/vite-plus-test/browser, vite-plus/test/browser
-      if (id === 'vitest/browser' || id === '@voidzero-dev/vite-plus-test/browser' || id === 'vite-plus/test/browser') {
-        return '\\0vitest/browser';
-      }
-      // Handle vitest/* subpaths (resolve to our dist files)
-      // Also handle @voidzero-dev package aliases that resolve to the same files
-      const vitestSubpathMap = {
-        'vitest': resolve(packageRoot, 'index.js'),
-        '@voidzero-dev/vite-plus-test': resolve(packageRoot, 'index.js'),
-        'vite-plus/test': resolve(packageRoot, 'index.js'),
-        'vitest/node': resolve(packageRoot, 'node.js'),
-        'vitest/config': resolve(packageRoot, 'config.js'),
-        'vitest/internal/browser': resolve(packageRoot, 'browser.js'),
-        'vitest/runners': resolve(packageRoot, 'runners.js'),
-        'vitest/suite': resolve(packageRoot, 'suite.js'),
-        'vitest/environments': resolve(packageRoot, 'environments.js'),
-        'vitest/coverage': resolve(packageRoot, 'coverage.js'),
-        'vitest/reporters': resolve(packageRoot, 'reporters.js'),
-        'vitest/snapshot': resolve(packageRoot, 'snapshot.js'),
-        'vitest/mocker': resolve(packageRoot, 'mocker.js'),
-        // Browser providers - resolve to our bundled @vitest/browser-* packages
-        'vitest/browser-playwright': resolve(packageRoot, '@vitest/browser-playwright/index.js'),
-        'vitest/browser-webdriverio': resolve(packageRoot, '@vitest/browser-webdriverio/index.js'),
-        'vitest/browser-preview': resolve(packageRoot, '@vitest/browser-preview/index.js'),
-      };
-      if (vitestSubpathMap[id]) {
-        return vitestSubpathMap[id];
-      }
-      // Handle @voidzero-dev/vite-plus-test/* subpaths (same as vitest/*)
-      if (id.startsWith('@voidzero-dev/vite-plus-test/')) {
-        const subpath = id.slice('@voidzero-dev/vite-plus-test/'.length);
-        const vitestEquiv = 'vitest/' + subpath;
-        if (vitestSubpathMap[vitestEquiv]) {
-          return vitestSubpathMap[vitestEquiv];
-        }
-      }
-      // Handle vite-plus/test/* subpaths (CLI package paths, same as vitest/*)
-      if (id.startsWith('vite-plus/test/')) {
-        const subpath = id.slice('vite-plus/test/'.length);
-        const vitestEquiv = 'vitest/' + subpath;
-        if (vitestSubpathMap[vitestEquiv]) {
-          return vitestSubpathMap[vitestEquiv];
-        }
-      }
-      // Handle @vitest/* packages (resolve to our copied files)
-      const vendorMap = {
-      ${mappingEntries}
-      };
-      if (vendorMap[id]) {
-        return vendorMap[id];
-      }
-    }
-  }`;
-
-  // Find BrowserPlugin return array and inject plugin
-  const pluginArrayPattern = /(return \[)(\n +\{\n +enforce: "pre",\n +name: "vitest:browser",)/;
-  if (pluginArrayPattern.test(content)) {
-    content = content.replace(pluginArrayPattern, `$1\n    ${vendorAliasesPlugin},$2`);
-    console.log('  Injected vitest:vendor-aliases plugin');
-  } else {
-    throw new Error(
-      'Failed to inject vendor-aliases plugin in @vitest/browser/index.js: pattern not found. ' +
-        'This likely means vitest code has changed and the patch needs to be updated.',
-    );
-  }
-
-  // 2. Patch exclude list to add native deps
-  // Pattern: const exclude = ["vitest", ...
-  const excludePattern = /(const exclude = \[)(\n?\s*"vitest",)/;
-  // Exclude packages that:
-  // Packages to exclude from Vite's dependency pre-bundling (optimizeDeps.exclude)
-  const packagesToExclude = [
-    // @vitest packages that need our resolveId plugin
-    '@vitest/browser',
-    '@vitest/ui',
-    '@vitest/ui/reporter',
-    '@vitest/mocker/node', // imports @voidzero-dev/vite-plus-core
-
-    // Our package aliases - preserve module identity with init scripts
-    // This ensures both init scripts (loaded via /@fs/) and tests use the same page singleton
-    '@voidzero-dev/vite-plus-test',
-    '@voidzero-dev/vite-plus-test/browser',
-    '@voidzero-dev/vite-plus-test/browser/context',
-    'vite-plus/test',
-    'vite-plus/test/browser',
-    'vite-plus/test/browser/context',
-
-    // Node.js only packages
-    'vite',
-    '@voidzero-dev/vite-plus-core',
-    '@voidzero-dev/vite-plus-core/module-runner',
-
-    // Native bindings
-    'lightningcss',
-    '@tailwindcss/oxide',
-    'tailwindcss', // pulls in @tailwindcss/oxide
-  ];
-
-  const excludeListStr = packagesToExclude.map((pkg) => `"${pkg}"`).join(',\n          ');
-  const excludeReplacement = `$1\n          ${excludeListStr},$2`;
-  if (excludePattern.test(content)) {
-    content = content.replace(excludePattern, excludeReplacement);
-    console.log('  Patched exclude list with native deps');
-  } else {
-    throw new Error(
-      'Failed to patch exclude list in @vitest/browser/index.js: pattern not found. ' +
-        'This likely means vitest code has changed and the patch needs to be updated.',
-    );
-  }
-
-  // 3. Remove include patterns that reference bundled deps
-  // These patterns like "vitest > expect-type" don't work with our bundled setup
-  // since the deps are already bundled into vendor files
-  const includePatterns = [
-    '"vitest > expect-type"',
-    '"vitest > @vitest/snapshot > magic-string"',
-    '"vitest > @vitest/expect > chai"',
-  ];
-  for (const pattern of includePatterns) {
-    content = content.replace(
-      new RegExp(`\\s*${pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')},?`, 'g'),
-      '',
-    );
-  }
-  console.log('  Removed bundled deps from include list');
-
-  // 4. Patch BrowserContext to also handle our package aliases as fallback
-  // This allows direct imports from our package without requiring vitest override
-  // Supports: vitest/browser, @voidzero-dev/vite-plus-test/browser, vite-plus/test/browser
-  const browserContextPattern = /if \(id === ID_CONTEXT\) \{/;
-  if (browserContextPattern.test(content)) {
-    content = content.replace(
-      browserContextPattern,
-      `if (id === ID_CONTEXT || id === "@voidzero-dev/vite-plus-test/browser" || id === "vite-plus/test/browser") {`,
-    );
-    console.log('  Patched BrowserContext to handle package aliases');
-  } else {
-    throw new Error(
-      'Failed to patch BrowserContext in @vitest/browser/index.js: pattern not found. ' +
-        'This likely means vitest code has changed and the patch needs to be updated.',
-    );
-  }
-
-  // 5. Patch version to use VP_VERSION, preventing the "Running mixed versions" warning
-  const versionPattern = /var version = "(\d+\.\d+\.\d+[^"]*)"/;
-  const beforeVersion = content;
-  content = content.replace(versionPattern, 'var version = process.env.VP_VERSION || "$1"');
-  if (content === beforeVersion) {
-    throw new Error(
-      'Failed to patch version in @vitest/browser/index.js: pattern not found. ' +
-        'This likely means vitest code has changed and the patch needs to be updated.',
-    );
-  }
-  console.log('  Patched version to use VP_VERSION env var');
-
-  await writeFile(browserIndexPath, content, 'utf-8');
-  console.log('  Successfully patched @vitest/browser/index.js');
-}
-
-/**
- * Patch browser provider locators.js files to use browser-safe imports.
- *
- * The original files import from '../browser/index.js' which includes Node.js server code.
- * We need to change them to import from browser-safe files instead.
- *
- * Providers handled:
- *   - @vitest/browser-playwright: import { page, server } from '../browser/index.js';
- *   - @vitest/browser-webdriverio: import { page, server, utils } from '../browser/index.js';
- *   - @vitest/browser-preview: import { page, server, utils, userEvent } from '../browser/index.js';
- */
-async function patchBrowserProviderLocators() {
-  console.log('\nPatching browser provider locators.js files...');
-
-  const providers = [
-    { name: 'browser-playwright', extraImports: [] as string[] },
-    { name: 'browser-webdriverio', extraImports: ['utils'] },
-    { name: 'browser-preview', extraImports: ['utils', 'userEvent'] },
-  ];
-
-  for (const provider of providers) {
-    const locatorsPath = join(distDir, `@vitest/${provider.name}/locators.js`);
-
-    try {
-      await stat(locatorsPath);
-    } catch {
-      console.log(`  Warning: @vitest/${provider.name}/locators.js not found, skipping`);
-      continue;
-    }
-
-    let content = await readFile(locatorsPath, 'utf-8');
-    let patched = false;
-
-    // 1. Patch the vitest/browser import to separate page (from context.js) and other imports
-    // After rewriteVitestImports(), the import is: import { page, server, ... } from '../browser/index.js';
-    // We need:
-    //   - page from '../browser/context.js' (browser-safe)
-    //   - server removed (we'll use window.__vitest_worker__.config instead)
-    //   - other imports (utils, userEvent) still from '../browser/index.js'
-
-    if (provider.extraImports.length === 0) {
-      // playwright: just import page from context.js
-      const serverImportPattern =
-        /import \{ page, server \} from ['"]\.\.\/browser\/index\.js['"];?/;
-      if (serverImportPattern.test(content)) {
-        content = content.replace(
-          serverImportPattern,
-          `import { page } from '../browser/context.js';`,
-        );
-        console.log(`  [${provider.name}] Changed server import to browser-safe context import`);
-        patched = true;
-      }
-    } else {
-      // webdriverio/preview: import page from context.js, keep other imports from index.js
-      const extraImportsStr = provider.extraImports.join(', ');
-      const importPattern = new RegExp(
-        `import \\{ page, server, ${extraImportsStr} \\} from ['"]\\.\\./browser/index\\.js['"];?`,
-      );
-      if (importPattern.test(content)) {
-        const replacement = `import { page } from '../browser/context.js';\nimport { ${extraImportsStr} } from '../browser/index.js';`;
-        content = content.replace(importPattern, replacement);
-        console.log(
-          `  [${provider.name}] Split imports: page from context.js, {${extraImportsStr}} from index.js`,
-        );
-        patched = true;
-      }
-    }
-
-    if (!patched) {
-      console.log(`  Warning: [${provider.name}] Could not find server import to patch`);
-    }
-
-    // 2. Replace all server.config references with browser-accessible window.__vitest_worker__.config
-    // This handles both:
-    //   - server.config.browser.locators.testIdAttribute
-    //   - server.config.browser.ui
-    const serverConfigPattern = /server\.config\./g;
-    const matchCount = (content.match(serverConfigPattern) || []).length;
-    if (matchCount > 0) {
-      content = content.replace(serverConfigPattern, `window.__vitest_worker__.config.`);
-      console.log(
-        `  [${provider.name}] Replaced ${matchCount} server.config references with window.__vitest_worker__.config`,
-      );
-    }
-
-    await writeFile(locatorsPath, content, 'utf-8');
-    console.log(`  Successfully patched @vitest/${provider.name}/locators.js`);
-  }
-}
-
-/**
- * Create browser-compat.js shim that re-exports @vitest/browser compatible symbols.
- * This allows our package to be used as an override for @vitest/browser.
- */
-async function createBrowserCompatShim() {
-  console.log('\nCreating browser-compat shim...');
-
-  const browserIndexPath = join(distDir, '@vitest/browser/index.js');
-
-  try {
-    await stat(browserIndexPath);
-  } catch {
-    console.log('  Warning: @vitest/browser/index.js not found, skipping');
-    return;
-  }
-
-  const browserSymbols = [
-    'resolveScreenshotPath',
-    'defineBrowserProvider',
-    'parseKeyDef',
-    'defineBrowserCommand',
-  ];
-
-  const shimContent = `// Re-export @vitest/browser compatible symbols
-// This allows this package to be used as an override for @vitest/browser
-export { ${browserSymbols.join(', ')} } from './@vitest/browser/index.js';
-`;
-
-  const shimPath = join(distDir, 'browser-compat.js');
-  await writeFile(shimPath, shimContent, 'utf-8');
-  console.log(`  Created ${relative(projectDir, shimPath)}`);
-}
-
-/**
- * Create a browser-safe stub for module-runner.
- * The real module-runner contains Node.js-only code (process.platform, Buffer, etc.)
- * that causes browsers to hang when loaded. This stub provides empty/placeholder
- * exports so that browser code can import without errors.
- */
-async function createModuleRunnerStub() {
-  console.log('\nCreating browser-safe module-runner stub...');
-
-  const stubContent = `// Browser-safe stub for module-runner
-// The real module-runner contains Node.js-only code that crashes browsers
-// This stub provides placeholder exports for browser compatibility
-
-// Stub class - browser doesn't actually use these
-export class EvaluatedModules {
-  constructor() {
-    this.idToModuleMap = new Map();
-    this.fileToModulesMap = new Map();
-    this.urlToIdModuleMap = new Map();
-  }
-  getModuleById() { return undefined; }
-  getModulesByFile() { return []; }
-  getModuleByUrl() { return undefined; }
-  ensureModule() { return {}; }
-  invalidateModule() {}
-  clear() {}
-}
-
-export class ModuleRunner {
-  constructor() {}
-  async import() { throw new Error('ModuleRunner is not available in browser'); }
-  evaluatedModules = new EvaluatedModules();
-}
-
-export class ESModulesEvaluator {
-  constructor() {}
-  async runExternalModule() { return {}; }
-  async runViteModule() { return {}; }
-}
-
-// Stub functions
-export function createDefaultImportMeta() { return {}; }
-export function createNodeImportMeta() { return {}; }
-export function createWebSocketModuleRunnerTransport() { return {}; }
-export function normalizeModuleId(id) { return id; }
-
-// SSR-related constants (browser doesn't use these)
-export const ssrDynamicImportKey = '__vite_ssr_dynamic_import__';
-export const ssrExportAllKey = '__vite_ssr_exportAll__';
-export const ssrExportNameKey = '__vite_ssr_export__';
-export const ssrImportKey = '__vite_ssr_import__';
-export const ssrImportMetaKey = '__vite_ssr_import_meta__';
-export const ssrModuleExportsKey = '__vite_ssr_exports__';
-`;
-
-  const stubPath = join(distDir, 'module-runner-stub.js');
-  await writeFile(stubPath, stubContent, 'utf-8');
-  console.log(`  Created ${relative(projectDir, stubPath)}`);
-}
-
-/**
- * Create a Node.js-specific entry that includes @vitest/browser symbols.
- * Browser code will use index.js (no browser-provider imports) to avoid loading Node.js code.
- * Node.js code (like @vitest/browser-playwright) will use index-node.js which includes
- * the browser symbols needed for pnpm override compatibility.
- *
- * This separation is critical because @vitest/browser/index.js imports from vitest/node,
- * which contains Node.js-only code (including __vite__injectQuery) that crashes browsers.
- */
-async function createNodeEntry() {
-  console.log('\nCreating Node.js-specific entry for @vitest/browser override...');
-
-  const browserIndexPath = join(distDir, '@vitest/browser/index.js');
-
-  try {
-    await stat(browserIndexPath);
-  } catch {
-    console.log('  Warning: @vitest/browser/index.js not found, skipping');
-    return;
-  }
-
-  const browserSymbols = [
-    'resolveScreenshotPath',
-    'defineBrowserProvider',
-    'parseKeyDef',
-    'defineBrowserCommand',
-  ];
-
-  // Create index-node.js that re-exports everything from index.js plus browser symbols
-  const nodeEntry = `// Node.js-specific entry that includes @vitest/browser provider symbols
-// Browser code should use index.js which doesn't pull in Node.js-only code
-export * from './index.js';
-
-// Re-export @vitest/browser symbols for pnpm override compatibility
-// These are only needed when this package overrides @vitest/browser in Node.js context
-export { ${browserSymbols.join(', ')} } from './@vitest/browser/index.js';
-`;
-
-  const nodeEntryPath = join(distDir, 'index-node.js');
-  await writeFile(nodeEntryPath, nodeEntry, 'utf-8');
-  console.log(`  Created dist/index-node.js with @vitest/browser exports`);
-}
-
-/**
- * Copy ALL files from @vitest/browser's dist to our dist.
- * The bundled code in dist/vendor/ calculates paths like:
- *   pkgRoot = resolve(import.meta.url, "../..") -> package root
- *   distRoot = resolve(pkgRoot, "dist") -> dist/
- * Then looks for client/ files at distRoot, so we copy to dist/ not dist/vendor/.
- */
-async function copyBrowserClientFiles() {
-  console.log('\nCopying @vitest/browser files to dist...');
-
-  // Find @vitest/browser's dist directory
-  const vitestBrowserDist = resolve(projectDir, 'node_modules/@vitest/browser/dist');
-
-  // Check if it exists
-  try {
-    await stat(vitestBrowserDist);
-  } catch {
-    console.log('  Warning: @vitest/browser not installed, skipping');
-    return;
-  }
-
-  // Copy all files from @vitest/browser/dist to our dist/
-  // The bundled code at dist/vendor/ resolves paths relative to dist/
-  // Use recursive directory traversal to include dotfiles (glob doesn't handle them well)
-  let copiedCount = 0;
-
-  // Rewrite imports in copied JS files to use our dist files
-  // The relative path depends on the file's location relative to dist/
-  function rewriteImports(content: string, destPath: string): string {
-    const fileDir = parse(destPath).dir;
-
-    // Calculate relative path from file location to vendor directory
-    const vendorPath = join(distDir, 'vendor');
-    let relativeToVendor = relative(fileDir, vendorPath);
-    // Ensure path starts with ./ for relative imports
-    if (!relativeToVendor.startsWith('.')) {
-      relativeToVendor = './' + relativeToVendor;
-    }
-
-    // Calculate relative path from file location to dist directory
-    let relativeToDist = relative(fileDir, distDir);
-    if (!relativeToDist.startsWith('.')) {
-      relativeToDist = './' + relativeToDist;
-    }
-
-    // Rewrite @vitest/* imports to use our copied @vitest files
-    for (const [pkg, distPath] of Object.entries(VITEST_PACKAGE_TO_PATH)) {
-      if (!pkg.startsWith('@vitest/')) {
-        continue;
-      }
-      // Pattern: from"@vitest/runner" or from "@vitest/runner"
-      const importPattern = new RegExp(`from\\s*["']${pkg.replace('/', '\\/')}["']`, 'g');
-      content = content.replace(importPattern, `from"${relativeToDist}/${distPath}"`);
-    }
-
-    // Rewrite vitest/* subpath imports to use our dist files
-    // These are the actual entry points for vitest's browser-safe exports
-    const vitestSubpathRewrites: Record<string, string> = {
-      'vitest/browser': `${relativeToDist}/context.js`, // vitest/browser exports context API
-      'vitest/internal/browser': `${relativeToDist}/browser.js`,
-      'vitest/runners': `${relativeToDist}/runners.js`,
-    };
-    for (const [specifier, destFile] of Object.entries(vitestSubpathRewrites)) {
-      const importPattern = new RegExp(`from\\s*["']${specifier.replace('/', '\\/')}["']`, 'g');
-      content = content.replace(importPattern, `from"${destFile}"`);
-    }
-
-    // Special handling for @vitest/browser/client -> our client.js
-    // This is needed because the browser client files import from @vitest/browser/client
-    const browserClientPattern = /from\s*["']@vitest\/browser\/client["']/g;
-    content = content.replace(browserClientPattern, `from"${relativeToDist}/client.js"`);
-
-    // Handle imports from ./index.js which is Node.js-only code
-    // In browser context, 'server' should read from __vitest_browser_runner__ at runtime
-    // Replace: import{server}from'./index.js' with a browser-safe stub
-    const serverStub = `const server = {
-  get browser() { return window.__vitest_browser_runner__?.config?.browser?.name; },
-  get config() { return window.__vitest_browser_runner__?.config || {}; },
-  get commands() { return window.__vitest_browser_runner__?.commands || {}; },
-  get provider() { return window.__vitest_browser_runner__?.provider; },
-};`;
-    content = content.replace(
-      /import\s*\{\s*server\s*\}\s*from\s*['"]\.\/index\.js['"];?/g,
-      serverStub,
-    );
-
-    // Remove side-effect imports from ./index.js (Node.js-only)
-    // Pattern: import'./index.js'; at the end of an import statement
-    content = content.replace(/import\s*['"]\.\/index\.js['"];?/g, '');
-
-    return content;
-  }
-
-  async function copyDirRecursive(srcDir: string, destDir: string) {
-    const entries = await readdir(srcDir, { withFileTypes: true });
-
-    for (const entry of entries) {
-      const srcPath = join(srcDir, entry.name);
-      const destPath = join(destDir, entry.name);
-
-      if (entry.isDirectory()) {
-        await mkdir(destPath, { recursive: true });
-        await copyDirRecursive(srcPath, destPath);
-      } else if (entry.isFile()) {
-        // Skip if file already exists (our bundled code takes precedence)
-        try {
-          await stat(destPath);
-          continue;
-        } catch {
-          // File doesn't exist, copy it
-        }
-        await mkdir(parse(destPath).dir, { recursive: true });
-
-        // For JS files, rewrite imports; otherwise just copy
-        if (entry.name.endsWith('.js')) {
-          let content = await readFile(srcPath, 'utf-8');
-          content = rewriteImports(content, destPath);
-          await writeFile(destPath, content, 'utf-8');
-        } else {
-          await copyFile(srcPath, destPath);
-        }
-        copiedCount++;
-      }
-    }
-  }
-
-  await copyDirRecursive(vitestBrowserDist, distDir);
-
-  // Create dummy.js for placeholder exports (matchers, utils)
-  const dummyContent = '// Placeholder for browser compatibility\nexport {};\n';
-  await writeFile(join(distDir, 'dummy.js'), dummyContent, 'utf-8');
-
-  console.log(`  Copied ${copiedCount} files from @vitest/browser to dist`);
-
-  // Create vendor stubs for browser packages that aren't bundled
-  // Other dist files reference these vendor paths but we don't bundle browser packages
-  // to avoid Node.js code leakage. Instead, we create stubs that re-export from actual dist files.
-  console.log('  Creating vendor stubs for browser packages...');
-  const browserVendorStubs = [
-    {
-      vendorFile: 'vitest_browser.mjs',
-      // vitest/browser exports the context API (page, server, userEvent)
-      content: `// Stub for browser context - re-exports from our context.js
-export * from '../context.js';
-`,
-    },
-    {
-      vendorFile: 'vitest_internal_browser.mjs',
-      // vitest/internal/browser is browser.js
-      content: `// Stub for internal browser API - re-exports from our browser.js
-export * from '../browser.js';
-`,
-    },
-    {
-      vendorFile: 'vitest_runners.mjs',
-      // vitest/runners
-      content: `// Stub for runners - re-exports from our runners.js
-export * from '../runners.js';
-`,
-    },
-    {
-      vendorFile: 'vitest_runner.mjs',
-      // @vitest/runner (note: singular, not plural like vitest_runners which is vitest/runners)
-      content: `// Stub for @vitest/runner - re-exports from our copied @vitest/runner
-export * from '../@vitest/runner/index.js';
-`,
-    },
-  ];
-
-  for (const { vendorFile, content } of browserVendorStubs) {
-    const stubPath = join(distDir, 'vendor', vendorFile);
-    await writeFile(stubPath, content, 'utf-8');
-  }
-  console.log(`  Created ${browserVendorStubs.length} vendor stubs`);
-}
-
-/**
- * Create browser/ directory at package root with context files.
- * The package exports "./browser" pointing to these files:
- *   - browser/context.js: Runtime guard (throws if used outside browser mode)
- *   - browser/context.d.ts: Re-exports types from dist/@vitest/browser/context.d.ts
- *
- * These files are NOT tracked in git (.gitignore excludes browser/)
- * but ARE included in the package (package.json files: ["browser/**"])
- */
-async function createBrowserEntryFiles() {
-  console.log('\nCreating browser/ entry files...');
-
-  const browserDir = resolve(projectDir, 'browser');
-  await mkdir(browserDir, { recursive: true });
-
-  // 1. Copy context.js from @vitest/browser (runtime guard)
-  const srcContextJs = resolve(projectDir, 'node_modules/@vitest/browser/context.js');
-  const destContextJs = join(browserDir, 'context.js');
-  await copyFile(srcContextJs, destContextJs);
-  console.log('  Created browser/context.js');
-
-  // 2. Create context.d.ts that re-exports from our bundled types
-  const contextDtsContent = `// Re-export browser context types from bundled @vitest/browser package
-// This provides: page, userEvent, server, commands, utils, locators, cdp, Locator, etc.
-// The bundled context.d.ts has imports rewritten to point to our dist files
-export * from '../dist/@vitest/browser/context.d.ts'
-`;
-  const destContextDts = join(browserDir, 'context.d.ts');
-  await writeFile(destContextDts, contextDtsContent, 'utf-8');
-  console.log('  Created browser/context.d.ts');
-}
-
-/**
- * Patch module augmentations in global.d.*.d.ts files.
- *
- * The original vitest types use module augmentation like:
- *   declare module "@vitest/expect" { interface Assertion<T> { toMatchSnapshot: ... } }
- *
- * Since we bundle @vitest/* packages inside dist/@vitest/*, the bare specifier
- * "@vitest/expect" doesn't exist as a package for consumers. This breaks the
- * module augmentation - TypeScript can't find @vitest/expect to augment.
- *
- * The fix has two parts:
- * 1. Change module augmentation to use relative paths that TypeScript CAN resolve:
- *      declare module "../@vitest/expect/index.js" { ... }
- * 2. Merge augmented interface/type definitions into the target .d.ts files so that
- *    downstream DTS bundlers (rolldown) can resolve them without cross-file augmentation.
- */
-async function patchModuleAugmentations() {
-  console.log('\nPatching module augmentations in global.d.*.d.ts files...');
-
-  const chunksDir = join(distDir, 'chunks');
-  const globalDtsFiles: string[] = [];
-
-  // Find all global.d.*.d.ts files
-  for await (const file of fsGlob(join(chunksDir, 'global.d.*.d.ts'))) {
-    globalDtsFiles.push(file);
-  }
-
-  if (globalDtsFiles.length === 0) {
-    console.log('  No global.d.*.d.ts files found');
-    return;
-  }
-
-  // Module augmentation mappings: bare specifier -> [relative path, target .d.ts file]
-  const augmentationMappings: Record<string, { relativePath: string; targetFile: string }> = {
-    '@vitest/expect': {
-      relativePath: '../@vitest/expect/index.js',
-      targetFile: join(distDir, '@vitest/expect/index.d.ts'),
-    },
-    '@vitest/runner': {
-      relativePath: '../@vitest/runner/index.js',
-      targetFile: join(distDir, '@vitest/runner/utils.d.ts'),
-    },
-  };
-
-  for (const file of globalDtsFiles) {
-    let content = await readFile(file, 'utf-8');
-    let modified = false;
-
-    for (const [bareSpecifier, { relativePath, targetFile }] of Object.entries(
-      augmentationMappings,
-    )) {
-      const oldPattern = `declare module "${bareSpecifier}"`;
-
-      // Extract the augmentation block content using brace matching
-      const startIdx = content.indexOf(oldPattern);
-      const braceStart = startIdx !== -1 ? content.indexOf('{', startIdx) : -1;
-      if (braceStart === -1) {
-        continue;
-      }
-
-      let depth = 0;
-      let braceEnd = -1;
-      for (let i = braceStart; i < content.length; i++) {
-        if (content[i] === '{') {
-          depth++;
-        } else if (content[i] === '}') {
-          depth--;
-          if (depth === 0) {
-            braceEnd = i;
-            break;
-          }
-        }
-      }
-      if (braceEnd === -1) {
-        continue;
-      }
-
-      const innerContent = content.slice(braceStart + 1, braceEnd).trim();
-
-      // Merge only NEW type declarations into the target .d.ts file.
-      // Interfaces that already exist (e.g., ExpectStatic, Assertion, MatcherState) must NOT
-      // be re-declared, as that would shadow extends clauses and break call signatures.
-      if (innerContent && existsSync(targetFile)) {
-        let targetContent = await readFile(targetFile, 'utf-8');
-
-        // Extract individual interface blocks from the augmentation content
-        const interfaceRegex = /(?:export\s+)?interface\s+(\w+)(?:<[^>]*>)?\s*\{/g;
-        let match;
-        const newDeclarations: string[] = [];
-
-        while ((match = interfaceRegex.exec(innerContent)) !== null) {
-          const name = match[1];
-          // Only merge if this interface does NOT already exist in the target file.
-          // Check both direct declarations (interface Name) and re-exports (export type { Name }).
-          const hasDirectDecl = new RegExp(`\\binterface\\s+${name}\\b`).test(targetContent);
-          const exportTypeMatch = targetContent.match(/export\s+type\s*\{([^}]*)\}/);
-          const isReExported =
-            exportTypeMatch != null && new RegExp(`\\b${name}\\b`).test(exportTypeMatch[1]);
-          if (hasDirectDecl || isReExported) {
-            console.log(
-              `  Skipped existing interface "${name}" (already in ${basename(targetFile)})`,
-            );
-            continue;
-          }
-
-          // Extract this interface block using brace matching
-          const ifaceStart = match.index;
-          const ifaceBraceStart = innerContent.indexOf('{', ifaceStart);
-          let ifaceDepth = 0;
-          let ifaceBraceEnd = -1;
-          for (let i = ifaceBraceStart; i < innerContent.length; i++) {
-            if (innerContent[i] === '{') {
-              ifaceDepth++;
-            } else if (innerContent[i] === '}') {
-              ifaceDepth--;
-              if (ifaceDepth === 0) {
-                ifaceBraceEnd = i;
-                break;
-              }
-            }
-          }
-          if (ifaceBraceEnd === -1) {
-            continue;
-          }
-
-          let block = innerContent.slice(ifaceStart, ifaceBraceEnd + 1).trim();
-          if (!block.startsWith('export')) {
-            block = `export ${block}`;
-          }
-          newDeclarations.push(block);
-          console.log(`  Merged new interface "${name}" into ${basename(targetFile)}`);
-        }
-
-        if (newDeclarations.length > 0) {
-          targetContent += `\n// Merged from module augmentation: declare module "${bareSpecifier}"\n${newDeclarations.join('\n')}\n`;
-          await writeFile(targetFile, targetContent, 'utf-8');
-        }
-      }
-
-      // Rewrite declare module path to relative
-      const newPattern = `declare module "${relativePath}"`;
-      content = content.replaceAll(oldPattern, newPattern);
-      modified = true;
-      console.log(`  Patched: ${bareSpecifier} -> ${relativePath} in ${basename(file)}`);
-    }
-
-    if (modified) {
-      await writeFile(file, content, 'utf-8');
-    }
-  }
-
-  // Re-export BrowserCommands from context.d.ts (imported but not exported)
-  const contextDtsPath = join(distDir, '@vitest/browser/context.d.ts');
-  if (existsSync(contextDtsPath)) {
-    let content = await readFile(contextDtsPath, 'utf-8');
-    if (
-      content.includes('BrowserCommands') &&
-      !content.match(/export\s+(type\s+)?\{[^}]*BrowserCommands/)
-    ) {
-      content += '\nexport type { BrowserCommands };\n';
-      await writeFile(contextDtsPath, content, 'utf-8');
-      console.log('  Added BrowserCommands re-export to context.d.ts');
-    }
-  }
-
-  // Validate: ensure no duplicate top-level interface declarations were introduced by merging.
-  // Only count interfaces at the module scope (not nested inside declare global, namespace, etc.)
-  for (const [bareSpecifier, { targetFile }] of Object.entries(augmentationMappings)) {
-    if (!existsSync(targetFile)) {
-      continue;
-    }
-    const finalContent = await readFile(targetFile, 'utf-8');
-
-    // Extract top-level interface names by tracking brace depth
-    const topLevelInterfaces: string[] = [];
-    let depth = 0;
-    for (let i = 0; i < finalContent.length; i++) {
-      if (finalContent[i] === '{') {
-        depth++;
-      } else if (finalContent[i] === '}') {
-        depth--;
-      } else if (depth === 0) {
-        const remaining = finalContent.slice(i);
-        const m = remaining.match(/^interface\s+(\w+)/);
-        if (m) {
-          topLevelInterfaces.push(m[1]);
-          i += m[0].length - 1;
-        }
-      }
-    }
-
-    const counts = new Map<string, number>();
-    for (const name of topLevelInterfaces) {
-      counts.set(name, (counts.get(name) || 0) + 1);
-    }
-
-    for (const [name, count] of counts) {
-      if (count > 1) {
-        throw new Error(
-          `Interface "${name}" is declared ${count} times at top level in ${basename(targetFile)}. ` +
-            `Module augmentation merge for "${bareSpecifier}" likely created a duplicate ` +
-            `declaration that will shadow extends clauses and break type signatures.`,
-        );
-      }
-    }
-  }
-}
-
-/**
- * Add triple-slash reference to @types/chai in @vitest/expect types.
- *
- * The @vitest/expect types use the Chai namespace (e.g., Chai.Assertion) which
- * is defined in @types/chai. Without a reference directive, TypeScript won't
- * automatically find the Chai types, causing the `not` property and other
- * chai-specific features to be missing from the Assertion interface.
- */
-async function patchChaiTypeReference() {
-  console.log('\nAdding @types/chai reference to @vitest/expect types...');
-
-  const expectIndexDts = join(distDir, '@vitest/expect/index.d.ts');
-
-  let content = await readFile(expectIndexDts, 'utf-8');
-
-  // Check if reference already exists
-  if (content.includes('/// <reference types="chai"')) {
-    console.log('  Reference already exists, skipping');
-    return;
-  }
-
-  // Add triple-slash reference at the top
-  content = `/// <reference types="chai" />\n${content}`;
-
-  await writeFile(expectIndexDts, content, 'utf-8');
-  console.log('  Added /// <reference types="chai" /> to @vitest/expect/index.d.ts');
-}
-
-/**
- * Patch the vitest mocker to recognize @voidzero-dev packages as valid sources for vi/vitest.
- *
- * The mocker's hoistMocks function checks if `vi` is imported from the 'vitest' module.
- * When users import from 'vite-plus/test' instead, the mocker doesn't
- * recognize it and throws "There are some problems in resolving the mocks API".
- *
- * This patch modifies the equality check to also accept our package names:
- * - vite-plus/test
- * - @voidzero-dev/vite-plus-test
- */
-async function patchMockerHoistedModule() {
-  console.log('\nPatching vitest mocker to recognize @voidzero-dev packages...');
-
-  // The hoistedModule check may be in node.js or chunk-hoistMocks.js depending on the vitest version
-  const candidateFiles = [
-    join(distDir, '@vitest/mocker/node.js'),
-    join(distDir, '@vitest/mocker/chunk-hoistMocks.js'),
-  ];
-
-  // Find and replace the hoistedModule check
-  // Original: if (hoistedModule === source) {
-  // New: if (hoistedModule === source || source === "vite-plus/test" || source === "@voidzero-dev/vite-plus-test") {
-  const originalCheck = 'if (hoistedModule === source) {';
-  const newCheck =
-    'if (hoistedModule === source || source === "vite-plus/test" || source === "@voidzero-dev/vite-plus-test") {';
-
-  let patched = false;
-  for (const candidatePath of candidateFiles) {
-    let content: string;
-    try {
-      content = await readFile(candidatePath, 'utf-8');
-    } catch {
-      continue;
-    }
-    if (content.includes(originalCheck)) {
-      content = content.replace(originalCheck, newCheck);
-      await writeFile(candidatePath, content, 'utf-8');
-      console.log(`  Patched hoistMocks to recognize @voidzero-dev packages in ${candidatePath}`);
-      patched = true;
-      break;
-    }
-  }
-
-  if (!patched) {
-    throw new Error(
-      'Could not find hoistedModule check to patch in @vitest/mocker. ' +
-        'This likely means vitest code has changed and the patch needs to be updated.',
-    );
-  }
-}
-
-/**
- * Patch vitest's ModuleRunnerTransform plugin to automatically add known
- * packages that use `expect.extend()` internally to `server.deps.inline`.
- *
- * When third-party libraries (e.g., @testing-library/jest-dom) call
- * `require('vitest').expect.extend(matchers)`, the npm override causes
- * a separate module instance to be created, so matchers are registered
- * on a different `chai` instance than the one used by the test runner.
- *
- * By inlining these packages via `server.deps.inline`, the Vite module
- * runner processes them through its transform pipeline, ensuring they
- * share the same module instance as the test runner.
- *
- * See: https://github.com/voidzero-dev/vite-plus/issues/897
- */
-async function patchServerDepsInline() {
-  console.log('\nPatching server.deps.inline for expect.extend compatibility...');
-
-  let cliApiChunk: string | undefined;
-  for await (const chunk of fsGlob(join(distDir, 'chunks/cli-api.*.js'))) {
-    cliApiChunk = chunk;
-    break;
-  }
-
-  if (!cliApiChunk) {
-    throw new Error('cli-api chunk not found for patchServerDepsInline');
-  }
-
-  let content = await readFile(cliApiChunk, 'utf-8');
-
-  // Packages that internally call expect.extend() and break under npm override.
-  // These must be inlined so they share the same vitest module instance.
-  const inlinePackages = ['@testing-library/jest-dom', '@storybook/test', 'jest-extended'];
-
-  // Find the configResolved handler in ModuleRunnerTransform (vitest:environments-module-runner)
-  // and inject our inline packages after the existing server.deps.inline logic.
-  const original = `if (external.length) {
-          testConfig.server.deps.external ??= [];
-          testConfig.server.deps.external.push(...external);
-        }`;
-
-  const patched = `if (external.length) {
-          testConfig.server.deps.external ??= [];
-          testConfig.server.deps.external.push(...external);
-        }
-        // Auto-inline packages that use expect.extend() internally (#897)
-        // Only inline packages that are actually installed in the project.
-        if (testConfig.server.deps.inline !== true) {
-          testConfig.server.deps.inline ??= [];
-          if (Array.isArray(testConfig.server.deps.inline)) {
-            const _require = createRequire(config.root + "/package.json");
-            const autoInline = ${JSON.stringify(inlinePackages)};
-            for (const pkg of autoInline) {
-              if (testConfig.server.deps.inline.includes(pkg)) continue;
-              try {
-                _require.resolve(pkg);
-                testConfig.server.deps.inline.push(pkg);
-              } catch {
-                // Package not installed in the project — skip silently
-              }
-            }
-          }
-        }`;
-
-  if (!content.includes(original)) {
-    throw new Error(
-      'Could not find server.deps.external pattern in ' +
-        cliApiChunk +
-        '. This likely means vitest code has changed and the patch needs to be updated.',
-    );
-  }
-
-  content = content.replace(original, patched);
-  await writeFile(cliApiChunk, content, 'utf-8');
-  console.log(`  Added auto-inline for: ${inlinePackages.join(', ')}`);
-}
-
-/**
- * Create /plugins/* exports for all copied @vitest/* packages.
- * This allows pnpm overrides to redirect @vitest/* imports to our copied versions.
- * e.g., @vitest/runner -> vitest/plugins/runner
- *       @vitest/utils/error -> vitest/plugins/utils-error
- */
-async function createPluginExports() {
-  console.log('\nCreating plugin exports for @vitest/* packages...');
-
-  const pluginsDir = join(distDir, 'plugins');
-  // Clean up stale plugin files from previous builds
-  await rm(pluginsDir, { recursive: true, force: true });
-  await mkdir(pluginsDir, { recursive: true });
-
-  const createdExports: Array<{ exportPath: string; shimFile: string }> = [];
-
-  for (const [pkg, distPath] of Object.entries(VITEST_PACKAGE_TO_PATH)) {
-    // Only create exports for @vitest/* packages
-    if (!pkg.startsWith('@vitest/')) {
-      continue;
-    }
-    // Convert @vitest/runner -> runner, @vitest/utils/error -> utils-error
-    // @vitest/utils/source-map/node -> utils-source-map-node
-    const exportName = pkg.replace('@vitest/', '').replaceAll('/', '-');
-    const shimFileName = `${exportName}.mjs`;
-    const shimPath = join(pluginsDir, shimFileName);
-
-    // Create the shim file that re-exports everything from @vitest/
-    const shimContent = `// Re-export ${pkg} from copied @vitest package
-export * from '../${distPath}';
-`;
-
-    await writeFile(shimPath, shimContent, 'utf-8');
-    createdExports.push({
-      exportPath: `./plugins/${exportName}`,
-      shimFile: `./dist/plugins/${shimFileName}`,
-    });
-    console.log(`  Created plugins/${shimFileName} -> ${distPath}`);
-  }
-
-  return createdExports;
-}
-
-/**
- * Validate that all external dependencies in dist are listed in package.json
- */
-async function validateExternalDeps() {
-  console.log('\nValidating external dependencies...');
-
-  // Collect all declared dependencies
-  const declaredDeps = new Set<string>([
-    ...Object.keys(pkg.dependencies || {}),
-    ...Object.keys(pkg.peerDependencies || {}),
-  ]);
-
-  // Also include self-references
-  declaredDeps.add(pkg.name);
-  declaredDeps.add('vitest'); // Self-reference via vitest name
-
-  // Collect all external specifiers from ALL dist files (including vendor)
-  const externalSpecifiers = new Map<string, Set<string>>(); // specifier -> files
-
-  const allJsFiles = fsGlob(join(distDir, '**/*.{js,mjs,cjs}'));
-
-  for await (const file of allJsFiles) {
-    const content = await readFile(file, 'utf-8');
-    const isCjs = file.endsWith('.cjs');
-
-    // Parse with oxc-parser
-    const result = parseSync(file, content, {
-      sourceType: isCjs ? 'script' : 'module',
-    });
-
-    const specifiers = new Set<string>();
-
-    // Collect ESM static imports
-    for (const imp of result.module.staticImports) {
-      specifiers.add(imp.moduleRequest.value);
-    }
-
-    // Collect ESM static exports (re-exports)
-    for (const exp of result.module.staticExports) {
-      for (const entry of exp.entries) {
-        if (entry.moduleRequest) {
-          specifiers.add(entry.moduleRequest.value);
-        }
-      }
-    }
-
-    // Collect dynamic imports (only string literals)
-    for (const dynImp of result.module.dynamicImports) {
-      const rawText = content.slice(dynImp.moduleRequest.start, dynImp.moduleRequest.end);
-      if (
-        (rawText.startsWith("'") && rawText.endsWith("'")) ||
-        (rawText.startsWith('"') && rawText.endsWith('"'))
-      ) {
-        specifiers.add(rawText.slice(1, -1));
-      }
-    }
-
-    // For CJS files, also scan for require() calls
-    if (isCjs) {
-      const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
-      let match;
-      while ((match = requireRegex.exec(content)) !== null) {
-        specifiers.add(match[1]);
-      }
-    }
-
-    // Filter and record external specifiers
-    for (const specifier of specifiers) {
-      // Skip relative paths
-      if (specifier.startsWith('.') || specifier.startsWith('/')) {
-        continue;
-      }
-      // Skip node built-ins
-      if (NODE_BUILTINS.has(specifier)) {
-        continue;
-      }
-      // Skip Node.js subpath imports
-      if (specifier.startsWith('#')) {
-        continue;
-      }
-
-      // Get the package name (handle scoped packages and subpaths)
-      const packageName = getPackageName(specifier);
-      if (!packageName) {
-        continue;
-      }
-
-      // Check if it's declared
-      if (declaredDeps.has(packageName)) {
-        continue;
-      }
-      // Check if it's in the blocklist (intentionally external)
-      if (EXTERNAL_BLOCKLIST.has(packageName) || EXTERNAL_BLOCKLIST.has(specifier)) {
-        continue;
-      }
-
-      // Record undeclared external
-      if (!externalSpecifiers.has(specifier)) {
-        externalSpecifiers.set(specifier, new Set());
-      }
-      externalSpecifiers.get(specifier)!.add(relative(distDir, file));
-    }
-  }
-
-  if (externalSpecifiers.size === 0) {
-    console.log('  ✓ All external dependencies are declared in package.json');
-    return;
-  }
-
-  // Group by package name
-  const byPackage = new Map<string, Set<string>>();
-  for (const [specifier, _files] of externalSpecifiers) {
-    const packageName = getPackageName(specifier)!;
-    if (!byPackage.has(packageName)) {
-      byPackage.set(packageName, new Set());
-    }
-    byPackage.get(packageName)!.add(specifier);
-  }
-
-  console.log(`\n  ⚠ Found ${byPackage.size} undeclared external dependencies:\n`);
-  for (const [packageName, specifiers] of byPackage.entries()) {
-    const files = externalSpecifiers.get([...specifiers][0])!;
-    console.log(`    ${packageName}`);
-    for (const specifier of specifiers) {
-      if (specifier !== packageName) {
-        console.log(`      - ${specifier}`);
-      }
-    }
-    console.log(
-      `      (used in: ${[...files].slice(0, 3).join(', ')}${files.size > 3 ? '...' : ''})`,
-    );
-  }
-}
-
-/**
- * Extract the package name from a specifier (handles scoped packages and subpaths)
- */
-function getPackageName(specifier: string): string | null {
-  // Scoped package: @scope/name or @scope/name/subpath
-  if (specifier.startsWith('@')) {
-    const parts = specifier.split('/');
-    if (parts.length >= 2) {
-      return `${parts[0]}/${parts[1]}`;
-    }
-    return null;
-  }
-  // Regular package: name or name/subpath
-  const parts = specifier.split('/');
-  return parts[0] || null;
-}
diff --git a/packages/test/package.json b/packages/test/package.json
deleted file mode 100644
index a2b3e54546..0000000000
--- a/packages/test/package.json
+++ /dev/null
@@ -1,368 +0,0 @@
-{
-  "name": "@voidzero-dev/vite-plus-test",
-  "version": "0.1.24",
-  "description": "The Unified Toolchain for the Web",
-  "homepage": "https://viteplus.dev/guide",
-  "bugs": {
-    "url": "https://github.com/voidzero-dev/vite-plus/issues"
-  },
-  "license": "MIT",
-  "author": "VoidZero Inc.",
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/voidzero-dev/vite-plus.git",
-    "directory": "packages/test"
-  },
-  "files": [
-    "*.cjs",
-    "*.cts",
-    "*.d.ts",
-    "*.mjs",
-    "browser/**",
-    "dist/**"
-  ],
-  "type": "module",
-  "sideEffects": false,
-  "main": "./dist/index.js",
-  "module": "./dist/index.js",
-  "types": "./dist/index.d.ts",
-  "imports": {
-    "#module-evaluator": {
-      "types": "./dist/module-evaluator.d.ts",
-      "default": "./dist/module-evaluator.js"
-    },
-    "#nodejs-worker-loader": "./dist/nodejs-worker-loader.js"
-  },
-  "exports": {
-    ".": {
-      "import": {
-        "types": "./dist/index.d.ts",
-        "browser": "./dist/index.js",
-        "node": "./dist/index-node.js",
-        "default": "./dist/index.js"
-      },
-      "require": {
-        "types": "./index.d.cts",
-        "default": "./index.cjs"
-      }
-    },
-    "./browser": {
-      "types": "./browser/context.d.ts",
-      "default": "./browser/context.js"
-    },
-    "./package.json": "./package.json",
-    "./optional-types.js": {
-      "types": "./optional-types.d.ts"
-    },
-    "./optional-runtime-types.js": {
-      "types": "./optional-runtime-types.d.ts"
-    },
-    "./src/*": "./src/*",
-    "./globals": {
-      "types": "./globals.d.ts"
-    },
-    "./jsdom": {
-      "types": "./jsdom.d.ts"
-    },
-    "./importMeta": {
-      "types": "./importMeta.d.ts"
-    },
-    "./import-meta": {
-      "types": "./import-meta.d.ts"
-    },
-    "./node": {
-      "types": "./dist/node.d.ts",
-      "default": "./dist/node.js"
-    },
-    "./internal/browser": {
-      "types": "./dist/browser.d.ts",
-      "default": "./dist/browser.js"
-    },
-    "./runners": {
-      "types": "./dist/runners.d.ts",
-      "default": "./dist/runners.js"
-    },
-    "./suite": {
-      "types": "./dist/suite.d.ts",
-      "default": "./dist/suite.js"
-    },
-    "./environments": {
-      "types": "./dist/environments.d.ts",
-      "default": "./dist/environments.js"
-    },
-    "./config": {
-      "types": "./config.d.ts",
-      "require": "./dist/config.cjs",
-      "default": "./dist/config.js"
-    },
-    "./coverage": {
-      "types": "./coverage.d.ts",
-      "default": "./dist/coverage.js"
-    },
-    "./reporters": {
-      "types": "./dist/reporters.d.ts",
-      "default": "./dist/reporters.js"
-    },
-    "./snapshot": {
-      "types": "./dist/snapshot.d.ts",
-      "default": "./dist/snapshot.js"
-    },
-    "./runtime": {
-      "types": "./dist/runtime.d.ts",
-      "default": "./dist/runtime.js"
-    },
-    "./worker": {
-      "types": "./worker.d.ts",
-      "default": "./dist/worker.js"
-    },
-    "./browser-compat": {
-      "default": "./dist/browser-compat.js"
-    },
-    "./client": {
-      "default": "./dist/client.js"
-    },
-    "./context": {
-      "types": "./browser/context.d.ts",
-      "default": "./dist/@vitest/browser/context.js"
-    },
-    "./browser/context": {
-      "types": "./browser/context.d.ts",
-      "default": "./dist/@vitest/browser/context.js"
-    },
-    "./locators": {
-      "default": "./dist/locators.js"
-    },
-    "./matchers": {
-      "default": "./dist/dummy.js"
-    },
-    "./utils": {
-      "default": "./dist/dummy.js"
-    },
-    "./browser-playwright": {
-      "types": "./dist/@vitest/browser-playwright/index.d.ts",
-      "default": "./dist/@vitest/browser-playwright/index.js"
-    },
-    "./browser-webdriverio": {
-      "types": "./dist/@vitest/browser-webdriverio/index.d.ts",
-      "default": "./dist/@vitest/browser-webdriverio/index.js"
-    },
-    "./browser-preview": {
-      "types": "./dist/@vitest/browser-preview/index.d.ts",
-      "default": "./dist/@vitest/browser-preview/index.js"
-    },
-    "./browser/providers/playwright": {
-      "types": "./dist/@vitest/browser-playwright/index.d.ts",
-      "default": "./dist/@vitest/browser-playwright/index.js"
-    },
-    "./browser/providers/webdriverio": {
-      "types": "./dist/@vitest/browser-webdriverio/index.d.ts",
-      "default": "./dist/@vitest/browser-webdriverio/index.js"
-    },
-    "./browser/providers/preview": {
-      "types": "./dist/@vitest/browser-preview/index.d.ts",
-      "default": "./dist/@vitest/browser-preview/index.js"
-    },
-    "./plugins/runner": {
-      "default": "./dist/plugins/runner.mjs"
-    },
-    "./plugins/runner-utils": {
-      "default": "./dist/plugins/runner-utils.mjs"
-    },
-    "./plugins/runner-types": {
-      "default": "./dist/plugins/runner-types.mjs"
-    },
-    "./plugins/utils": {
-      "default": "./dist/plugins/utils.mjs"
-    },
-    "./plugins/utils-source-map": {
-      "default": "./dist/plugins/utils-source-map.mjs"
-    },
-    "./plugins/utils-source-map-node": {
-      "default": "./dist/plugins/utils-source-map-node.mjs"
-    },
-    "./plugins/utils-error": {
-      "default": "./dist/plugins/utils-error.mjs"
-    },
-    "./plugins/utils-helpers": {
-      "default": "./dist/plugins/utils-helpers.mjs"
-    },
-    "./plugins/utils-display": {
-      "default": "./dist/plugins/utils-display.mjs"
-    },
-    "./plugins/utils-timers": {
-      "default": "./dist/plugins/utils-timers.mjs"
-    },
-    "./plugins/utils-highlight": {
-      "default": "./dist/plugins/utils-highlight.mjs"
-    },
-    "./plugins/utils-offset": {
-      "default": "./dist/plugins/utils-offset.mjs"
-    },
-    "./plugins/utils-resolver": {
-      "default": "./dist/plugins/utils-resolver.mjs"
-    },
-    "./plugins/utils-serialize": {
-      "default": "./dist/plugins/utils-serialize.mjs"
-    },
-    "./plugins/utils-constants": {
-      "default": "./dist/plugins/utils-constants.mjs"
-    },
-    "./plugins/utils-diff": {
-      "default": "./dist/plugins/utils-diff.mjs"
-    },
-    "./plugins/spy": {
-      "default": "./dist/plugins/spy.mjs"
-    },
-    "./plugins/expect": {
-      "default": "./dist/plugins/expect.mjs"
-    },
-    "./plugins/snapshot": {
-      "default": "./dist/plugins/snapshot.mjs"
-    },
-    "./plugins/snapshot-environment": {
-      "default": "./dist/plugins/snapshot-environment.mjs"
-    },
-    "./plugins/snapshot-manager": {
-      "default": "./dist/plugins/snapshot-manager.mjs"
-    },
-    "./plugins/mocker": {
-      "default": "./dist/plugins/mocker.mjs"
-    },
-    "./plugins/mocker-node": {
-      "default": "./dist/plugins/mocker-node.mjs"
-    },
-    "./plugins/mocker-browser": {
-      "default": "./dist/plugins/mocker-browser.mjs"
-    },
-    "./plugins/mocker-redirect": {
-      "default": "./dist/plugins/mocker-redirect.mjs"
-    },
-    "./plugins/mocker-transforms": {
-      "default": "./dist/plugins/mocker-transforms.mjs"
-    },
-    "./plugins/mocker-automock": {
-      "default": "./dist/plugins/mocker-automock.mjs"
-    },
-    "./plugins/mocker-register": {
-      "default": "./dist/plugins/mocker-register.mjs"
-    },
-    "./plugins/pretty-format": {
-      "default": "./dist/plugins/pretty-format.mjs"
-    },
-    "./plugins/browser": {
-      "default": "./dist/plugins/browser.mjs"
-    },
-    "./plugins/browser-context": {
-      "default": "./dist/plugins/browser-context.mjs"
-    },
-    "./plugins/browser-client": {
-      "default": "./dist/plugins/browser-client.mjs"
-    },
-    "./plugins/browser-locators": {
-      "default": "./dist/plugins/browser-locators.mjs"
-    },
-    "./plugins/browser-playwright": {
-      "default": "./dist/plugins/browser-playwright.mjs"
-    },
-    "./plugins/browser-webdriverio": {
-      "default": "./dist/plugins/browser-webdriverio.mjs"
-    },
-    "./plugins/browser-preview": {
-      "default": "./dist/plugins/browser-preview.mjs"
-    }
-  },
-  "scripts": {
-    "build": "oxnode -C dev ./build.ts"
-  },
-  "dependencies": {
-    "@standard-schema/spec": "^1.1.0",
-    "@types/chai": "^5.2.2",
-    "@voidzero-dev/vite-plus-core": "workspace:*",
-    "es-module-lexer": "^1.7.0",
-    "obug": "^2.1.1",
-    "pixelmatch": "^7.1.0",
-    "pngjs": "^7.0.0",
-    "sirv": "^3.0.2",
-    "std-env": "^4.0.0",
-    "tinybench": "^2.9.0",
-    "tinyexec": "^1.0.2",
-    "tinyglobby": "^0.2.15",
-    "ws": "^8.18.3"
-  },
-  "devDependencies": {
-    "@blazediff/core": "1.9.2",
-    "@oxc-node/cli": "catalog:",
-    "@oxc-node/core": "catalog:",
-    "@vitest/browser": "4.1.8",
-    "@vitest/browser-playwright": "4.1.8",
-    "@vitest/browser-preview": "4.1.8",
-    "@vitest/browser-webdriverio": "4.1.8",
-    "@vitest/expect": "4.1.8",
-    "@vitest/mocker": "4.1.8",
-    "@vitest/pretty-format": "4.1.8",
-    "@vitest/runner": "4.1.8",
-    "@vitest/snapshot": "4.1.8",
-    "@vitest/spy": "4.1.8",
-    "@vitest/utils": "4.1.8",
-    "chai": "^6.2.1",
-    "convert-source-map": "^2.0.0",
-    "estree-walker": "^3.0.3",
-    "expect-type": "^1.2.2",
-    "magic-string": "^0.30.21",
-    "oxc-parser": "catalog:",
-    "oxfmt": "catalog:",
-    "pathe": "^2.0.3",
-    "picomatch": "^4.0.3",
-    "rolldown": "workspace:*",
-    "rolldown-plugin-dts": "catalog:",
-    "tinyrainbow": "^3.1.0",
-    "vitest-dev": "^4.1.8",
-    "why-is-node-running": "^2.3.0"
-  },
-  "peerDependencies": {
-    "@edge-runtime/vm": "*",
-    "@opentelemetry/api": "^1.9.0",
-    "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
-    "@vitest/coverage-istanbul": "4.1.8",
-    "@vitest/coverage-v8": "4.1.8",
-    "@vitest/ui": "4.1.8",
-    "happy-dom": "*",
-    "jsdom": "*",
-    "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
-  },
-  "peerDependenciesMeta": {
-    "@edge-runtime/vm": {
-      "optional": true
-    },
-    "@opentelemetry/api": {
-      "optional": true
-    },
-    "@types/node": {
-      "optional": true
-    },
-    "@vitest/coverage-istanbul": {
-      "optional": true
-    },
-    "@vitest/coverage-v8": {
-      "optional": true
-    },
-    "@vitest/ui": {
-      "optional": true
-    },
-    "happy-dom": {
-      "optional": true
-    },
-    "jsdom": {
-      "optional": true
-    },
-    "vite": {
-      "optional": false
-    }
-  },
-  "engines": {
-    "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
-  },
-  "bundledVersions": {
-    "vitest": "4.1.8"
-  }
-}
diff --git a/packages/test/tsconfig.json b/packages/test/tsconfig.json
deleted file mode 100644
index 62406593a8..0000000000
--- a/packages/test/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "extends": "../../tsconfig.json",
-  "compilerOptions": {
-    "experimentalDecorators": true
-  },
-  "files": [],
-  "include": [],
-  "exclude": ["**/*"]
-}
diff --git a/packages/tools/package.json b/packages/tools/package.json
index 139901c821..f9c7ae6d02 100644
--- a/packages/tools/package.json
+++ b/packages/tools/package.json
@@ -12,15 +12,16 @@
   },
   "dependencies": {
     "@yarnpkg/fslib": "catalog:",
-    "@yarnpkg/shell": "catalog:"
+    "@yarnpkg/shell": "catalog:",
+    "nanotar": "catalog:"
   },
   "devDependencies": {
     "@oxc-node/cli": "catalog:",
     "@oxc-node/core": "catalog:",
     "@types/semver": "catalog:",
-    "@voidzero-dev/vite-plus-test": "workspace:*",
     "minimatch": "catalog:",
     "semver": "catalog:",
+    "vitest": "catalog:",
     "yaml": "catalog:"
   }
 }
diff --git a/packages/tools/src/__tests__/sync-remote-deps.spec.ts b/packages/tools/src/__tests__/sync-remote-deps.spec.ts
index f530b59045..9c0ce531ad 100644
--- a/packages/tools/src/__tests__/sync-remote-deps.spec.ts
+++ b/packages/tools/src/__tests__/sync-remote-deps.spec.ts
@@ -1,5 +1,5 @@
-import { describe, expect, test } from '@voidzero-dev/vite-plus-test';
 import * as semver from 'semver';
+import { describe, expect, test } from 'vitest';
 
 import { mergePnpmWorkspaces, syncCargoOxcVersions } from '../sync-remote-deps.ts';
 
diff --git a/packages/tools/src/__tests__/sync-remote-yaml.spec.ts b/packages/tools/src/__tests__/sync-remote-yaml.spec.ts
index 56f2370864..a417ee215a 100644
--- a/packages/tools/src/__tests__/sync-remote-yaml.spec.ts
+++ b/packages/tools/src/__tests__/sync-remote-yaml.spec.ts
@@ -1,5 +1,5 @@
-import { describe, expect, test } from '@voidzero-dev/vite-plus-test';
 import * as semver from 'semver';
+import { describe, expect, test } from 'vitest';
 import * as yaml from 'yaml';
 
 import { mergePnpmWorkspaces, mergeWorkspaceYaml } from '../sync-remote-deps.ts';
diff --git a/packages/tools/src/__tests__/utils.spec.ts b/packages/tools/src/__tests__/utils.spec.ts
index 5db49f2731..df1d07ef95 100644
--- a/packages/tools/src/__tests__/utils.spec.ts
+++ b/packages/tools/src/__tests__/utils.spec.ts
@@ -3,7 +3,7 @@ import fs from 'node:fs';
 import { homedir, tmpdir } from 'node:os';
 import path from 'node:path';
 
-import { describe, expect, test } from '@voidzero-dev/vite-plus-test';
+import { describe, expect, test } from 'vitest';
 
 import { isPassThroughEnv, replaceUnstableOutput } from '../utils.ts';
 
@@ -60,6 +60,55 @@ bar@v1.0.0
     }
   });
 
+  test('replace vitest-family JSON version pins', () => {
+    // these track the bundled VITEST_VERSION and bump on every daily upgrade-deps run,
+    // so masking keeps catalog / devDependency snaps stable across bumps
+    const output = [
+      '  "catalog": {',
+      '    "vite": "npm:@voidzero-dev/vite-plus-core@latest",',
+      '    "vitest": "4.1.9",',
+      '    "vite-plus": "latest"',
+      '  },',
+      '  "devDependencies": {',
+      '    "@vitest/browser-playwright": "4.1.9",',
+      '    "@vitest/browser-webdriverio": "4.1.9-beta.2"',
+      '  }',
+    ].join('\n');
+    const result = replaceUnstableOutput(output);
+    expect(result).toContain('"vitest": "<semver>"');
+    expect(result).toContain('"@vitest/browser-playwright": "<semver>"');
+    expect(result).toContain('"@vitest/browser-webdriverio": "<semver>"');
+    // range specs and the vite-plus alias must stay untouched
+    expect(result).toContain('"vite": "npm:@voidzero-dev/vite-plus-core@latest"');
+    expect(result).toContain('"vite-plus": "latest"');
+  });
+
+  test('keeps user-owned @vitest/coverage-* provider pins visible', () => {
+    // coverage providers are project-installed peers vite-plus never pins; the
+    // runtime guard fail-fasts on version skew, so the exact version must stay
+    // visible in snapshots rather than collapse to <semver>
+    const output = [
+      '    "@vitest/coverage-v8": "4.1.8",',
+      '    "@vitest/coverage-istanbul": "4.1.8"',
+    ].join('\n');
+    expect(replaceUnstableOutput(output)).toBe(output);
+  });
+
+  test('keeps non-exact vitest specs (catalog: / ranges) unmasked', () => {
+    const output = ['    "vitest": "catalog:",', '    "vitest": "^4.0.0"'].join('\n');
+    expect(replaceUnstableOutput(output)).toBe(output);
+  });
+
+  test('keeps unsupported pre-4 vitest pins visible (migration-refusal echoes)', () => {
+    // The migrator refuses to manage vitest < 4, so a pre-4 JSON pin is always a
+    // user's OWN untouched package.json echoed by an unsupported-version fixture.
+    // Masking it would hide a regression that rewrote the dependency before failing.
+    expect(replaceUnstableOutput('    "vitest": "3.2.4"')).toBe('    "vitest": "3.2.4"');
+    expect(replaceUnstableOutput('    "@vitest/spy": "3.0.0"')).toBe('    "@vitest/spy": "3.0.0"');
+    // future double-digit majors still mask
+    expect(replaceUnstableOutput('    "vitest": "10.0.1"')).toBe('    "vitest": "<semver>"');
+  });
+
   test('replace date', () => {
     const output = `
 Start at  15:01:23
@@ -126,6 +175,15 @@ Done in 171ms using pnpm v10.16.1
     }
   });
 
+  test('strip pnpm supply-chain verification status line', () => {
+    const output = `
+> vp add testnpm2 -D
+✓ Lockfile passes supply-chain policies (verified 123ms ago)
+Packages: +1
+    `;
+    expect(replaceUnstableOutput(output.trim())).not.toContain('supply-chain policies');
+  });
+
   test.skipIf(process.platform === 'win32')('replace unstable cwd', () => {
     const cwd = tmpdir();
     const output = path.join(cwd, 'foo.txt');
diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts
index 5a6fa9b58e..d8847ff5fa 100644
--- a/packages/tools/src/index.ts
+++ b/packages/tools/src/index.ts
@@ -29,10 +29,14 @@ switch (subcommand) {
     const { brandVite } = await import('./brand-vite.ts');
     brandVite();
     break;
+  case 'repack-vite-tgz':
+    const { repackViteTgz } = await import('./repack-vite-tgz.ts');
+    await repackViteTgz();
+    break;
   default:
     console.error(`Unknown subcommand: ${subcommand}`);
     console.error(
-      'Available subcommands: snap-test, replace-file-content, sync-remote, json-sort, merge-peer-deps, install-global-cli, brand-vite',
+      'Available subcommands: snap-test, replace-file-content, sync-remote, json-sort, merge-peer-deps, install-global-cli, brand-vite, repack-vite-tgz',
     );
     process.exit(1);
 }
diff --git a/packages/tools/src/repack-vite-tgz.ts b/packages/tools/src/repack-vite-tgz.ts
new file mode 100644
index 0000000000..c97cdf07e5
--- /dev/null
+++ b/packages/tools/src/repack-vite-tgz.ts
@@ -0,0 +1,81 @@
+import { readFile, writeFile } from 'node:fs/promises';
+
+import { createTarGzip, parseTarGzip, type TarFileInput } from 'nanotar';
+
+interface PackageJson {
+  name?: string;
+  version?: string;
+  dependencies?: Record<string, string>;
+  devDependencies?: Record<string, string>;
+  peerDependencies?: Record<string, string>;
+  optionalDependencies?: Record<string, string>;
+}
+
+function stripVitePlusCoreSelfRefs(pkg: PackageJson, newName: string): void {
+  for (const field of [
+    'dependencies',
+    'devDependencies',
+    'peerDependencies',
+    'optionalDependencies',
+  ] as const) {
+    const group = pkg[field];
+    if (!group) {
+      continue;
+    }
+    for (const [key, value] of Object.entries(group)) {
+      if (
+        (key === newName || key === '@voidzero-dev/vite-plus-core') &&
+        typeof value === 'string' &&
+        value.includes('@voidzero-dev/vite-plus-core')
+      ) {
+        delete group[key];
+      }
+    }
+  }
+}
+
+export async function repackViteTgz() {
+  const [inputPath, outputPath, newName, newVersion] = process.argv.slice(3);
+
+  if (!inputPath || !outputPath || !newName) {
+    console.error('Usage: tool repack-vite-tgz <input.tgz> <output.tgz> <new-name> [new-version]');
+    process.exit(1);
+  }
+
+  const inputBytes = await readFile(inputPath);
+  const entries = await parseTarGzip(inputBytes);
+
+  let patched = 0;
+  const repacked: TarFileInput[] = entries.map((entry) => {
+    let data = entry.data;
+    if (entry.name === 'package/package.json' && data) {
+      const pkg = JSON.parse(new TextDecoder().decode(data)) as PackageJson;
+      pkg.name = newName;
+      if (newVersion) {
+        pkg.version = newVersion;
+      }
+      // Strip any self-ref (`vite: npm:@voidzero-dev/vite-plus-core@...`)
+      // injected by the workspace-level vite -> vite-plus-core override at
+      // pnpm pack time. Once we rename the tgz to "vite", this becomes a
+      // circular self-dependency that confuses pnpm's resolver and triggers a
+      // registry lookup for the alias target version.
+      stripVitePlusCoreSelfRefs(pkg, newName);
+      data = new TextEncoder().encode(JSON.stringify(pkg, null, 2) + '\n');
+      patched += 1;
+    }
+    return { name: entry.name, data, attrs: entry.attrs };
+  });
+
+  if (patched !== 1) {
+    console.error(`Expected exactly one package/package.json entry, found ${patched}`);
+    process.exit(1);
+  }
+
+  const outBytes = await createTarGzip(repacked);
+  await writeFile(outputPath, outBytes);
+  console.log(
+    `Repacked ${inputPath} -> ${outputPath} (name=${newName}${
+      newVersion ? `, version=${newVersion}` : ''
+    }, ${outBytes.byteLength} bytes)`,
+  );
+}
diff --git a/packages/tools/src/snap-test.ts b/packages/tools/src/snap-test.ts
index d234f2bdd7..b8c0bdde1b 100755
--- a/packages/tools/src/snap-test.ts
+++ b/packages/tools/src/snap-test.ts
@@ -265,9 +265,7 @@ function replaceInstalledCheckoutPackages(rootDir: string, repoRoot: string): vo
   const replacements = new Map([
     ['node_modules/vite-plus', path.join(repoRoot, 'packages', 'cli')],
     ['node_modules/vite', path.join(repoRoot, 'packages', 'core')],
-    ['node_modules/vitest', path.join(repoRoot, 'packages', 'test')],
     ['node_modules/@voidzero-dev/vite-plus-core', path.join(repoRoot, 'packages', 'core')],
-    ['node_modules/@voidzero-dev/vite-plus-test', path.join(repoRoot, 'packages', 'test')],
   ]);
 
   while (stack.length > 0) {
diff --git a/packages/tools/src/sync-remote-deps.ts b/packages/tools/src/sync-remote-deps.ts
index 029f16e1f2..bf453e6333 100755
--- a/packages/tools/src/sync-remote-deps.ts
+++ b/packages/tools/src/sync-remote-deps.ts
@@ -626,10 +626,12 @@ export function mergePnpmWorkspaces(
     ...(rolldownVite.minimumReleaseAgeExclude || []),
   ]);
 
-  // Copy patchedDependencies from vite (with path prefix)
-  if (rolldownVite.patchedDependencies) {
-    result.patchedDependencies = {};
-    for (const [dep, patchPath] of Object.entries(rolldownVite.patchedDependencies)) {
+  // Merge patchedDependencies: start with vite-plus root patches so they are
+  // preserved across syncs (e.g. patches/@vitest__mocker@*), then layer vite's
+  // patches with the `vite/` directory prefix on top.
+  if (main.patchedDependencies || rolldownVite.patchedDependencies) {
+    result.patchedDependencies = { ...main.patchedDependencies };
+    for (const [dep, patchPath] of Object.entries(rolldownVite.patchedDependencies ?? {})) {
       // Prepend vite directory to patch paths
       result.patchedDependencies[dep] = patchPath.startsWith('./')
         ? `./${VITE_DIR}/${patchPath.slice(2)}`
diff --git a/packages/tools/src/utils.ts b/packages/tools/src/utils.ts
index da45d491dc..597b47aaf7 100644
--- a/packages/tools/src/utils.ts
+++ b/packages/tools/src/utils.ts
@@ -49,6 +49,30 @@ export function replaceUnstableOutput(output: string, cwd?: string) {
       // e.g.: ` v1.0.0` -> ` <semver>`
       // e.g.: `/1.0.0` -> `/<semver>`
       .replaceAll(/([@/\s]v?)\d+\.\d+\.\d+(?:-.*)?/g, '$1<semver>')
+      // vitest-family pins written as JSON values (catalog blocks, devDependencies,
+      // overrides/resolutions) all track the bundled VITEST_VERSION and so change on
+      // every daily upgrade-deps bump. The quote-preceded value is not caught by the
+      // generic semver rule above (which only matches `@`/`/`/whitespace prefixes),
+      // so mask it here to keep these snaps stable — mirroring the already-masked YAML
+      // `vitest: <semver>` catalog form. e.g.: `"vitest": "4.1.9"` -> `"vitest": "<semver>"`
+      //
+      // Only major >= 4 is masked. The migrator refuses to manage vitest < 4
+      // (`checkVitestVersion` requires >=4.0.0), so the only place a pre-4 pin can
+      // surface in a snapshot is an unsupported-version fixture echoing the user's
+      // OWN untouched `package.json` (e.g. migration-not-supported-vitest3 keeps
+      // `"vitest": "3.2.4"`). Leaving those visible keeps that snap able to prove the
+      // refused migration did not rewrite the dependency. Major matcher: `[4-9]` or
+      // any 2+ digit major (`[1-9]\d+`), so it also covers future vitest 10+.
+      //
+      // `@vitest/coverage-*` is EXCLUDED: coverage providers are user-installed peers
+      // that vite-plus never adds, pins, or overrides (see VITE_PLUS_OVERRIDE_PACKAGES
+      // in constants.ts), and `define-config.ts` fail-fasts when an installed provider
+      // skews from the bundled vitest. Their exact version is meaningful, so keep it
+      // visible — masking it would hide a stale/accidentally-rewritten user pin.
+      .replaceAll(
+        /("(?:vitest|@vitest\/(?!coverage-)[\w-]+)": ")(?:[4-9]|[1-9]\d+)\.\d+\.\d+(?:-[\w.]+)?(")/g,
+        '$1<semver>$2',
+      )
       // devEngines.packageManager auto-pin writes the exact resolved version
       // e.g.: `"name": "pnpm",\n  "version": "11.5.1"` -> `"version": "<semver>"`
       // (the optional suffix covers prerelease and build metadata: -rc-1, +sha.abc)
@@ -86,6 +110,10 @@ export function replaceUnstableOutput(output: string, cwd?: string) {
       )
       // ignore pnpm progress
       .replaceAll(/Progress: resolved \d+, reused \d+, downloaded \d+, added \d+\n/g, '')
+      // ignore the pnpm supply-chain verification status line (pnpm >= 10.34.3);
+      // whether it appears and its "verified ... ago" timing depend on pnpm
+      // version and lockfile verification state, not on vp behavior
+      .replaceAll(/✓ Lockfile passes supply-chain policies \(verified [^)]*\)\n/g, '')
       // ignore pnpm warn lines; pnpm pads `WARN` with thin spaces (U+2009) in
       // TTY output and brackets it as `[WARN]` in non-interactive output
       .replaceAll(/[\u2009 ]?\[?WARN\]?\s+Skip\s+adding .+?\n/g, '')
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9f1ca147d1..7a7a3e8926 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -87,6 +87,39 @@ catalogs:
     '@typescript/native-preview':
       specifier: 7.0.0-dev.20260605.1
       version: 7.0.0-dev.20260605.1
+    '@vitest/browser':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/browser-playwright':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/browser-preview':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/browser-webdriverio':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/expect':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/mocker':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/pretty-format':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/runner':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/snapshot':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/spy':
+      specifier: 4.1.9
+      version: 4.1.9
+    '@vitest/utils':
+      specifier: 4.1.9
+      version: 4.1.9
     '@yarnpkg/fslib':
       specifier: ^3.1.3
       version: 3.1.4
@@ -240,6 +273,9 @@ catalogs:
     validate-npm-package-name:
       specifier: ^7.0.2
       version: 7.0.2
+    vitest:
+      specifier: 4.1.9
+      version: 4.1.9
     vue:
       specifier: ^3.5.35
       version: 3.5.35
@@ -256,9 +292,7 @@ catalogs:
 overrides:
   rolldown: workspace:rolldown@*
   vite: workspace:@voidzero-dev/vite-plus-core@*
-  vite-plus: workspace:*
-  vitest: workspace:@voidzero-dev/vite-plus-test@*
-  vitest-dev: npm:vitest@^4.1.8
+  vite-plus: workspace:vite-plus@*
 
 packageExtensionsChecksum: sha256-Tldxs3DhJEw/FFBonUidqhCBqApA0zxQnop3Y+BTO3U=
 
@@ -317,11 +351,11 @@ importers:
         specifier: workspace:@voidzero-dev/vite-plus-core@*
         version: link:packages/core
       vite-plus:
-        specifier: workspace:*
+        specifier: workspace:vite-plus@*
         version: link:packages/cli
       vitest:
-        specifier: workspace:@voidzero-dev/vite-plus-test@*
-        version: link:packages/test
+        specifier: 'catalog:'
+        version: 4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.10.3)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
 
   packages/cli:
     dependencies:
@@ -331,12 +365,36 @@ importers:
       '@oxlint/plugins':
         specifier: 'catalog:'
         version: 1.68.0
+      '@vitest/browser':
+        specifier: 'catalog:'
+        version: 4.1.9(vite@packages+core)(vitest@4.1.9)
+      '@vitest/browser-preview':
+        specifier: 'catalog:'
+        version: 4.1.9(vite@packages+core)(vitest@4.1.9)
+      '@vitest/expect':
+        specifier: 'catalog:'
+        version: 4.1.9
+      '@vitest/mocker':
+        specifier: 'catalog:'
+        version: 4.1.9(vite@packages+core)
+      '@vitest/pretty-format':
+        specifier: 'catalog:'
+        version: 4.1.9
+      '@vitest/runner':
+        specifier: 'catalog:'
+        version: 4.1.9
+      '@vitest/snapshot':
+        specifier: 'catalog:'
+        version: 4.1.9
+      '@vitest/spy':
+        specifier: 'catalog:'
+        version: 4.1.9
+      '@vitest/utils':
+        specifier: 'catalog:'
+        version: 4.1.9
       '@voidzero-dev/vite-plus-core':
         specifier: workspace:*
         version: link:../core
-      '@voidzero-dev/vite-plus-test':
-        specifier: workspace:*
-        version: link:../test
       oxfmt:
         specifier: 'catalog:'
         version: 0.54.0(vite-plus@packages+cli)
@@ -346,6 +404,9 @@ importers:
       oxlint-tsgolint:
         specifier: 'catalog:'
         version: 0.23.0
+      vitest:
+        specifier: 'catalog:'
+        version: 4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
     devDependencies:
       '@napi-rs/cli':
         specifier: 'catalog:'
@@ -365,6 +426,12 @@ importers:
       '@types/validate-npm-package-name':
         specifier: 'catalog:'
         version: 4.0.2
+      '@vitest/browser-playwright':
+        specifier: 'catalog:'
+        version: 4.1.9(playwright@1.57.0)(vite@packages+core)(vitest@4.1.9)
+      '@vitest/browser-webdriverio':
+        specifier: 'catalog:'
+        version: 4.1.9(vite@packages+core)(vitest@4.1.9)(webdriverio@9.20.1)
       '@voidzero-dev/vite-plus-prompts':
         specifier: workspace:*
         version: link:../prompts
@@ -600,160 +667,6 @@ importers:
         specifier: 'catalog:'
         version: 0.22.2(@arethetypeswrong/core@0.18.2)(@tsdown/css@0.22.2)(@tsdown/exe@0.22.2)(@typescript/native-preview@7.0.0-dev.20260605.1)(@vitejs/devtools@0.3.3(typescript@6.0.2)(vite@packages+core))(publint@0.3.21)(tsx@4.22.4)(typescript@6.0.2)(unplugin-unused@0.5.6)(unrun@0.3.0)
 
-  packages/test:
-    dependencies:
-      '@edge-runtime/vm':
-        specifier: '*'
-        version: 5.0.0
-      '@opentelemetry/api':
-        specifier: ^1.9.0
-        version: 1.9.0
-      '@standard-schema/spec':
-        specifier: ^1.1.0
-        version: 1.1.0
-      '@types/chai':
-        specifier: ^5.2.2
-        version: 5.2.3
-      '@types/node':
-        specifier: ^20.0.0 || ^22.0.0 || >=24.0.0
-        version: 24.12.4
-      '@vitest/coverage-istanbul':
-        specifier: 4.1.8
-        version: 4.1.8(vitest@4.1.8)
-      '@vitest/coverage-v8':
-        specifier: 4.1.8
-        version: 4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8)
-      '@vitest/ui':
-        specifier: 4.1.8
-        version: 4.1.8(vitest@4.1.8)
-      '@voidzero-dev/vite-plus-core':
-        specifier: workspace:*
-        version: link:../core
-      es-module-lexer:
-        specifier: ^1.7.0
-        version: 1.7.0
-      happy-dom:
-        specifier: '*'
-        version: 20.0.10
-      jsdom:
-        specifier: '*'
-        version: 27.2.0
-      obug:
-        specifier: ^2.1.1
-        version: 2.1.3
-      pixelmatch:
-        specifier: ^7.1.0
-        version: 7.1.0
-      pngjs:
-        specifier: ^7.0.0
-        version: 7.0.0
-      sirv:
-        specifier: ^3.0.2
-        version: 3.0.2(patch_hash=c07c56eb72faea34341d465cde2314e89db472106ed378181e3447893af6bf95)
-      std-env:
-        specifier: ^4.0.0
-        version: 4.0.0
-      tinybench:
-        specifier: ^2.9.0
-        version: 2.9.0
-      tinyexec:
-        specifier: ^1.0.2
-        version: 1.2.4
-      tinyglobby:
-        specifier: ^0.2.15
-        version: 0.2.17
-      vite:
-        specifier: workspace:@voidzero-dev/vite-plus-core@*
-        version: link:../core
-      ws:
-        specifier: ^8.18.3
-        version: 8.21.0
-    devDependencies:
-      '@blazediff/core':
-        specifier: 1.9.2
-        version: 1.9.2
-      '@oxc-node/cli':
-        specifier: 'catalog:'
-        version: 0.1.0
-      '@oxc-node/core':
-        specifier: 'catalog:'
-        version: 0.1.0
-      '@vitest/browser':
-        specifier: 4.1.8
-        version: 4.1.8(vite@packages+core)(vitest@4.1.8)
-      '@vitest/browser-playwright':
-        specifier: 4.1.8
-        version: 4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8)
-      '@vitest/browser-preview':
-        specifier: 4.1.8
-        version: 4.1.8(vite@packages+core)(vitest@4.1.8)
-      '@vitest/browser-webdriverio':
-        specifier: 4.1.8
-        version: 4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1)
-      '@vitest/expect':
-        specifier: 4.1.8
-        version: 4.1.8
-      '@vitest/mocker':
-        specifier: 4.1.8
-        version: 4.1.8(vite@packages+core)
-      '@vitest/pretty-format':
-        specifier: 4.1.8
-        version: 4.1.8
-      '@vitest/runner':
-        specifier: 4.1.8
-        version: 4.1.8
-      '@vitest/snapshot':
-        specifier: 4.1.8
-        version: 4.1.8
-      '@vitest/spy':
-        specifier: 4.1.8
-        version: 4.1.8
-      '@vitest/utils':
-        specifier: 4.1.8
-        version: 4.1.8
-      chai:
-        specifier: ^6.2.1
-        version: 6.2.2
-      convert-source-map:
-        specifier: ^2.0.0
-        version: 2.0.0
-      estree-walker:
-        specifier: ^3.0.3
-        version: 3.0.3
-      expect-type:
-        specifier: ^1.2.2
-        version: 1.3.0
-      magic-string:
-        specifier: ^0.30.21
-        version: 0.30.21
-      oxc-parser:
-        specifier: 'catalog:'
-        version: 0.135.0
-      oxfmt:
-        specifier: 'catalog:'
-        version: 0.54.0(vite-plus@packages+cli)
-      pathe:
-        specifier: ^2.0.3
-        version: 2.0.3
-      picomatch:
-        specifier: ^4.0.3
-        version: 4.0.4
-      rolldown:
-        specifier: workspace:rolldown@*
-        version: link:../../rolldown/packages/rolldown
-      rolldown-plugin-dts:
-        specifier: 'catalog:'
-        version: 0.25.2(@typescript/native-preview@7.0.0-dev.20260605.1)(oxc-resolver@11.19.1(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0))(rolldown@rolldown+packages+rolldown)(typescript@6.0.2)
-      tinyrainbow:
-        specifier: ^3.1.0
-        version: 3.1.0
-      vitest-dev:
-        specifier: npm:vitest@^4.1.8
-        version: vitest@4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
-      why-is-node-running:
-        specifier: ^2.3.0
-        version: 2.3.0
-
   packages/tools:
     dependencies:
       '@yarnpkg/fslib':
@@ -762,6 +675,9 @@ importers:
       '@yarnpkg/shell':
         specifier: 'catalog:'
         version: 4.1.3(typanion@3.14.0)
+      nanotar:
+        specifier: 'catalog:'
+        version: 0.3.0
     devDependencies:
       '@oxc-node/cli':
         specifier: 'catalog:'
@@ -772,15 +688,15 @@ importers:
       '@types/semver':
         specifier: 'catalog:'
         version: 7.7.1
-      '@voidzero-dev/vite-plus-test':
-        specifier: workspace:*
-        version: link:../test
       minimatch:
         specifier: 'catalog:'
         version: 10.2.4
       semver:
         specifier: 'catalog:'
         version: 7.8.4
+      vitest:
+        specifier: 'catalog:'
+        version: 4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
       yaml:
         specifier: 'catalog:'
         version: 2.9.0
@@ -818,7 +734,7 @@ importers:
         specifier: 'catalog:'
         version: 6.0.2
       vite-plus:
-        specifier: workspace:*
+        specifier: workspace:vite-plus@*
         version: link:../packages/cli
 
   rolldown/packages/bench:
@@ -1134,8 +1050,8 @@ importers:
         specifier: workspace:@voidzero-dev/vite-plus-core@*
         version: link:../packages/core
       vitest:
-        specifier: workspace:@voidzero-dev/vite-plus-test@*
-        version: link:../packages/test
+        specifier: ^4.1.7
+        version: 4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
 
   vite/packages/create-vite:
     devDependencies:
@@ -2054,16 +1970,9 @@ packages:
     resolution: {integrity: sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==}
     engines: {node: ^22.18.0 || >=24.11.0}
 
-  '@bcoe/v8-coverage@1.0.2':
-    resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
-    engines: {node: '>=18'}
-
   '@blazediff/core@1.9.1':
     resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==}
 
-  '@blazediff/core@1.9.2':
-    resolution: {integrity: sha512-KJoZqeJ7I8pXTzkOl6uobJ7EtvUGWmmEOqxgWTVi6jvG+h18BmxGrlA07KD+1J+Zi7AVTXDPIeVKMSa+pmlXDQ==}
-
   '@braidai/lang@1.1.2':
     resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==}
 
@@ -2525,10 +2434,6 @@ packages:
     resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
     engines: {node: '>=12'}
 
-  '@istanbuljs/schema@0.1.3':
-    resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
-    engines: {node: '>=8'}
-
   '@jridgewell/gen-mapping@0.3.13':
     resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
 
@@ -4926,47 +4831,33 @@ packages:
   '@vitejs/release-scripts@1.7.1':
     resolution: {integrity: sha512-8YpovUu73Yb+ZnXIsR9VoF/yhbd2wTMhvJKDOjlB0bbOdm+ATl1l7Z7rskdX4CGzN7UfdvLcE6vGaHDCsirdpw==}
 
-  '@vitest/browser-playwright@4.1.8':
-    resolution: {integrity: sha512-SR7FqgegaexEg73xvf3ArtygXegagMdXnL0EZMpxrWvvhQxvicD/E8p0ib0J91riPRtQUViyh67Xjw3NqvyhVg==}
+  '@vitest/browser-playwright@4.1.9':
+    resolution: {integrity: sha512-Bq1rOGf9waevzG3EOkO/dene6bvKTUsZMVg8S1i+WH3JcMjuXEjiahP9rAqZRELUqjBySOJsvvSWqK/B3wjKQw==}
     peerDependencies:
       playwright: '*'
-      vitest: workspace:@voidzero-dev/vite-plus-test@*
+      vitest: 4.1.9
 
-  '@vitest/browser-preview@4.1.8':
-    resolution: {integrity: sha512-TZ529W8dFLv4Mywn+PCFJt/iwY/l9iQtKXat2nHS1ZUFuc6GtPGPZor2JMceEppoyNUHHyO2uJeiuWll2WQCRA==}
+  '@vitest/browser-preview@4.1.9':
+    resolution: {integrity: sha512-a4/OrkMDb/WUnE4OOB/4FJbK3rYVO7YykqtUgcTKG4p2a0R3XcjPVu7SLRHFBs2+NIYhv5yxp1Lz3dbdGBjIow==}
     peerDependencies:
-      vitest: workspace:@voidzero-dev/vite-plus-test@*
+      vitest: 4.1.9
 
-  '@vitest/browser-webdriverio@4.1.8':
-    resolution: {integrity: sha512-F5DyLfiwnnkqFifo3pZ0xXkI0Mpkro/sGmJY6redwzMARACKqYjNZCOaOVn28SljFKnGrG/LqNf096WExBqgfA==}
+  '@vitest/browser-webdriverio@4.1.9':
+    resolution: {integrity: sha512-bZFBg9PfjsiMJkD1cX3n7nUX+7kynZloLhnahBcbVPUP9NvxbyEhb52nSuiua2Fj+iSEhBqM/fWVDGMDZqloNg==}
     peerDependencies:
-      vitest: workspace:@voidzero-dev/vite-plus-test@*
+      vitest: 4.1.9
       webdriverio: '*'
 
-  '@vitest/browser@4.1.8':
-    resolution: {integrity: sha512-u21VzX07HzlJYpFgkxmjEXar/tG2UqWGgyGG/46SrrPc7rSdCTPw5vuowopO9CIqF8UCUQzDFdbVnNpw6N0BfQ==}
+  '@vitest/browser@4.1.9':
+    resolution: {integrity: sha512-j1BKtWmPcqpMhmx/L9EPLgAJpCb0zKfwoWLmqBbxaogCXHjOwHFSEoHCBfnGtx93xKQwilZ26m+UOsHqHMkRNg==}
     peerDependencies:
-      vitest: workspace:@voidzero-dev/vite-plus-test@*
+      vitest: 4.1.9
 
-  '@vitest/coverage-istanbul@4.1.8':
-    resolution: {integrity: sha512-/h514nMZMKI6foh21mVgO1zlCH6pdDamwKMbla1uLU2GMxTlfp0PQMxovWozmzQdCIQYZ2XXEzg2zNZom2zAOg==}
-    peerDependencies:
-      vitest: workspace:@voidzero-dev/vite-plus-test@*
-
-  '@vitest/coverage-v8@4.1.8':
-    resolution: {integrity: sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==}
-    peerDependencies:
-      '@vitest/browser': 4.1.8
-      vitest: workspace:@voidzero-dev/vite-plus-test@*
-    peerDependenciesMeta:
-      '@vitest/browser':
-        optional: true
+  '@vitest/expect@4.1.9':
+    resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==}
 
-  '@vitest/expect@4.1.8':
-    resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==}
-
-  '@vitest/mocker@4.1.8':
-    resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==}
+  '@vitest/mocker@4.1.9':
+    resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==}
     peerDependencies:
       msw: ^2.4.9
       vite: workspace:@voidzero-dev/vite-plus-core@*
@@ -4979,28 +4870,23 @@ packages:
   '@vitest/pretty-format@4.1.7':
     resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==}
 
-  '@vitest/pretty-format@4.1.8':
-    resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==}
-
-  '@vitest/runner@4.1.8':
-    resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==}
+  '@vitest/pretty-format@4.1.9':
+    resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==}
 
-  '@vitest/snapshot@4.1.8':
-    resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==}
+  '@vitest/runner@4.1.9':
+    resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==}
 
-  '@vitest/spy@4.1.8':
-    resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==}
+  '@vitest/snapshot@4.1.9':
+    resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==}
 
-  '@vitest/ui@4.1.8':
-    resolution: {integrity: sha512-RUS2ZU2TsduVrI+9c12uTNaKrNUTsm6yFt3fueEUB9iKvyC2UP83F+sqIz00HQIah4UOL1TMoDAki8K0NjGvsA==}
-    peerDependencies:
-      vitest: workspace:@voidzero-dev/vite-plus-test@*
+  '@vitest/spy@4.1.9':
+    resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==}
 
   '@vitest/utils@4.1.7':
     resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==}
 
-  '@vitest/utils@4.1.8':
-    resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==}
+  '@vitest/utils@4.1.9':
+    resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==}
 
   '@vue-macros/common@3.1.2':
     resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==}
@@ -5221,9 +5107,6 @@ packages:
     resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
     engines: {node: '>=4'}
 
-  ast-v8-to-istanbul@1.0.0:
-    resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==}
-
   ast-walker-scope@0.8.3:
     resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
     engines: {node: '>=20.19.0'}
@@ -6394,9 +6277,6 @@ packages:
     resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
     engines: {node: '>=18'}
 
-  html-escaper@2.0.2:
-    resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
-
   htmlfy@0.8.1:
     resolution: {integrity: sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==}
 
@@ -6607,18 +6487,6 @@ packages:
     resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
     engines: {node: '>=16'}
 
-  istanbul-lib-coverage@3.2.2:
-    resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
-    engines: {node: '>=8'}
-
-  istanbul-lib-report@3.0.1:
-    resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
-    engines: {node: '>=10'}
-
-  istanbul-reports@3.2.0:
-    resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
-    engines: {node: '>=8'}
-
   jackspeak@3.4.3:
     resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
 
@@ -6626,9 +6494,6 @@ packages:
     resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
     hasBin: true
 
-  js-tokens@10.0.0:
-    resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
-
   js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
@@ -6897,17 +6762,10 @@ packages:
   magic-string@0.30.21:
     resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
 
-  magicast@0.5.2:
-    resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==}
-
   make-dir@2.1.0:
     resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
     engines: {node: '>=6'}
 
-  make-dir@4.0.0:
-    resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
-    engines: {node: '>=10'}
-
   matcher-collection@2.0.1:
     resolution: {integrity: sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==}
     engines: {node: 6.* || 8.* || >= 10.*}
@@ -7159,7 +7017,7 @@ packages:
     hasBin: true
     peerDependencies:
       svelte: ^5.0.0
-      vite-plus: workspace:*
+      vite-plus: workspace:vite-plus@*
     peerDependenciesMeta:
       svelte:
         optional: true
@@ -7172,7 +7030,7 @@ packages:
     hasBin: true
     peerDependencies:
       svelte: ^5.0.0
-      vite-plus: workspace:*
+      vite-plus: workspace:vite-plus@*
     peerDependenciesMeta:
       svelte:
         optional: true
@@ -7189,7 +7047,7 @@ packages:
     hasBin: true
     peerDependencies:
       oxlint-tsgolint: '>=0.22.1'
-      vite-plus: workspace:*
+      vite-plus: workspace:vite-plus@*
     peerDependenciesMeta:
       oxlint-tsgolint:
         optional: true
@@ -7326,10 +7184,6 @@ packages:
     resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
     engines: {node: '>= 6'}
 
-  pixelmatch@7.1.0:
-    resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==}
-    hasBin: true
-
   pkg-types@1.3.1:
     resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
 
@@ -8533,20 +8387,20 @@ packages:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
 
-  vitest@4.1.8:
-    resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==}
+  vitest@4.1.9:
+    resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==}
     engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
     hasBin: true
     peerDependencies:
       '@edge-runtime/vm': '*'
       '@opentelemetry/api': ^1.9.0
       '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
-      '@vitest/browser-playwright': 4.1.8
-      '@vitest/browser-preview': 4.1.8
-      '@vitest/browser-webdriverio': 4.1.8
-      '@vitest/coverage-istanbul': 4.1.8
-      '@vitest/coverage-v8': 4.1.8
-      '@vitest/ui': 4.1.8
+      '@vitest/browser-playwright': 4.1.9
+      '@vitest/browser-preview': 4.1.9
+      '@vitest/browser-webdriverio': 4.1.9
+      '@vitest/coverage-istanbul': 4.1.9
+      '@vitest/coverage-v8': 4.1.9
+      '@vitest/ui': 4.1.9
       happy-dom: '*'
       jsdom: '*'
       vite: workspace:@voidzero-dev/vite-plus-core@*
@@ -8790,7 +8644,8 @@ packages:
 
 snapshots:
 
-  '@acemir/cssom@0.9.24': {}
+  '@acemir/cssom@0.9.24':
+    optional: true
 
   '@adobe/css-tools@4.3.3': {}
 
@@ -8814,6 +8669,7 @@ snapshots:
       '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
       '@csstools/css-tokenizer': 3.0.4
       lru-cache: 11.2.7
+    optional: true
 
   '@asamuzakjp/dom-selector@6.7.4':
     dependencies:
@@ -8822,8 +8678,10 @@ snapshots:
       css-tree: 3.1.0
       is-potential-custom-element-name: 1.0.1
       lru-cache: 11.2.7
+    optional: true
 
-  '@asamuzakjp/nwsapi@2.3.9': {}
+  '@asamuzakjp/nwsapi@2.3.9':
+    optional: true
 
   '@ast-grep/napi-darwin-arm64@0.43.0':
     optional: true
@@ -9581,12 +9439,8 @@ snapshots:
       '@babel/helper-string-parser': 8.0.0-rc.6
       '@babel/helper-validator-identifier': 8.0.0-rc.6
 
-  '@bcoe/v8-coverage@1.0.2': {}
-
   '@blazediff/core@1.9.1': {}
 
-  '@blazediff/core@1.9.2': {}
-
   '@braidai/lang@1.1.2': {}
 
   '@bufbuild/protobuf@2.10.1': {}
@@ -9623,12 +9477,14 @@ snapshots:
       conventional-commits-filter: 5.0.0
       conventional-commits-parser: 6.4.0
 
-  '@csstools/color-helpers@5.1.0': {}
+  '@csstools/color-helpers@5.1.0':
+    optional: true
 
   '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
     dependencies:
       '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
       '@csstools/css-tokenizer': 3.0.4
+    optional: true
 
   '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
     dependencies:
@@ -9636,14 +9492,18 @@ snapshots:
       '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
       '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
       '@csstools/css-tokenizer': 3.0.4
+    optional: true
 
   '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
     dependencies:
       '@csstools/css-tokenizer': 3.0.4
+    optional: true
 
-  '@csstools/css-syntax-patches-for-csstree@1.0.17': {}
+  '@csstools/css-syntax-patches-for-csstree@1.0.17':
+    optional: true
 
-  '@csstools/css-tokenizer@3.0.4': {}
+  '@csstools/css-tokenizer@3.0.4':
+    optional: true
 
   '@devframes/hub@0.5.4(devframe@0.5.4(typescript@6.0.2))':
     dependencies:
@@ -9654,11 +9514,13 @@ snapshots:
       perfect-debounce: 2.1.0
       tinyexec: 1.2.4
 
-  '@edge-runtime/primitives@6.0.0': {}
+  '@edge-runtime/primitives@6.0.0':
+    optional: true
 
   '@edge-runtime/vm@5.0.0':
     dependencies:
       '@edge-runtime/primitives': 6.0.0
+    optional: true
 
   '@emnapi/core@1.10.0':
     dependencies:
@@ -10094,8 +9956,6 @@ snapshots:
       wrap-ansi: 8.1.0
       wrap-ansi-cjs: wrap-ansi@7.0.0
 
-  '@istanbuljs/schema@0.1.3': {}
-
   '@jridgewell/gen-mapping@0.3.13':
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.5
@@ -10614,7 +10474,8 @@ snapshots:
       '@octokit/request-error': 7.1.0
       '@octokit/webhooks-methods': 6.0.0
 
-  '@opentelemetry/api@1.9.0': {}
+  '@opentelemetry/api@1.9.0':
+    optional: true
 
   '@oxc-node/cli@0.1.0':
     dependencies:
@@ -11640,7 +11501,8 @@ snapshots:
 
   '@types/validate-npm-package-name@4.0.2': {}
 
-  '@types/whatwg-mimetype@3.0.2': {}
+  '@types/whatwg-mimetype@3.0.2':
+    optional: true
 
   '@types/which@2.0.2': {}
 
@@ -11966,35 +11828,35 @@ snapshots:
     transitivePeerDependencies:
       - conventional-commits-filter
 
-  '@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8)':
+  '@vitest/browser-playwright@4.1.9(playwright@1.57.0)(vite@packages+core)(vitest@4.1.9)':
     dependencies:
-      '@vitest/browser': 4.1.8(vite@packages+core)(vitest@4.1.8)
-      '@vitest/mocker': 4.1.8(vite@packages+core)
+      '@vitest/browser': 4.1.9(vite@packages+core)(vitest@4.1.9)
+      '@vitest/mocker': 4.1.9(vite@packages+core)
       playwright: 1.57.0
       tinyrainbow: 3.1.0
-      vitest: 4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
+      vitest: 4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.10.3)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
     transitivePeerDependencies:
       - bufferutil
       - msw
       - utf-8-validate
       - vite
 
-  '@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8)':
+  '@vitest/browser-preview@4.1.9(vite@packages+core)(vitest@4.1.9)':
     dependencies:
       '@testing-library/dom': 10.4.1
       '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
-      '@vitest/browser': 4.1.8(vite@packages+core)(vitest@4.1.8)
-      vitest: 4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
+      '@vitest/browser': 4.1.9(vite@packages+core)(vitest@4.1.9)
+      vitest: 4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.10.3)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
     transitivePeerDependencies:
       - bufferutil
       - msw
       - utf-8-validate
       - vite
 
-  '@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1)':
+  '@vitest/browser-webdriverio@4.1.9(vite@packages+core)(vitest@4.1.9)(webdriverio@9.20.1)':
     dependencies:
-      '@vitest/browser': 4.1.8(vite@packages+core)(vitest@4.1.8)
-      vitest: 4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
+      '@vitest/browser': 4.1.9(vite@packages+core)(vitest@4.1.9)
+      vitest: 4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.10.3)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
       webdriverio: 9.20.1
     transitivePeerDependencies:
       - bufferutil
@@ -12002,16 +11864,16 @@ snapshots:
       - utf-8-validate
       - vite
 
-  '@vitest/browser@4.1.8(vite@packages+core)(vitest@4.1.8)':
+  '@vitest/browser@4.1.9(vite@packages+core)(vitest@4.1.9)':
     dependencies:
       '@blazediff/core': 1.9.1
-      '@vitest/mocker': 4.1.8(vite@packages+core)
-      '@vitest/utils': 4.1.8
+      '@vitest/mocker': 4.1.9(vite@packages+core)
+      '@vitest/utils': 4.1.9
       magic-string: 0.30.21
       pngjs: 7.0.0
       sirv: 3.0.2(patch_hash=c07c56eb72faea34341d465cde2314e89db472106ed378181e3447893af6bf95)
       tinyrainbow: 3.1.0
-      vitest: 4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
+      vitest: 4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.10.3)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
       ws: 8.21.0
     transitivePeerDependencies:
       - bufferutil
@@ -12019,50 +11881,18 @@ snapshots:
       - utf-8-validate
       - vite
 
-  '@vitest/coverage-istanbul@4.1.8(vitest@4.1.8)':
-    dependencies:
-      '@babel/core': 7.29.7
-      '@istanbuljs/schema': 0.1.3
-      '@jridgewell/gen-mapping': 0.3.13
-      '@jridgewell/trace-mapping': 0.3.31
-      istanbul-lib-coverage: 3.2.2
-      istanbul-lib-report: 3.0.1
-      istanbul-reports: 3.2.0
-      magicast: 0.5.2
-      obug: 2.1.3
-      tinyrainbow: 3.1.0
-      vitest: 4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
-    transitivePeerDependencies:
-      - supports-color
-
-  '@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8)':
-    dependencies:
-      '@bcoe/v8-coverage': 1.0.2
-      '@vitest/utils': 4.1.8
-      ast-v8-to-istanbul: 1.0.0
-      istanbul-lib-coverage: 3.2.2
-      istanbul-lib-report: 3.0.1
-      istanbul-reports: 3.2.0
-      magicast: 0.5.2
-      obug: 2.1.3
-      std-env: 4.0.0
-      tinyrainbow: 3.1.0
-      vitest: 4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
-    optionalDependencies:
-      '@vitest/browser': 4.1.8(vite@packages+core)(vitest@4.1.8)
-
-  '@vitest/expect@4.1.8':
+  '@vitest/expect@4.1.9':
     dependencies:
       '@standard-schema/spec': 1.1.0
       '@types/chai': 5.2.3
-      '@vitest/spy': 4.1.8
-      '@vitest/utils': 4.1.8
+      '@vitest/spy': 4.1.9
+      '@vitest/utils': 4.1.9
       chai: 6.2.2
       tinyrainbow: 3.1.0
 
-  '@vitest/mocker@4.1.8(vite@packages+core)':
+  '@vitest/mocker@4.1.9(vite@packages+core)':
     dependencies:
-      '@vitest/spy': 4.1.8
+      '@vitest/spy': 4.1.9
       estree-walker: 3.0.3
       magic-string: 0.30.21
     optionalDependencies:
@@ -12072,34 +11902,23 @@ snapshots:
     dependencies:
       tinyrainbow: 3.1.0
 
-  '@vitest/pretty-format@4.1.8':
+  '@vitest/pretty-format@4.1.9':
     dependencies:
       tinyrainbow: 3.1.0
 
-  '@vitest/runner@4.1.8':
+  '@vitest/runner@4.1.9':
     dependencies:
-      '@vitest/utils': 4.1.8
+      '@vitest/utils': 4.1.9
       pathe: 2.0.3
 
-  '@vitest/snapshot@4.1.8':
+  '@vitest/snapshot@4.1.9':
     dependencies:
-      '@vitest/pretty-format': 4.1.8
-      '@vitest/utils': 4.1.8
+      '@vitest/pretty-format': 4.1.9
+      '@vitest/utils': 4.1.9
       magic-string: 0.30.21
       pathe: 2.0.3
 
-  '@vitest/spy@4.1.8': {}
-
-  '@vitest/ui@4.1.8(vitest@4.1.8)':
-    dependencies:
-      '@vitest/utils': 4.1.8
-      fflate: 0.8.2
-      flatted: 3.4.2
-      pathe: 2.0.3
-      sirv: 3.0.2(patch_hash=c07c56eb72faea34341d465cde2314e89db472106ed378181e3447893af6bf95)
-      tinyglobby: 0.2.17
-      tinyrainbow: 3.1.0
-      vitest: 4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core)
+  '@vitest/spy@4.1.9': {}
 
   '@vitest/utils@4.1.7':
     dependencies:
@@ -12107,9 +11926,9 @@ snapshots:
       convert-source-map: 2.0.0
       tinyrainbow: 3.1.0
 
-  '@vitest/utils@4.1.8':
+  '@vitest/utils@4.1.9':
     dependencies:
-      '@vitest/pretty-format': 4.1.8
+      '@vitest/pretty-format': 4.1.9
       convert-source-map: 2.0.0
       tinyrainbow: 3.1.0
 
@@ -12399,12 +12218,6 @@ snapshots:
     dependencies:
       tslib: 2.8.1
 
-  ast-v8-to-istanbul@1.0.0:
-    dependencies:
-      '@jridgewell/trace-mapping': 0.3.31
-      estree-walker: 3.0.3
-      js-tokens: 10.0.0
-
   ast-walker-scope@0.8.3:
     dependencies:
       '@babel/parser': 7.29.7
@@ -12492,6 +12305,7 @@ snapshots:
   bidi-js@1.0.3:
     dependencies:
       require-from-string: 2.0.2
+    optional: true
 
   binary-extensions@2.3.0: {}
 
@@ -12840,6 +12654,7 @@ snapshots:
     dependencies:
       mdn-data: 2.12.2
       source-map-js: 1.2.1
+    optional: true
 
   css-value@0.0.1: {}
 
@@ -12852,6 +12667,7 @@ snapshots:
       '@asamuzakjp/css-color': 4.1.0
       '@csstools/css-syntax-patches-for-csstree': 1.0.17
       css-tree: 3.1.0
+    optional: true
 
   csstype@3.2.3: {}
 
@@ -12869,6 +12685,7 @@ snapshots:
     dependencies:
       whatwg-mimetype: 4.0.0
       whatwg-url: 15.1.0
+    optional: true
 
   debug@2.6.9:
     dependencies:
@@ -12884,7 +12701,8 @@ snapshots:
 
   decamelize@6.0.1: {}
 
-  decimal.js@10.6.0: {}
+  decimal.js@10.6.0:
+    optional: true
 
   decircular@0.1.1: {}
 
@@ -13576,6 +13394,7 @@ snapshots:
       '@types/node': 20.19.25
       '@types/whatwg-mimetype': 3.0.2
       whatwg-mimetype: 3.0.0
+    optional: true
 
   has-flag@3.0.0: {}
 
@@ -13611,8 +13430,7 @@ snapshots:
   html-encoding-sniffer@4.0.0:
     dependencies:
       whatwg-encoding: 3.1.1
-
-  html-escaper@2.0.2: {}
+    optional: true
 
   htmlfy@0.8.1: {}
 
@@ -13740,7 +13558,8 @@ snapshots:
 
   is-plain-obj@4.1.0: {}
 
-  is-potential-custom-element-name@1.0.1: {}
+  is-potential-custom-element-name@1.0.1:
+    optional: true
 
   is-reference@1.2.1:
     dependencies:
@@ -13778,19 +13597,6 @@ snapshots:
 
   isexe@3.1.1: {}
 
-  istanbul-lib-coverage@3.2.2: {}
-
-  istanbul-lib-report@3.0.1:
-    dependencies:
-      istanbul-lib-coverage: 3.2.2
-      make-dir: 4.0.0
-      supports-color: 7.2.0
-
-  istanbul-reports@3.2.0:
-    dependencies:
-      html-escaper: 2.0.2
-      istanbul-lib-report: 3.0.1
-
   jackspeak@3.4.3:
     dependencies:
       '@isaacs/cliui': 8.0.2
@@ -13799,8 +13605,6 @@ snapshots:
 
   jiti@2.7.0: {}
 
-  js-tokens@10.0.0: {}
-
   js-tokens@4.0.0: {}
 
   js-tokens@9.0.1: {}
@@ -13842,6 +13646,7 @@ snapshots:
       - bufferutil
       - supports-color
       - utf-8-validate
+    optional: true
 
   jsesc@0.5.0: {}
 
@@ -14091,28 +13896,19 @@ snapshots:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.5
 
-  magicast@0.5.2:
-    dependencies:
-      '@babel/parser': 7.29.7
-      '@babel/types': 7.29.7
-      source-map-js: 1.2.1
-
   make-dir@2.1.0:
     dependencies:
       pify: 4.0.1
       semver: 5.7.2
     optional: true
 
-  make-dir@4.0.0:
-    dependencies:
-      semver: 7.8.4
-
   matcher-collection@2.0.1:
     dependencies:
       '@types/minimatch': 3.0.5
       minimatch: 3.1.5
 
-  mdn-data@2.12.2: {}
+  mdn-data@2.12.2:
+    optional: true
 
   meow@13.2.0: {}
 
@@ -14701,10 +14497,6 @@ snapshots:
 
   pirates@4.0.7: {}
 
-  pixelmatch@7.1.0:
-    dependencies:
-      pngjs: 7.0.0
-
   pkg-types@1.3.1:
     dependencies:
       confbox: 0.1.8
@@ -14986,7 +14778,8 @@ snapshots:
 
   require-directory@2.1.1: {}
 
-  require-from-string@2.0.2: {}
+  require-from-string@2.0.2:
+    optional: true
 
   resolve-from@4.0.0: {}
 
@@ -15242,6 +15035,7 @@ snapshots:
   saxes@6.0.0:
     dependencies:
       xmlchars: 2.2.0
+    optional: true
 
   scheduler@0.27.0: {}
 
@@ -15515,7 +15309,8 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
-  symbol-tree@3.2.4: {}
+  symbol-tree@3.2.4:
+    optional: true
 
   sync-child-process@1.0.2:
     dependencies:
@@ -15578,11 +15373,13 @@ snapshots:
 
   tinyrainbow@3.1.0: {}
 
-  tldts-core@7.0.19: {}
+  tldts-core@7.0.19:
+    optional: true
 
   tldts@7.0.19:
     dependencies:
       tldts-core: 7.0.19
+    optional: true
 
   to-regex-range@5.0.1:
     dependencies:
@@ -15597,10 +15394,12 @@ snapshots:
   tough-cookie@6.0.0:
     dependencies:
       tldts: 7.0.19
+    optional: true
 
   tr46@6.0.0:
     dependencies:
       punycode: 2.3.1
+    optional: true
 
   tree-kill@1.2.2: {}
 
@@ -15913,15 +15712,49 @@ snapshots:
 
   vary@1.1.2: {}
 
-  vitest@4.1.8(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8))(@vitest/browser-preview@4.1.8(vite@packages+core)(vitest@4.1.8))(@vitest/browser-webdriverio@4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1))(@vitest/coverage-istanbul@4.1.8(vitest@4.1.8))(@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8))(@vitest/ui@4.1.8(vitest@4.1.8))(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core):
+  vitest@4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.10.3)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core):
     dependencies:
-      '@vitest/expect': 4.1.8
-      '@vitest/mocker': 4.1.8(vite@packages+core)
-      '@vitest/pretty-format': 4.1.8
-      '@vitest/runner': 4.1.8
-      '@vitest/snapshot': 4.1.8
-      '@vitest/spy': 4.1.8
-      '@vitest/utils': 4.1.8
+      '@vitest/expect': 4.1.9
+      '@vitest/mocker': 4.1.9(vite@packages+core)
+      '@vitest/pretty-format': 4.1.9
+      '@vitest/runner': 4.1.9
+      '@vitest/snapshot': 4.1.9
+      '@vitest/spy': 4.1.9
+      '@vitest/utils': 4.1.9
+      es-module-lexer: 2.0.0
+      expect-type: 1.3.0
+      magic-string: 0.30.21
+      obug: 2.1.3
+      pathe: 2.0.3
+      picomatch: 4.0.4
+      std-env: 4.0.0
+      tinybench: 2.9.0
+      tinyexec: 1.2.4
+      tinyglobby: 0.2.17
+      tinyrainbow: 3.1.0
+      vite: link:packages/core
+      why-is-node-running: 2.3.0
+    optionalDependencies:
+      '@edge-runtime/vm': 5.0.0
+      '@opentelemetry/api': 1.9.0
+      '@types/node': 24.10.3
+      '@vitest/browser-playwright': 4.1.9(playwright@1.57.0)(vite@packages+core)(vitest@4.1.9)
+      '@vitest/browser-preview': 4.1.9(vite@packages+core)(vitest@4.1.9)
+      '@vitest/browser-webdriverio': 4.1.9(vite@packages+core)(vitest@4.1.9)(webdriverio@9.20.1)
+      happy-dom: 20.0.10
+      jsdom: 27.2.0
+    transitivePeerDependencies:
+      - msw
+
+  vitest@4.1.9(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-playwright@4.1.9)(@vitest/browser-preview@4.1.9)(@vitest/browser-webdriverio@4.1.9)(happy-dom@20.0.10)(jsdom@27.2.0)(vite@packages+core):
+    dependencies:
+      '@vitest/expect': 4.1.9
+      '@vitest/mocker': 4.1.9(vite@packages+core)
+      '@vitest/pretty-format': 4.1.9
+      '@vitest/runner': 4.1.9
+      '@vitest/snapshot': 4.1.9
+      '@vitest/spy': 4.1.9
+      '@vitest/utils': 4.1.9
       es-module-lexer: 2.0.0
       expect-type: 1.3.0
       magic-string: 0.30.21
@@ -15939,12 +15772,9 @@ snapshots:
       '@edge-runtime/vm': 5.0.0
       '@opentelemetry/api': 1.9.0
       '@types/node': 24.12.4
-      '@vitest/browser-playwright': 4.1.8(playwright@1.57.0)(vite@packages+core)(vitest@4.1.8)
-      '@vitest/browser-preview': 4.1.8(vite@packages+core)(vitest@4.1.8)
-      '@vitest/browser-webdriverio': 4.1.8(vite@packages+core)(vitest@4.1.8)(webdriverio@9.20.1)
-      '@vitest/coverage-istanbul': 4.1.8(vitest@4.1.8)
-      '@vitest/coverage-v8': 4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8)
-      '@vitest/ui': 4.1.8(vitest@4.1.8)
+      '@vitest/browser-playwright': 4.1.9(playwright@1.57.0)(vite@packages+core)(vitest@4.1.9)
+      '@vitest/browser-preview': 4.1.9(vite@packages+core)(vitest@4.1.9)
+      '@vitest/browser-webdriverio': 4.1.9(vite@packages+core)(vitest@4.1.9)(webdriverio@9.20.1)
       happy-dom: 20.0.10
       jsdom: 27.2.0
     transitivePeerDependencies:
@@ -15990,6 +15820,7 @@ snapshots:
   w3c-xmlserializer@5.0.0:
     dependencies:
       xml-name-validator: 5.0.0
+    optional: true
 
   wait-port@1.1.0:
     dependencies:
@@ -16066,7 +15897,8 @@ snapshots:
       - supports-color
       - utf-8-validate
 
-  webidl-conversions@8.0.0: {}
+  webidl-conversions@8.0.0:
+    optional: true
 
   webpack-virtual-modules@0.6.2: {}
 
@@ -16074,7 +15906,8 @@ snapshots:
     dependencies:
       iconv-lite: 0.6.3
 
-  whatwg-mimetype@3.0.0: {}
+  whatwg-mimetype@3.0.0:
+    optional: true
 
   whatwg-mimetype@4.0.0: {}
 
@@ -16082,6 +15915,7 @@ snapshots:
     dependencies:
       tr46: 6.0.0
       webidl-conversions: 8.0.0
+    optional: true
 
   which@2.0.2:
     dependencies:
@@ -16140,9 +15974,11 @@ snapshots:
     dependencies:
       is-wsl: 3.1.0
 
-  xml-name-validator@5.0.0: {}
+  xml-name-validator@5.0.0:
+    optional: true
 
-  xmlchars@2.2.0: {}
+  xmlchars@2.2.0:
+    optional: true
 
   y18n@5.0.8: {}
 
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 17b7b591af..39adc3310c 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -40,8 +40,17 @@ catalog:
   '@types/ws': ^8.18.0
   '@typescript/native-preview': 7.0.0-dev.20260605.1
   '@vitejs/plugin-react': ^5.1.2
-  '@vitest/browser': ^4.1.6
-  '@vitest/browser-playwright': ^4.1.6
+  '@vitest/browser': 4.1.9
+  '@vitest/browser-playwright': 4.1.9
+  '@vitest/browser-preview': 4.1.9
+  '@vitest/browser-webdriverio': 4.1.9
+  '@vitest/expect': 4.1.9
+  '@vitest/mocker': 4.1.9
+  '@vitest/pretty-format': 4.1.9
+  '@vitest/runner': 4.1.9
+  '@vitest/snapshot': 4.1.9
+  '@vitest/spy': 4.1.9
+  '@vitest/utils': 4.1.9
   '@voidzero-dev/vitepress-theme': 4.8.4
   '@vueuse/core': ^14.0.0
   '@yarnpkg/fslib': ^3.1.3
@@ -116,7 +125,7 @@ catalog:
   vitepress-plugin-group-icons: ^1.7.1
   vitepress-plugin-llms: ^1.1.0
   vitepress-plugin-og: ^0.0.5
-  vitest: ^4.1.6
+  vitest: 4.1.9
   vue: ^3.5.35
   web-tree-sitter: ^0.26.0
   ws: ^8.20.1
@@ -175,23 +184,21 @@ minimumReleaseAgeExclude:
 overrides:
   rolldown: workspace:rolldown@*
   vite: workspace:@voidzero-dev/vite-plus-core@*
-  vite-plus: workspace:*
-  vitest: workspace:@voidzero-dev/vite-plus-test@*
-  vitest-dev: npm:vitest@^4.1.8
+  vite-plus: workspace:vite-plus@*
 packageExtensions:
+  '@rollup/plugin-dynamic-import-vars':
+    dependencies:
+      '@types/estree': ^1.0.0
   sass-embedded:
     peerDependencies:
       source-map-js: '*'
     peerDependenciesMeta:
       source-map-js:
         optional: true
-  '@rollup/plugin-dynamic-import-vars':
-    dependencies:
-      '@types/estree': ^1.0.0
 patchedDependencies:
-  sirv@3.0.2: vite/patches/sirv@3.0.2.patch
   chokidar@3.6.0: vite/patches/chokidar@3.6.0.patch
   dotenv-expand@13.0.0: vite/patches/dotenv-expand@13.0.0.patch
+  sirv@3.0.2: vite/patches/sirv@3.0.2.patch
 peerDependencyRules:
   allowAny:
     - vite
diff --git a/rfcs/cli-output-polish.md b/rfcs/cli-output-polish.md
index c3f4010d8b..97427add66 100644
--- a/rfcs/cli-output-polish.md
+++ b/rfcs/cli-output-polish.md
@@ -298,7 +298,9 @@ The `vite_install` crate also has `Warning:` and `Note:` messages across multipl
 
 ### Phase 3: Rebrand vitest Output
 
-Vitest is bundled (not cloned source) via `@voidzero-dev/vite-plus-test`. Its build script (`packages/test/build.ts`) copies and rewrites vitest's dist files. We patch the bundled cac chunk during the build to rebrand CLI output.
+> **Note:** This phase has been reverted. The `@voidzero-dev/vite-plus-test` bundled wrapper was removed in favor of consuming upstream `vitest` directly, since the `vite` → `@voidzero-dev/vite-plus-core` package manager override already handles the dependency redirection. Vitest output is no longer rebranded.
+
+Historical context (no longer applies): Vitest was bundled (not cloned source) via `@voidzero-dev/vite-plus-test`. Its build script (`packages/test/build.ts`) copied and rewrote vitest's dist files. We patched the bundled cac chunk during the build to rebrand CLI output.
 
 #### 3.1 Approach: Build-time patching of bundled cac chunk
 
diff --git a/rfcs/migration-command.md b/rfcs/migration-command.md
index db14eacfee..e7a63cafd7 100644
--- a/rfcs/migration-command.md
+++ b/rfcs/migration-command.md
@@ -203,12 +203,10 @@ Wrote agent instructions to AGENTS.md
   },
   "devDependencies": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
     "@vitejs/plugin-react": "^4.2.0"
   },
   "overrides": {
-    "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vite": "npm:@voidzero-dev/vite-plus-core@latest"
   }
 }
 ```
@@ -223,7 +221,6 @@ Wrote agent instructions to AGENTS.md
   },
   "devDependencies": {
     "vite": "catalog:",
-    "vitest": "catalog:",
     "@vitejs/plugin-react": "^4.2.0",
     "vite-plus": "catalog:"
   },
@@ -244,15 +241,12 @@ Wrote agent instructions to AGENTS.md
 ```yaml
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
   vite-plus: latest
 overrides:
   vite: 'catalog:'
-  vitest: 'catalog:'
 peerDependencyRules:
   allowAny:
     - vite
-    - vitest
   allowedVersions:
     vite: '*'
     vitest: '*'
@@ -267,16 +261,14 @@ Projects that already have a `pnpm` field in `package.json` (e.g., with `overrid
   "name": "my-package",
   "devDependencies": {
     "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
     "vite-plus": "latest"
   },
   "pnpm": {
     "overrides": {
-      "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-      "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+      "vite": "npm:@voidzero-dev/vite-plus-core@latest"
     },
     "peerDependencyRules": {
-      "allowAny": ["vite", "vitest"],
+      "allowAny": ["vite"],
       "allowedVersions": { "vite": "*", "vitest": "*" }
     }
   }
@@ -297,6 +289,7 @@ Projects that already have a `pnpm` field in `package.json` (e.g., with `overrid
 - rewrite `import from '@vitest/browser/{name}'` to `import from 'vite-plus/test/browser/{name}'`, e.g.: `import from '@vitest/browser/context'` to `import from 'vite-plus/test/browser/context'`
 - rewrite `import from '@vitest/browser-playwright'` to `import from 'vite-plus/test/browser-playwright'`
 - rewrite `import from '@vitest/browser-playwright/{name}'` to `import from 'vite-plus/test/browser-playwright/{name}'`
+- `declare module 'vitest'`, `declare module 'vitest/{name}'`, and `declare module '@vitest/browser*'` are intentionally **not** rewritten — `vite-plus/test*` is a thin re-export of upstream `vitest*`, so type augmentations have to target the upstream module identity to merge correctly
 
 **Note**: For Yarn, use `resolutions` instead of `overrides`.
 
@@ -511,15 +504,12 @@ For monorepo projects and standalone projects without existing `pnpm` config in
 ```yaml
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
   vite-plus: latest
 overrides:
   vite: 'catalog:'
-  vitest: 'catalog:'
 peerDependencyRules:
   allowAny:
     - vite
-    - vitest
   allowedVersions:
     vite: '*'
     vitest: '*'
@@ -532,12 +522,10 @@ peerDependencyRules:
 ```json
 {
   "devDependencies": {
-    "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vite": "npm:@voidzero-dev/vite-plus-core@latest"
   },
   "overrides": {
-    "vite": "npm:@voidzero-dev/vite-plus-core@latest",
-    "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
+    "vite": "npm:@voidzero-dev/vite-plus-core@latest"
   }
 }
 ```
@@ -549,7 +537,6 @@ peerDependencyRules:
 ```yaml
 catalog:
   vite: npm:@voidzero-dev/vite-plus-core@latest
-  vitest: npm:@voidzero-dev/vite-plus-test@latest
 ```
 
 `package.json`
@@ -557,8 +544,7 @@ catalog:
 ```json
 {
   "resolutions": {
-    "vite": "catalog:",
-    "vitest": "catalog:"
+    "vite": "catalog:"
   }
 }
 ```
diff --git a/vite.config.ts b/vite.config.ts
index 52d99d7e93..34769934c5 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -68,12 +68,6 @@ export default defineConfig({
       'packages/cli/snap-tests-global/migration-lint-staged-ts-config',
       'packages/cli/snap-tests-global/migration-partially-installed-vite-plus/**',
       'ecosystem-ci/*/**',
-      'packages/test/**.cjs',
-      'packages/test/**.cts',
-      'packages/test/**.d.mjs',
-      'packages/test/**.d.ts',
-      'packages/test/**.mjs',
-      'packages/test/browser/',
       'packages/cli/src/run-config.ts',
       'vite',
       'rolldown',
@@ -108,7 +102,6 @@ export default defineConfig({
           'vp run rolldown#build-node',
           'vp run vite#build-types',
           'vp run @voidzero-dev/vite-plus-core#build',
-          'vp run @voidzero-dev/vite-plus-test#build',
           'vp run vite-plus#build',
         ].join(' && '),
       },
