# bivium 中文文档

规范地址：https://npmcn.com/package/bivium/

版本：0\.3\.0
npm 元数据更新时间：2026-11-03T04:12:12.644Z
中文译文更新时间：2026-11-03T06:08:45.972Z
软件包许可：MIT

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/bivium
原文对照：https://npmcn.com/package/bivium/?lang=original
项目主页：https://github.com/metonym/bivium
源码仓库：https://github.com/metonym/bivium

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：193 kB，8 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：0 个
- 首次发布：2026-10-26T02:35:40.896Z
- 最新版本发布：2026-11-03T04:12:12.288Z
- 已发布版本数：8（近 12 个月 8 个）

## 中文简介

只运行变更可能影响的测试，不要错过任何一个。

## 安装

```sh
npm install bivium
```

也可以使用：`pnpm add bivium`、`yarn add bivium`、`bun add bivium`

## README

# bivium

> 只运行变更可能影响的测试，不要错过任何一个。

Bivium为存储库构建一个静态的、文件级的依赖关系图，并将其从更改所涉及的文件回溯到到达这些文件的测试。它关闭失败：任何它不能读取的东西（非文字） `import()`（未解析的说明符，没有声明输入的磁盘读取）计数取决于所有内容，并且它无法进行的更改运行整个套件，这是有原因的。它挑选测试；你的跑步者负责跑步。

它没有依赖关系，也不需要安装它计划的存储库：它从不读取git索引和工作树 `node_modules`． CI可以在跳过安装的作业中进行计划，并跳过选择为 `none`． 它运行在Node≥22和Bun上。它是从 [carbon-components-svelte](https://github.com/carbon-design-system/carbon-components-svelte)的 `test:changed`.

```sh
bun add -d bivium
```

```ts
// bivium.config.ts
import { defineConfig } from "bivium";

export default defineConfig({
  suites: {
    unit: { tests: "tests/**/*.test.ts", run: "vitest run {files}", root: "tests" },
    e2e: {
      tests: "e2e/**/*.test.ts",
      run: "playwright test {files}",
      root: "e2e",
      urls: { "/*": "e2e/fixtures/*" }, // page.goto("/x.html") → e2e/fixtures/x.html
    },
  },
  roots: ["src", "tests", "e2e"], // a changed file outside them runs everything
  ignore: ["**/*.md", "docs/**"],
});
```

```console
$ bunx bivium plan
unit tests: 3 of 12 files
  tests/Button.test.ts
  tests/Modal.test.ts
  tests/css/unknown.test.ts
e2e tests: none
$ bunx bivium run --suite unit     # vitest run tests/Button.test.ts …
```

## 它写的是什么？

一遍词法分析器读取JavaScript和TypeScript (`.js`， `.ts`， `.jsx`， `.tsx`，以及他们的 `m`/`c` 的脚本和标记表达式 `.svelte`，剧本 `.vue`， `.astro` 和 `.html`，和 `script[src]`， `link[href]` 和 `iframe[src]` 的 `.html`． 只有代码才算数：在字符串或注释中拼写的import不加边。

| 它遵循 | 作为 |
| :-- | :-- |
| 静态、副作用和动态导入； `export … from`， `require()` | 说明符可以加载的每个文件的边缘：每个存在的扩展名，每个 `exports` 条件。一个失踪的 `x.js` （`.jsx`， `.mjs`， `.cjs`)是它的TypeScript源，因为TypeScript会读取它： `x.ts`， `x.tsx` 或者 `x.d.ts`，通过任何路径、别名或 `exports` 目标。的值导入 `x.d.ts` 是未知的：什么运行不在图中 |
| `vi.mock()`， `vi.importActual()` 还有其他的家庭成员 | 边缘 |
| `import.meta.glob()` | 每一个匹配的文件，现在和以后 |
| JSDoc `@type {import("x")}` 和 `@import` | 边缘（TypeScript读取它们） |
| `%sveltekit.assets%/x` 在SvelteKit里 `app.html` | 有优势 `static/x` 在项目中，最近的带有 `svelte.config.*` 或者一个 `package.json`. `%sveltekit.head%`， `%sveltekit.body%` 其余的都不是参考文献 |
| `page.goto("/x.html")` 在一套房里 `urls` | 书页的边缘 |
| `compilerOptions.paths` 和 `baseUrl` 最近的 `tsconfig.json`，与其亲属 `extends` | 所有目标的边缘，都试过了 `alias` 之前, `package.json` `exports`． 一个 `tsconfig.json` 它不解析映射，什么都没有 `check` 列出它 |
| `#name` 子路径导入，通过 `imports` 导入文件本身的 `package.json` | 所有条件的边缘都是存在的；一个无与伦比的 `#name` 是未知的 |
| 从仅由再导出组成的模块中进行命名导入 | 当包声明时，导出每个名称的一个文件的边缘 `sideEffects` |
| 文件自己的包及其祖先的包 `package.json` 依赖项和内置项 | `pkg:` 节点 |
| 别的 | 未知：文件取决于每次更改 |

一个根 `bun.lock`， `package-lock.json` 或者 `pnpm-lock.yaml` 不是源文件：对其中一个文件的更改会产生它所更改的包的种子(参见 [Lockfiles](https://npmcn.com/package/bivium/#readme-lockfiles)）。

不能清晰地lex的文件由regex提取器读取，这是过度近似的。CSS不会被扫描：生成的样式表通过 `edges`，和 [窄](https://npmcn.com/package/bivium/#readme-narrowers) 可以做得更好。

## 配置

`bivium.config.ts` (或 `.mts`， `.mjs`， `.js`， `.json`)，或者 `--config <file>`． 一个 `.ts` 配置需要Bun或Node≥22.18。每个glob键取一个glob或一个列表。未知的键是错误的，因为拼写错误的键会悄悄地选择更少的键。

| 关键 | 意义 |
| :-- | :-- |
| `suites.<name>.tests` | 套件的测试文件 |
| `suites.<name>.run` | 命令 `bivium run` 运行。 `{files}` 被选择(nothing for ？ `all`）; 没有它，文件将被追加 |
| `suites.<name>.root` | 孤儿规则适用的地方：套件测试无法到达的更改文件以其他方式连接（安装文件，运行器配置），因此整个套件运行。路径，而不是globs (`e2e`,而不是 `e2e/**`): glob是一个错误。默认值：整个存储库 |
| `suites.<name>.urls` | URL模式→文件模式，for `page.goto()` 电话下 `root` |
| `suites.<name>.pages` | 只有一页 `goto()` 打开;只有它们加载的文件才连接到没有测试 |
| `suites.<name>.setup` | 运行程序在每次测试（测试和测试）之前加载的文件 `setupFiles`,摩卡 `--require`,剧作家 `globalSetup`)：套件的每个测试都依赖于它们和它们导入的内容。Bivium不会读取跑步者的配置，所以在这里命名它们 |
| `suites.<name>.wiring` | 其更改运行整个套件的文件：另一个套件，一个运行器配置 |
| `suites.<name>.nonInputs` | 下的文件 `root` 运行程序从不读取，例如类型检查输入 |
| `suites.<name>.types` | `false` 当套件运行代码并且无法观察类型(Vitest, Jest，剧作家， `bun test`)：只能通过 `import type`， `export type … from`， JSDoc类型或声明文件都不选择它们。对于孤儿规则，仅类型到达只对声明文件计数，因为运行器可以按名称加载任何其他文件：列表下的仅类型模块 `root` 在 `nonInputs`。默认值: `true`，其中一个类型检查套件(`tsc`， `vitest --typecheck`)的需求。内联 `import { type A }` 永远是一个边缘：下 `verbatimModuleSyntax` 它仍然会加载模块 |
| `suites.<name>.output` | 步进输出 `plan --github` 写道。默认值: `<name>-tests` |
| `roots` | 图读取的路径（不是globs）。在它们之外的一个未被忽略的更改文件将运行所有内容。默认值：整个存储库 |
| `ignore` | 没有测试可以观察到的文件，除非通过边缘（文档，故事）。对1的更改只选择到达它的测试，从不按孤立规则运行套件，并且不选择依赖未知的测试 |
| `alias` | 说明符前缀→路径，作为打包器的别名。在tsconfig之前尝试过 `paths` 和 `package.json` `exports`． 以结尾的键 `/` 是前缀，就像Vite读到的： `{ "@/": "src/" }` 地图 `@/x` 来 `src/x` |
| `edges` | 生成的文件glob→它的输入，它依赖于传递，如 `@depends-on`： `{ "dist/*.css": "styles/**" }`． 一个键可能会命名git没有列出的文件，比如被git忽略的生成器输出(`{ ".velite/*": "content/**" }`)：在它下解析的导入，通过扩展名或作为索引，落在该文件上，而不是未解析。在一个值中， `$1`， `$2`…是键的通配符匹配的内容，从左开始计数： `{ "examples/*/package.json": "examples/$1/**" }` |
| `reads` | 文件glob→它作为文本读取的文件，如 `@reads`只有一个国家的直接变化才有意义，所以他们的输入和未知不会传播到它。一个 `edges` 或者 `reads` 为一个文件命名的键（没有通配符）声明它所读取的内容为它自己的 `@depends-on` 或者 `@reads` 会，所以它的磁盘读取不是未知的。值采取 `$1`… `edges`： `{ "tests/*.test.ts": "tests/__snapshots__/$1.test.ts.snap" }` |
| `self` | 选择器自己的文件：更改一个就可以运行所有文件。配置文件总是一个 |
| `importOnly` | 只有导入到达的文件：删除一个文件不会使套件孤立 |
| `hermetic` | 磁盘读取和非文字导入的文件不会到达存储库的文件：库的文件 `fs` 调用调用者的路径，这种测试只读取它所写的临时文件。他们没有添加未知 |
| `barrels` | 可以绑定命名导入的模块。默认：每个模块只由再出口组成 |
| `external` | 将代码导入的依赖项安装打包而不声明 |
| `inert` | 没有测试使用的包，除非导入它们：lint、formatters、type包(`["@biomejs/biome", "knip", "@types/*"]`）. 一个 [lockfile变化](https://npmcn.com/package/bivium/#readme-lockfiles) 只有他们到达选择什么。与 `loadedByName` 设置，只包带有 `bin` 需要的清单 |
| `loadedByName` | 测试可以通过导入以外的方式加载的包：一个测试环境或一个配置用字符串(`["jsdom", "ts-jest"]`）. 设置它，甚至到 `[]`，每隔一个没有 `bin` 只通过导入加载，所以a [lockfile变化](https://npmcn.com/package/bivium/#readme-lockfiles) 达到一个没有测试导入只选择它的导入。有一个包裹。 `bin` 总是作为一个测试可以运行 |
| `extensions` | 在指定符之后尝试后缀 |
| `deadGlobs` | 死着 `check` 允许,每个 `{ file, glob, reason }`，地球仪 `file` 法术, |
| `knownUnknowns` | 节点 `check` 允许有未知的依赖关系 `{ file, reason }`． 有了它，连到 `[]` (这是 `--max-unknown 0`)， `check` 在一个未知节点上失败，一些测试到达它没有列出，在一个条目不再是未知的，所以一个新的未知不能取代一个消失了 |
| `narrowers` | 看到 [窄](https://npmcn.com/package/bivium/#readme-narrowers) |
| `base` | 默认base ref.否则上游默认分支(`origin/HEAD`,然后 `origin/main`， `origin/master`， `main`， `master`） |
| `cache` | 缓存的目录，或 `false`。默认值: `node_modules/.cache/bivium`，或者 `.bivium/cache` |

## 测试无法达到的变化

套房下的一个更改文件 `root` 套件的测试没有达到其他方式（一个运行器配置，一个设置文件），所以整个套件运行：孤儿规则。使用默认值 `root`，整个存储库，任何文件bivium都无法追踪到测试运行套件，这是健全和粗糙的。集 `root` 在每个套件的测试和源代码所在的目录中，说明其余部分是什么：

| 你改变了…… | 和希望…… | 使用 |
| :-- | :-- | :-- |
| 一个没有测试可以观察到的文件：文档、故事、变更日志 | 没有什么可运行的，除非某些测试导入它 | `ignore` |
| 套房下的一个文件 `root` 只有另一个工具读取：类型检查输入 | 这套房没什么可跑的 | 套房的 `nonInputs` |
| 一个跑路配置或第二个线束 | 整个套房 | 套房的 `wiring` |
| 运行程序在每次测试之前加载的安装文件，或者它导入的文件 | 套件的每个测试 | 套房的 `setup` |
| 选择器的配置，或者它自己的代码 | 一切 | `self` （配置文件总是如此） |
| 一个声明文件，在运行代码的套件中 | 没有什么 | 套房的 `types: false` |
| 仅类型模块 `import type` 达克斯，在一个运行代码的套件里 | 没有什么 | 套房的 `types: false`，其中的模块 `nonInputs` |
| 可以测试列出但不能读取的文件（一条路由，一个故事） | 测试，当一个被添加或删除 | `// @lists <glob>` 在测试中 |
| 构建时将文件转换为测试加载的文件 | 加载输出的测试 | `edges` 从输出到输入 |

外面的文件 `roots` 没有被忽视的运行一切：它是连接图无法看到的支撑，例如CI工作流。

## 食谱

**玩笑还是玩笑。** 命名运行程序加载的安装文件，mirror `resolve.alias` a我很喜欢 `"@/"` 像编写的那样工作)，为他们的测试提供快照，并且，对于运行代码的套件，跳过仅类型的边缘：

```ts
suites: {
  unit: {
    tests: "src/**/*.test.{ts,tsx}",
    run: "vitest run {files}",
    root: "src",
    setup: ["src/setupTests.ts"], // vitest.config.ts `setupFiles`
    wiring: ["vitest.config.ts"],
    types: false,
    nonInputs: ["src/**/types.ts"], // types only: nothing loads them at runtime
  },
},
alias: { "@/": "src/" },
reads: { "src/**/*.test.{ts,tsx}": "src/$1__snapshots__/$2.test.$3.snap" },
```

tsconfig `paths` ( `vite-tsconfig-paths` read)是不带an的 `alias`． 一个无广延性的 `vi.mock("@/x")` 像导入一样解析。运行程序按名称加载的测试环境(`jsdom`， `happy-dom`)进去了 `loadedByName` 如果你设置它。

**规划工作区。** 工作区包通过它们的 `package.json` `exports`，和 `.js` 指定符转换为一个(`@acme/ui/Button.js`)达到它的 `.ts` 源。一个 `pnpm-lock.yaml` Change通过它所更改的包进行选择。集 `roots` 到套件应该看到其更改的包和应用程序，以及每个测试运行器的套件。

**剧作家。** 地图 `page.goto()` 它们打开的页面的url `urls`，只列出一页 `goto()` 打开在 `pages`，及名称 `playwright.config.ts` 在 `wiring`：

```ts
e2e: {
  tests: "e2e/**/*.spec.ts",
  root: "e2e",
  urls: { "/*": "e2e/fixtures/*.html" },
  wiring: ["playwright.config.ts"],
},
```

**生成的代码。** 将内容或类型生成器写入被忽略的目录(`.velite`， `.contentlayer/generated`)是一个 `edges` 从写到读的条目： `{ ".velite/*": ["content/**", "velite.config.ts"] }`． 的进口 `#velite` 或者 `../.velite` 然后到达内容，而不是每一个变化。

## Lockfiles

依赖关系会改变一个锁文件，而没有文件会导入它。当一个根 `bun.lock`， `package-lock.json` （v2或v3）或 `pnpm-lock.yaml` （v6或v9）更改后，bivium将其与合并基础进行区分，并使用 `pkg:` 条目发生变化的包的节点，以及依赖于其中一个包的每个包的节点，所以a `svelte` Bump选择导入的内容 `svelte`，或者在此基础上构建的软件包。一个 `package.json` 其唯一的变化是依赖，lockfile记录的方式与那些包的变化相同。

更改后的包还可以以图中看不到的方式使用：测试生成的CLI、运行器、类型包。所以当一个包发生变化时 `package.json` 声明，或者一些文件导入，而没有测试导入，一切都运行，原因是(`bun.lock: playwright 1.63.0 → 1.64.0, and no test imports it`)，除非包裹是 `inert`． 与 `loadedByName` 集，只包带一个 `bin` 在lockfile中，或它所列出的文件中，以这种方式计数：仅到达CMS配置导入库的碰撞不选择任何内容。对于一个bivium无法解析的lockfile，它的设置（除了它的包之外）也可以运行。`overrides`补丁，pnpm `settings`， `catalogs`（或PNPM本身的版本），以及 `package.json` 更改lockfile不记录。其他锁档(`yarn.lock`， `bun.lockb`)是和其他文件一样的文件：在根目录之外，对一个文件的修改会运行所有文件。

```console
$ bunx bivium plan
bun.lock: @biomejs/biome 2.5.14 → 2.6.3, inert
bun.lock: svelte 5.57.1 → 5.57.2
unit tests: 4 of 12 files
…
```

## 注释

| 评论 | 意义 |
| :-- | :-- |
| `// @depends-on <glob> [glob…]` | 非导入的输入：脚本读取的文件，生成的工具。传递。一种文件，它读取磁盘时没有一个(或。 `@reads`，或者 `@lists`)是未知的，除非它 `hermetic` |
| `// @reads <glob> [glob…]` | 文件作为文本读取。只有直接更改一个才算数，而不是更改它所导入的内容。配置的 `reads` 对于不能保存注释的文件也这样说 |
| `// @lists <glob> [glob…]` | 它列出文件名而不是内容的文件：检查每条路由的约定测试都有一个处理程序。只有添加、删除或重命名一个才算数；编辑不会选择它，也不会按照孤立规则运行它的套件 |
| `// @glob-registry` | 这个文件的 `import.meta.glob` 是一个查找表，不添加边：加载条目名称本身是什么 |

## 命令行界面

| 命令 | 做 |
| :-- | :-- |
| `bivium plan [base] [--json] [--github]` | 打印每个套房的选择(`none`，文件出了多少， `all` 文件，或者 `all (full run)`)，以及为什么一个套房会满员。 `--json` 添加基础，合并基础，更改的文件，更窄的结果，更改的包，每个套件的测试计数(`totals`)，以及作为数据的原因(`causes`，每一个 `FullRunCause`： `{ kind, suites, file?, package?, narrower?, message }`）。 `--github` 还将每个套件的选择写入 `$GITHUB_OUTPUT` |
| `bivium run [base] [--suite <name>]` | 管理套房 `run` 命令及其选择（默认：第一个套件），或者在没有任何影响时跳过它 |
| `bivium why <test> [file] [--format tree|chain|source|json] [--all]` | 从已更改的文件或从 `file`每条边和它所在的线的拼写，以及有多少其他的变化达到了测试。 `--format tree` （默认值）从测试中提取； `chain` 从更改中读取到测试； `source` 打印每个跳的源行； `json` (或 `--json`)将链打印为数据。 `--all` 显示每个变化的链。对于未选择的测试，它会显示文件链到达的位置 |
| `bivium check [--max-unknown <n>] [--json]` | 首先计算它根据原因找到了什么，然后列出每个具有未知依赖关系的节点，以及在哪里和如何修复每个原因，并在超过 `n` 他们中的一个达到了测试，或者只有一个 `knownUnknowns` 没有列表。没有测试所依赖的节点被单独列出并且不计数：计划只运行测试，因此没有扩展。还列出了 `tsconfig.json` 不能解析的文件，以及 [死着](https://npmcn.com/package/bivium/#readme-dead-globs)，一次失败 `deadGlobs` 不允许在不匹配的条目上使用或。 `--json` 将报告打印为数据 |
| `bivium graph [--suite <name>] [--top <n>] [--json]` | 对每个文件的更改在图中选择了多少个套件的测试（忽略在任何更改上运行的测试，它分开计算）： `n` 到达最多的文件，以及文件如何从没有扩展到每个测试。达到大多数测试的几个集线器文件是使计划广泛的东西。 `--json` 打印每个套房的 `total` 和 `everyChange` 测试，每个节点都有它的 `imports` 和 `importers` (文件边), `fanIn` 每个套房，是否 `unknown` |

`--config`， `--cwd` 和 `--no-cache` 使用每个命令，在它之前或之后，和 `why` 和 `check` 采取 `--base <ref>`. `--help` (或 `-h`)打印使用情况。一个未知的命令、选项或 `--format` 是一个错误。退出状态：完成时为0，完成时为1 `check` 查找问题，2用于查找使用或配置错误； `run` 带着命令的状态退出。

`why` 按拼法命名每条边： `import`， `type` (一个 `import type`， `export type … from`（JSDoc类型，或与声明文件之间的边）， `setup` （一个套件的安装文件）， `goto` (一个页面 `page.goto()` 打开), `html` (一个HTML页面加载的模块： `script[src]`， `link[href]`， `iframe[src]` 或者内联脚本的import)， `name` （绑定到桶名的命名导入）和 `export` （导出它的文件）， `glob` （`import.meta.glob`)， `depends-on`， `edges` (配置) `reads` 和 `lists`.

```console
$ bunx bivium why e2e/accordion.test.ts
e2e/accordion.test.ts
└─ goto    e2e/fixtures/accordion.html             e2e/accordion.test.ts:13
   └─ html    e2e/fixtures/accordion.ts            e2e/fixtures/accordion.html:10
      └─ import  e2e/fixtures/Fixture.svelte       e2e/fixtures/accordion.ts:1
         └─ name    src/index.js#AccordionItem     e2e/fixtures/Fixture.svelte:2
            └─ export  src/AccordionItem.svelte    src/index.js:2
               ● changed
+ 1 more change reaches this test: css/_accordion.scss (--all shows each chain)

$ bunx bivium why e2e/accordion.test.ts --format chain
src/AccordionItem.svelte changed
  exported as AccordionItem  src/index.js:2
  imported by                e2e/fixtures/Fixture.svelte:2
  imported by                e2e/fixtures/accordion.ts:1
  loaded by page             e2e/fixtures/accordion.html:10
  opened by                  e2e/accordion.test.ts:13  ← selected
+ 1 more change reaches this test: css/_accordion.scss (--all shows each chain)

$ bunx bivium why e2e/accordion.test.ts --format source
selected: e2e/accordion.test.ts ← src/AccordionItem.svelte (5 hops)

  --> e2e/accordion.test.ts:13  goto
13 | await page.goto("/accordion.html");
  --> e2e/fixtures/accordion.html:10  html
10 | <script type="module" src="./accordion.ts"></script>
  --> e2e/fixtures/accordion.ts:1  import
 1 | import Fixture from "./Fixture.svelte";
  --> e2e/fixtures/Fixture.svelte:2  name
 2 | import { Accordion, AccordionItem } from "carbon-components-svelte";
  --> src/index.js:2  export
 2 | export { default as AccordionItem } from "./AccordionItem.svelte";
  ● src/AccordionItem.svelte: changed
+ 1 more change reaches this test: css/_accordion.scss (--all shows each chain)
```

对于一个依赖于每一个变化的测试， `chain` 说明为什么，在哪里，以及如何修复它，如 `check` :

```console
$ bunx bivium why tests/e2e.test.ts --format chain
tests/e2e.test.ts runs on every change
  because scripts/launcher.js:10  unresolved "./cli-entry.js"
    fix: no file at that path: fix the import, or add an `edges` entry if a build generates it
  reached via scripts/launcher.js
    depended on by  scripts/build.ts:1
    depended on by  tests/e2e.test.ts:2  ← selected
```

## 死着

一个错字就 `@depends-on`，或者只指向计划器跳过的文件，看起来很好，但从不选择它的文件。 `check` 评估每一个 `@depends-on`， `@reads`， `@lists` 和 `import.meta.glob` 模式,每 `edges` 和 `reads` 价值，每个套房 `setup`，针对git列出的每个文件（跟踪的和未跟踪的，是否在根目录中），并报告其中一个：

| 问题 | 意味着 |
| :-- | :-- |
| 不匹配 | 没有文件匹配：拼写错误或过时的路径 |
| 只匹配被忽略的文件，部分被忽略 | 有些匹配会被忽略，并且不在图中，因此更改为1将不选择任何内容 |

带有capture的配置值只有在其键匹配的每个文件都不匹配时才会失效，并且在配置文件下也会报告与图读取的文件不匹配的键。图读取的被忽略的文件仍然是遍历的种子，而根之外的更改将运行所有内容，因此两者都不是死的。一个故意死掉的球进去 `deadGlobs` 有一个原因：

```ts
deadGlobs: [
  { file: "tests/css/tokens.test.ts", glob: "docs/src/**", reason: "docs-only changes skip CI" },
],
```

## CI

在任何安装之前，计划自己的作业，并为每个套件的作业提供其选择：

```yaml
jobs:
  plan:
    runs-on: ubuntu-latest
    outputs:
      unit: ${{ steps.plan.outputs.unit-tests }}
      e2e: ${{ steps.plan.outputs.e2e-tests }}
    steps:
      - uses: actions/checkout@v7
        with:
          # History for the merge base, without old file contents: git
          # fetches the few a plan reads (a changed barrel or lockfile).
          fetch-depth: 0
          filter: blob:none
      - uses: oven-sh/setup-bun@v2
      - id: merge-base
        run: echo "sha=$(git merge-base origin/main HEAD)" >> "$GITHUB_OUTPUT"
      - uses: actions/cache@v6
        with:
          path: .bivium/cache
          key: bivium-${{ steps.merge-base.outputs.sha }}
          restore-keys: bivium-
      - id: plan
        # --bun: run on Bun, which loads a TypeScript config whatever Node
        # the runner has.
        run: bunx --bun bivium plan origin/main --github
  unit:
    needs: plan
    if: needs.plan.outputs.unit != 'none'
    steps:
      # …install, then run all tests or the selected files:
      - run: bunx vitest run ${{ needs.plan.outputs.unit != 'all' && needs.plan.outputs.unit || '' }}
```

缓存保存从每个文件提取的内容，这些内容由git blob键控，因此warm计划只读取分支更改的文件。在合并基础上键入它：拉取请求可以从默认分支恢复缓存，该分支在提交时运行是要命中的。没有 `node_modules`，缓存就在 `.bivium/cache`;它写了一个 `.gitignore` 所以它不会显示为变化。

在一台苹果m系列笔记本电脑的负载情况下，一个套餐的成本是多少（5个套餐中值，待机时间）：

| 存储库 | 文件 | 包，没有缓存 | 包子,温暖 | 节点24，没有缓存 | 节点24，温暖 | 缓存 |
| :-- | :-- | :-- | --: | --: | --: | --: |
| carbon-components-svelte | 4680年 | 0.15秒 | 0.09秒 | 0.27秒 | 0.13秒 | 452 KB |
| 生成的TypeScript， 23 MB | 7000年 | 0.14秒 | 0.08秒 | 0.30秒 | 0.12秒 | 896 KB |

所以这个大小的缓存是可选的， `bunx --bun` 这是值得的，并且在测试作业中进行规划（没有额外的作业，没有安装）花费的时间不到一秒钟。生成的存储库的冷计划在Bun上的RSS峰值为88 MB，在Node上为124 MB。

## 窄

有些更改通过生成的文件到达每个测试：每个页面加载已编译的样式表，因此任何样式编辑都会选择每个测试。更窄的使用来自语义差异的更细的种子替换这种更改的粗种子，例如呈现更改的CSS规则的源。

```ts
narrowers: [
  {
    name: "css",
    match: "styles/**", // the changed files it narrows
    opaque: "dist/*.css", // the generated nodes its seeds stand in for
    suites: ["e2e"],
    async narrow({ base, head, changed, graph }) {
      const rules = diffRules(await compile(base.commit), await compile(head.commit));
      if (!rules) return { all: "a rule that requires no class changed" };
      return { seeds: sourcesSpelling(rules), note: `${rules.length} rules changed` };
    },
  },
],
```

匹配的文件仍然沿着路径走，但是它停在 `opaque` 结点和种子代表着它们。窄的只能窄：它的套房的选择被削减到没有它选择的步行。一个返回的 `{ all: reason }`抛出或返回的任何东西都将完整地运行其套件。 `narrow()` 获取存储库 `root`，合并基和工作树为 `base` 和 `head` (每个都有 `commit` 和 `read(path)`)，匹配的 `changed` 文件，和只读 `graph` （`nodes()`， `importers(node)`， `dependents(nodes)`， `tests(suite)`）。 [`eval/carbon/css-narrower.ts`](https://npmcn.com/package/bivium/eval/carbon/css-narrower.ts) 它的碳成分是细长的，在玻璃上。

## 从代码

```ts
import { defineConfig, plan } from "bivium";

const config = defineConfig({ suites: { unit: { tests: "tests/**/*.test.ts" } } });
const result = await plan({ config, base: "origin/main" });
result.suites.unit; // { mode: "some", files: [...] }; result.suites.e2e is a type error
```

`plan(options?)` 需要 `cwd`， `base`， `config` (默认：配置文件 `cwd`), `cache`，并返回每个套件的选择、测试计数、作为句子和数据的原因、lockfile更改种子或发现惰性的包、基文件、合并基文件和更改的文件，以及每个狭窄返回的内容。给定一个配置，套件名称将传递到结果的类型。包只导出 `plan`， `defineConfig` 以及它们的类型。

## 相比之下,

|  | 选择 | 错过 |
| :-- | :-- | :-- |
| `vitest --changed`， `jest --changedSince` | 从运行程序自己的解析器测试其模块图到达已更改文件的测试 | 磁盘读取，固定装置，页面测试访问，配置和设置布线 |
| 剧作家 `--only-changed` | 测试导入更改的文件 | 测试打开的页面以及它们加载的内容 |
| Nx, Turborepo | 整个项目的投入发生了变化 | 在项目粒度中，项目声明的输入中没有任何内容 |
| 巴泽尔 | 其声明的输入发生变化的目标 | 如果每个输入都声明了，那么什么也没有 |
| bivium | 测试文件，跨运行程序和文件类型，从一个图 | 它什么也看不见；它不能的，它就跑 |

## 常见问题

### bivium 是什么？

只运行变更可能影响的测试，不要错过任何一个。

### 如何安装 bivium？

在项目目录运行 npm install bivium，或使用 pnpm add / yarn add / bun add。它还提供命令行工具 bivium，可以用 npx bivium 直接运行。

### bivium 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### bivium 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### bivium 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### bivium 可以商用吗？

bivium 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### bivium 还在维护吗？

最新版本 0\.3\.0 发布于 2026/11/03（今天）；至今共发布 8 个版本，其中过去 12 个月发布了 8 个；首次发布于 2026/10/26。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

