diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 97e57b7..4675ee6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,13 +36,13 @@ jobs:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- - name: Run categorized tests
+ - name: Run Vitest
run: npm run test:ci
- name: Upload JUnit test results
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}-node-${{ matrix.node }}
- path: test-results/*.xml
+ path: test-results/vitest.junit.xml
if-no-files-found: error
- run: npm run pack:verify
diff --git a/AGENTS.md b/AGENTS.md
index afd1b31..ec41d6f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -74,7 +74,7 @@ Adding support for a new Coding Agent host starts with
- All relative `.md` references across `skills/`, `references/`, `templates/`, `models/`, `docs/`, and `case-studies/`
must resolve; `test/skills-docs/doc-link-graph.test.mjs` enforces this in `npm test`.
-- After adding, moving, or renaming markdown docs, run `node --test test/skills-docs/doc-link-graph.test.mjs` before committing,
+- After adding, moving, or renaming markdown docs, run `npx vitest run test/skills-docs/doc-link-graph.test.mjs` before committing,
and regenerate the routing graph with `node scripts/doc-link-graph/cli.mjs skills/better-harness`
(it rewrites `docs/better-harness-doc-links.mmd`, which the test checks for staleness).
- Every reference doc shipped under `skills/better-harness/references/` must stay reachable from `SKILL.md` routing,
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ad36763..8b0c526 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -43,7 +43,7 @@ Useful focused checks:
```bash
# Markdown links and the generated Harness routing graph
-node --test test/skills-docs/doc-link-graph.test.mjs
+npx vitest run test/skills-docs/doc-link-graph.test.mjs
# Package and runtime-bundle boundaries
npm run pack:verify
@@ -95,8 +95,8 @@ unrelated cleanup, dependency changes, or local host state into the same change.
Run the smallest relevant tests while developing, then the broader gate justified
by the risk:
-- Markdown moves or links: `node --test test/skills-docs/doc-link-graph.test.mjs`.
-- Runtime logic: focused `node --test test/.test.mjs`, then `npm test`.
+- Markdown moves or links: `npx vitest run test/skills-docs/doc-link-graph.test.mjs`.
+- Runtime logic: focused `npx vitest run test/.test.mjs`, then `npm test`.
- Package/runtime roots: `npm run pack:verify`.
- Visual output: preview smoke, console inspection, and screenshot review.
- Cross-platform command changes: verify argv-based execution and avoid
diff --git a/docs/adapters/contributing-new-coding-agent.md b/docs/adapters/contributing-new-coding-agent.md
index a6afe12..e735267 100644
--- a/docs/adapters/contributing-new-coding-agent.md
+++ b/docs/adapters/contributing-new-coding-agent.md
@@ -210,7 +210,7 @@ moved, regenerate and verify the link graph:
```bash
node scripts/doc-link-graph/cli.mjs skills/better-harness
-node --test test/skills-docs/doc-link-graph.test.mjs
+npx vitest run test/skills-docs/doc-link-graph.test.mjs
npm test
npm run pack:verify
git diff --check
diff --git a/docs/community.md b/docs/community.md
index 9596431..d3ab38f 100644
--- a/docs/community.md
+++ b/docs/community.md
@@ -30,7 +30,7 @@ This is the complete reference. For the common cases, see Start Here above.
| Skill detail and reusable guidance | Yes | `skills//references/` or shared `references/` | Topic-scoped Markdown with stable headings and source boundaries | Loaded only when the skill or agent task needs it | Link/path check; consumer grep for any 2+ consumer claim |
| Maturity model | Yes, additive only | `models/.md` plus `models/routing.md` | Stable `model_id`, aliases, audience, levels, dimensions, evidence sources, scoring and confidence rules | Selectable through model routing after routing-file registration | Model-routing checks, fixture/report sample, no mutation of built-in defaults unless intentional |
| Detector or analysis signal | Yes | owning `models/.md`, `scripts//`, or `skills//references/` | Detector or signal id when stable, source evidence, emitted fields, false-positive and downgrade rules | Selected through model, capability, or skill routing; shared prose needs two visible workflow consumers | Fixtures or sample evidence ledger plus behavior, report-quality, or model-routing checks |
-| Executable analysis capability | Yes, but only with tests | `scripts//cli.mjs` | CLI contract, JSON output shape, fixtures, scoped helpers | Called by skills, hooks, reports, or host adapters | `node --test`, fixture assertions, cross-platform command construction |
+| Executable analysis capability | Yes, but only with tests | `scripts//cli.mjs` | CLI contract, JSON output shape, fixtures, scoped helpers | Called by skills, hooks, reports, or host adapters | `npm test` or focused `npx vitest run`, fixture assertions, cross-platform command construction |
| Host evidence adapter | Yes | `scripts//platforms/.mjs` and `docs/adapters/README.md` | Matrix row with discovery paths, normalized evidence shape, smoke command, and split trigger if needed | Capability-specific host collection; not packaging | Host matrix entry, smoke command or explicit unavailable note |
| Lifecycle enforcement | Yes, narrowly | `hooks/hooks.json.template` and `hooks/git-scripts//` | Hook event, mode, command contract, expected failure behavior | Installed from the template into host lifecycle points | Hook fixture/test or dry-run output; no shell-specific assumptions |
| Reporting template family | Yes, with routing and runtime tests | `templates/reporting/` | `report-structure.md` owns Markdown structure; mode files own metadata, companion rules, imports/props, and validation | Selected by report generation and output mode | Template tests, parser checks, path checks, and visual preview/smoke when relevant |
diff --git a/docs/specs/2026-08-13-vitest-migration.md b/docs/specs/2026-08-13-vitest-migration.md
new file mode 100644
index 0000000..c60c3d1
--- /dev/null
+++ b/docs/specs/2026-08-13-vitest-migration.md
@@ -0,0 +1,97 @@
+# Make test failures directly visible
+
+## Traceability
+
+- Spec ID: vitest-migration
+- Status: Implemented
+- Supersedes the CI runner/reporting slice of
+ [Make test failures concise and actionable](2026-08-13-test-ci-diagnostics.md).
+
+## Intent
+
+Replace the repository's custom `node:test` CI orchestration with Vitest so a
+failed GitHub Actions step names the failing file and test, prints the useful
+assertion stack in the main log, links an annotation to the source, and retains
+downloadable JUnit evidence. Remove the custom reporting lifecycle that reduced
+successful output but hid the identity of failures behind dots and artifacts.
+
+## Acceptance Scenarios
+
+- AC-1: `npm test` runs the complete `test/**/*.test.mjs` population through
+ Vitest on the declared Node.js versions without requiring tests to adopt
+ Vitest assertions or mocks.
+- AC-2: An intentionally failing isolated test produces a main-log summary
+ containing its relative file, full test name, assertion message, and source
+ location; diagnosing the failure does not require opening an artifact.
+- AC-3: GitHub Actions uses Vitest's built-in default and `github-actions`
+ reporters plus its JUnit reporter, uploads the JUnit file on success or
+ failure, and publishes a test Job Summary.
+- AC-4: The repository no longer owns a custom test scheduler or reporter for
+ ordinary CI execution; `test/support/run-ci.mjs` and
+ `test/support/github-actions-reporter.mjs` are removed.
+- AC-5: Existing Node assertion behavior, dynamic skip behavior, per-test
+ cleanup, suite setup/teardown, filename filtering, and exit codes remain
+ observable after migration.
+- AC-6: CI passes on Linux Node 22.20.0, macOS Node 22.20.0, Windows Node
+ 22.20.0, and Linux Node 24.x from the exact pushed head.
+- AC-7: `npm run pack:verify` continues to exclude test infrastructure and
+ development dependencies from the published runtime artifact.
+
+## Non-goals
+
+- Rewriting Node `assert` calls to Vitest `expect` matchers.
+- Adding coverage thresholds, browser tests, snapshots, or TypeScript test
+ compilation.
+- Changing product behavior or weakening platform-specific tests.
+- Keeping capability-by-capability subprocess scheduling when Vitest can report
+ file and suite ownership directly.
+
+## Plan and Tasks
+
+1. Add the exact stable Vitest version as a development dependency and add
+ local and CI configurations for the existing `.test.mjs` population.
+2. Mechanically replace `node:test` imports with Vitest imports. Map per-test
+ cleanup from `t.after` to `t.onTestFinished`, retain context `skip`, and map
+ suite hooks to `beforeAll` and `afterAll`.
+3. Replace `npm test` and `npm run test:ci` with Vitest run commands. Configure
+ the CI route with `default`, `github-actions`, and JUnit reporters so failures
+ stay human-readable while machine artifacts remain available.
+4. Update the GitHub Actions upload boundary for the single Vitest JUnit file
+ and remove the custom runner and reporter.
+5. Validate one isolated intentional failure, focused migrated tests, the full
+ suite, package verification, documentation links, and the exact-head CI
+ matrix before release.
+
+Decision rationale: Vitest 4.1.10 supports the repository's Node.js range and
+ships the three required reporting surfaces. The test corpus primarily uses
+`node:assert/strict`, so the migration changes lifecycle registration while
+leaving nearly all assertions and fixtures unchanged.
+
+## Test and Review Evidence
+
+- AC-1/AC-5: `npm test` passes all 91 files and 1,305 Vitest cases. The five
+ result-count difference from the 1,310 Node baseline is accounted for by
+ removing four custom-runner component tests and representing the two adapter
+ cases directly instead of counting their former parent subtest. All 91 test
+ files import Vitest, and no `node:test`, `t.after`, or `context.test` route
+ remains. Focused lifecycle coverage passes 79/79 across cleanup, dynamic
+ skip, and filesystem-heavy suites.
+- AC-2: the reporting contract launches Vitest against an intentionally failing
+ temporary fixture and passes only when the captured main log contains the
+ relative file, full test name, Node assertion message, and line 3 location.
+- AC-3: a focused `npm run test:ci` writes and parses
+ `test-results/vitest.junit.xml`. A GitHub Actions environment simulation also
+ writes a non-empty Job Summary with the one-test pass result.
+- AC-4: both custom support modules are absent, and no non-historical reference
+ to either module remains.
+- AC-6: the exact-head four-platform PR matrix remains the merge gate; local
+ evidence does not claim remote Windows, macOS, or Linux status.
+- AC-7: `npm run pack:verify` passes with 508 npm entries and 530 runtime zip
+ entries. Test files, Vitest configuration, and installed development modules
+ remain outside both artifacts.
+- Documentation and installation: `npm ci` succeeds from the lockfile; the
+ regenerated documentation graph contains 39 files and 56 links, and its
+ focused suite passes 6/6. `git diff --check` is clean.
+- Risk: Vitest file parallelism and cleanup timing can differ from `node:test`.
+ The fork pool preserves process isolation, cleanup is migrated explicitly,
+ and the full cross-platform suite remains the release gate.
diff --git a/package-lock.json b/package-lock.json
index 6eb11ec..6d021f0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,17 +15,489 @@
"bin": {
"better-harness": "scripts/better-harness.mjs"
},
+ "devDependencies": {
+ "vitest": "4.1.10"
+ },
"engines": {
"node": ">=22.20.0 <25.0.0",
"npm": ">=10.9.3 <12.0.0"
}
},
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.144.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz",
+ "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz",
+ "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz",
+ "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz",
+ "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz",
+ "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz",
+ "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz",
+ "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz",
+ "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz",
+ "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz",
+ "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz",
+ "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz",
+ "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz",
+ "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
+ "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.10",
+ "@vitest/utils": "4.1.10",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
+ "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.10",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
+ "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
+ "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.10",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
+ "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.10",
+ "@vitest/utils": "4.1.10",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
+ "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
+ "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.10",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/@vscode/tree-sitter-wasm": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.3.1.tgz",
"integrity": "sha512-RJFoomET6FajjG511fmQxeBQfU6M24a0aFZPqpid+ttIxanWf1VGytBG0UmsGjt07qmIPJS8U31D+aecuCucsQ==",
"license": "MIT"
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
+ "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/esbuild-wasm": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.28.1.tgz",
@@ -37,6 +509,724 @@
"engines": {
"node": ">=18"
}
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
+ "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz",
+ "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.144.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.2.4",
+ "@rolldown/binding-darwin-arm64": "1.2.4",
+ "@rolldown/binding-darwin-x64": "1.2.4",
+ "@rolldown/binding-freebsd-x64": "1.2.4",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.4",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.4",
+ "@rolldown/binding-linux-arm64-musl": "1.2.4",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.4",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.4",
+ "@rolldown/binding-linux-x64-gnu": "1.2.4",
+ "@rolldown/binding-linux-x64-musl": "1.2.4",
+ "@rolldown/binding-openharmony-arm64": "1.2.4",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.4",
+ "@rolldown/binding-win32-x64-msvc": "1.2.4"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
+ "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz",
+ "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz",
+ "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
+ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.25",
+ "rolldown": "~1.2.1",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.4.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
+ "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.10",
+ "@vitest/mocker": "4.1.10",
+ "@vitest/pretty-format": "4.1.10",
+ "@vitest/runner": "4.1.10",
+ "@vitest/snapshot": "4.1.10",
+ "@vitest/spy": "4.1.10",
+ "@vitest/utils": "4.1.10",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "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.10",
+ "@vitest/browser-preview": "4.1.10",
+ "@vitest/browser-webdriverio": "4.1.10",
+ "@vitest/coverage-istanbul": "4.1.10",
+ "@vitest/coverage-v8": "4.1.10",
+ "@vitest/ui": "4.1.10",
+ "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/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "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
+ }
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
}
}
}
diff --git a/package.json b/package.json
index b6b800c..51f9920 100644
--- a/package.json
+++ b/package.json
@@ -80,8 +80,8 @@
"prepublishOnly": "npm run check",
"preview": "node scripts/harness-analysis/canvas-preview-server.mjs",
"preview:canvas": "node scripts/harness-analysis/canvas-preview-server.mjs",
- "test": "node --test \"test/**/*.test.mjs\"",
- "test:ci": "node test/support/run-ci.mjs"
+ "test": "vitest run",
+ "test:ci": "vitest run --config vitest.ci.config.mjs"
},
"packageManager": "npm@10.9.3",
"publishConfig": {
@@ -96,5 +96,8 @@
"dependencies": {
"@vscode/tree-sitter-wasm": "0.3.1",
"esbuild-wasm": "0.28.1"
+ },
+ "devDependencies": {
+ "vitest": "4.1.10"
}
}
diff --git a/references/agent-customize/skill-eval.md b/references/agent-customize/skill-eval.md
index a462b19..55001da 100644
--- a/references/agent-customize/skill-eval.md
+++ b/references/agent-customize/skill-eval.md
@@ -71,7 +71,7 @@ never edit the canonical Plugin Eval result to make the tools agree.
For the repository's own `skills/better-harness` target, also run:
```text
-node --test test/skills-docs/better-harness-skill.test.mjs test/skills-docs/doc-link-graph.test.mjs
+npx vitest run test/skills-docs/better-harness-skill.test.mjs test/skills-docs/doc-link-graph.test.mjs
node scripts/doc-link-graph/cli.mjs skills/better-harness
npm run pack:verify
```
diff --git a/test/README.md b/test/README.md
index d4143dd..931127a 100644
--- a/test/README.md
+++ b/test/README.md
@@ -2,9 +2,9 @@
Tests are grouped by the production capability that owns the behavior. Keep a
new test beside its primary owner even when it crosses unit, integration, and
-CLI boundaries. Shared fixtures stay in `test/fixtures`. CI-only reporters and
-the categorized runner stay in `test/support`; they are not published with the
-package and are not discovered as tests.
+CLI boundaries. Shared fixtures stay in `test/fixtures`. Vitest discovers tests
+directly from the capability directories; test support code is not published
+with the package.
## Categories
@@ -32,7 +32,8 @@ Run the complete suite:
npm test
```
-Run the CI presentation locally, including compact progress and JUnit files:
+Run the CI presentation locally, including human-readable failures, GitHub
+Actions reporting when applicable, and the JUnit file:
```sh
npm run test:ci
@@ -41,16 +42,16 @@ npm run test:ci
Run one capability:
```sh
-node --test "test/sessions/*.test.mjs"
-node --test "test/reporting/*.test.mjs"
+npx vitest run test/sessions
+npx vitest run test/reporting
```
Run one file:
```sh
-node --test test/plugins/plugin-lifecycle.test.mjs
+npx vitest run test/plugins/plugin-lifecycle.test.mjs
```
-Do not add a second test manifest. Both local and CI runners discover
-`*.test.mjs` files from the capability directories; this document explains
+Do not add a second test manifest. Both local and CI commands use the same
+Vitest discovery configuration for `*.test.mjs` files; this document explains
ownership and routing.
diff --git a/test/agents/agent-asset-baseline.test.mjs b/test/agents/agent-asset-baseline.test.mjs
index 72385b7..187c743 100644
--- a/test/agents/agent-asset-baseline.test.mjs
+++ b/test/agents/agent-asset-baseline.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
ASSET_BASELINE_KIND,
diff --git a/test/agents/agent-asset-integrity.test.mjs b/test/agents/agent-asset-integrity.test.mjs
index 49e7685..8dde479 100644
--- a/test/agents/agent-asset-integrity.test.mjs
+++ b/test/agents/agent-asset-integrity.test.mjs
@@ -5,7 +5,7 @@ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
-import test from "node:test";
+import { test } from "vitest";
import {
ASSET_INTEGRITY_PROFILE,
diff --git a/test/agents/agent-customize-architecture.test.mjs b/test/agents/agent-customize-architecture.test.mjs
index fc3f850..c67d6b7 100644
--- a/test/agents/agent-customize-architecture.test.mjs
+++ b/test/agents/agent-customize-architecture.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import * as inventory from "../../scripts/agent-customize/inventory.mjs";
import {
diff --git a/test/agents/agent-customize.test.mjs b/test/agents/agent-customize.test.mjs
index 7f96410..6297737 100644
--- a/test/agents/agent-customize.test.mjs
+++ b/test/agents/agent-customize.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, realpath, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
collectAgentCustomizeInventory,
diff --git a/test/agents/agent-guardrails-secret-scan.test.mjs b/test/agents/agent-guardrails-secret-scan.test.mjs
index 6eda764..9938823 100644
--- a/test/agents/agent-guardrails-secret-scan.test.mjs
+++ b/test/agents/agent-guardrails-secret-scan.test.mjs
@@ -4,7 +4,7 @@ import { promises as fsPromises } from "node:fs";
import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
handleHookEvent,
@@ -158,7 +158,7 @@ test("workspace-contained scanning refuses a swap to an external symlink at read
const outsidePath = path.join(root, "outside.yaml");
const probePath = path.join(workspace, "symlink-probe");
const outsideKey = syntheticOpenAiKey();
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
await mkdir(workspace, { recursive: true });
await writeFile(configPath, "name: safe\n");
await writeFile(outsidePath, `OPENAI_API_KEY=${outsideKey}\n`);
@@ -261,7 +261,7 @@ test("secret-scan CLI hook mode reads JSON stdin and emits a block payload", ()
test("secret-scan CLI fails closed when an explicit path cannot be scanned", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "better-harness-secret-scan-missing-"));
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
const result = spawnSync(process.execPath, [scannerCli, "missing.txt", "--json"], {
cwd: root,
encoding: "utf8",
@@ -276,7 +276,7 @@ test("secret-scan CLI fails closed when an explicit path cannot be scanned", asy
test("secret-scan CLI reports complete coverage and exits zero for a clean explicit file", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "better-harness-secret-scan-clean-"));
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
await writeFile(path.join(root, "clean.txt"), "ordinary fixture text\n");
const result = spawnSync(process.execPath, [scannerCli, "clean.txt", "--json"], {
cwd: root,
@@ -295,7 +295,7 @@ test("secret-scan CLI treats an explicitly supplied symbolic-link file as incomp
const root = await mkdtemp(path.join(os.tmpdir(), "better-harness-secret-scan-link-"));
const target = path.join(root, "target.txt");
const link = path.join(root, "linked.txt");
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
await writeFile(target, "safe fixture\n");
try {
await symlink(target, link);
@@ -320,7 +320,7 @@ test("secret-scan CLI treats symbolic links nested in a scanned directory as inc
const scanRoot = path.join(root, "scan");
const target = path.join(root, "target.txt");
const link = path.join(scanRoot, "linked.txt");
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
await mkdir(scanRoot, { recursive: true });
await writeFile(target, "safe fixture\n");
try {
diff --git a/test/agents/agent-lint-monorepo.test.mjs b/test/agents/agent-lint-monorepo.test.mjs
index 9ceacb8..b1c0500 100644
--- a/test/agents/agent-lint-monorepo.test.mjs
+++ b/test/agents/agent-lint-monorepo.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
collectAgentInstructionGraph,
diff --git a/test/agents/agent-lint.test.mjs b/test/agents/agent-lint.test.mjs
index acd72b2..c2a6540 100644
--- a/test/agents/agent-lint.test.mjs
+++ b/test/agents/agent-lint.test.mjs
@@ -4,7 +4,7 @@ import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from "node:fs/promis
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
-import test from "node:test";
+import { test } from "vitest";
import {
collectAgentInstructionGraph,
diff --git a/test/agents/coding-agent-asset-eval.test.mjs b/test/agents/coding-agent-asset-eval.test.mjs
index 18a1fb7..866b6f0 100644
--- a/test/agents/coding-agent-asset-eval.test.mjs
+++ b/test/agents/coding-agent-asset-eval.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
attachAssetEvaluations,
diff --git a/test/agents/coding-agent-practices-inventory.test.mjs b/test/agents/coding-agent-practices-inventory.test.mjs
index 7bf9c40..1eb0896 100644
--- a/test/agents/coding-agent-practices-inventory.test.mjs
+++ b/test/agents/coding-agent-practices-inventory.test.mjs
@@ -4,7 +4,7 @@ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
-import test from "node:test";
+import { test } from "vitest";
import { collectQoderInventory } from "../../scripts/coding-agent-practices/inventory.mjs";
diff --git a/test/agents/harness-checkup.test.mjs b/test/agents/harness-checkup.test.mjs
index 6d02c53..8b4ff3b 100644
--- a/test/agents/harness-checkup.test.mjs
+++ b/test/agents/harness-checkup.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
applySourcePatch,
@@ -670,7 +670,7 @@ test("source patch rejects symbolic links in the target path", async (t) => {
"workspace:linked/settings.json": sha256(original),
},
};
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
await mkdir(workspace, { recursive: true });
await mkdir(outside, { recursive: true });
await writeFile(path.join(outside, "settings.json"), original);
@@ -709,7 +709,7 @@ test("source patch rejects a symbolic-link backup root", async (t) => {
"workspace:settings.json": sha256(original),
},
};
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
await mkdir(workspace, { recursive: true });
await mkdir(outside, { recursive: true });
await writeFile(sourcePath, original);
diff --git a/test/cli/better-harness-cli.test.mjs b/test/cli/better-harness-cli.test.mjs
index a596454..fa95009 100644
--- a/test/cli/better-harness-cli.test.mjs
+++ b/test/cli/better-harness-cli.test.mjs
@@ -3,7 +3,7 @@ import { spawn, spawnSync } from "node:child_process";
import { mkdtemp, mkdir, realpath, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { pathToFileURL } from "node:url";
import { main, resolveDispatch } from "../../scripts/better-harness-cli/cli.mjs";
import { commandInventory } from "../../scripts/better-harness-cli/registry.mjs";
@@ -1109,7 +1109,7 @@ test("cloc CLI runs from a spaced symlink installation path", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "better-harness cloc install-"));
const linkedClocDir = path.join(root, "linked cloc");
const workspace = path.join(root, "workspace with spaces");
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
await writeFixtureFile(workspace, "src/app.mjs", "export const value = 1;\n");
try {
await symlink(
diff --git a/test/cli/harness-doctor.test.mjs b/test/cli/harness-doctor.test.mjs
index 3dacf96..78d7b3a 100644
--- a/test/cli/harness-doctor.test.mjs
+++ b/test/cli/harness-doctor.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { chmod, mkdir, mkdtemp, rm, unlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { renderDoctor } from "../../scripts/harness-doctor/cli.mjs";
import { runHarnessDoctor } from "../../scripts/harness-doctor/index.mjs";
diff --git a/test/cli/harness-quickstart.test.mjs b/test/cli/harness-quickstart.test.mjs
index 280f701..2bba2d5 100644
--- a/test/cli/harness-quickstart.test.mjs
+++ b/test/cli/harness-quickstart.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { workspaceToQoderSlug } from "../../scripts/session-analysis/platforms/qoder.mjs";
diff --git a/test/cli/read-only-command.test.mjs b/test/cli/read-only-command.test.mjs
index 7492054..adc456a 100644
--- a/test/cli/read-only-command.test.mjs
+++ b/test/cli/read-only-command.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
LifecycleCliError,
diff --git a/test/cli/terminal-demo.test.mjs b/test/cli/terminal-demo.test.mjs
index 1491226..3e3ed82 100644
--- a/test/cli/terminal-demo.test.mjs
+++ b/test/cli/terminal-demo.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
discoverHistoryRoots,
diff --git a/test/governance/ai-contribution-census.test.mjs b/test/governance/ai-contribution-census.test.mjs
index f24a4fb..4fa5303 100644
--- a/test/governance/ai-contribution-census.test.mjs
+++ b/test/governance/ai-contribution-census.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import { after, before, describe, test } from "node:test";
+import { afterAll, beforeAll, describe, test } from "vitest";
import {
analyzeAiContributionCensus,
@@ -116,11 +116,11 @@ async function makeFixtureRepo() {
describe("AI contribution census", { concurrency: false }, () => {
let repo;
- before(async () => {
+ beforeAll(async () => {
repo = await makeFixtureRepo();
});
- after(async () => {
+ afterAll(async () => {
await rm(repo, { recursive: true, force: true });
});
diff --git a/test/governance/blast-radius.test.mjs b/test/governance/blast-radius.test.mjs
index 4d0573a..d8aff67 100644
--- a/test/governance/blast-radius.test.mjs
+++ b/test/governance/blast-radius.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, readFile, rm, writeFile, mkdir } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
analyzeRepository,
diff --git a/test/governance/cloc.test.mjs b/test/governance/cloc.test.mjs
index 078774a..e72cc5d 100644
--- a/test/governance/cloc.test.mjs
+++ b/test/governance/cloc.test.mjs
@@ -4,7 +4,7 @@ import { existsSync } from "node:fs";
import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { analyzeCloc } from "../../scripts/cloc/analyze.mjs";
import { countFile, countFilesSummary } from "../../scripts/cloc/count-file.mjs";
diff --git a/test/governance/core-change-watch-scope.test.mjs b/test/governance/core-change-watch-scope.test.mjs
index 36870ae..29b439f 100644
--- a/test/governance/core-change-watch-scope.test.mjs
+++ b/test/governance/core-change-watch-scope.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
assertCompatibleAnalysisScope,
diff --git a/test/governance/core-change-watch.test.mjs b/test/governance/core-change-watch.test.mjs
index bffb5ab..d9b1f4e 100644
--- a/test/governance/core-change-watch.test.mjs
+++ b/test/governance/core-change-watch.test.mjs
@@ -4,7 +4,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { fileRoleFor } from "../../scripts/core-change-watch/common.mjs";
import { analyzeChangeDrift } from "../../scripts/core-change-watch/change-drift.mjs";
diff --git a/test/governance/dependency-governance.test.mjs b/test/governance/dependency-governance.test.mjs
index ae544fa..d113365 100644
--- a/test/governance/dependency-governance.test.mjs
+++ b/test/governance/dependency-governance.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
analyzeDependencyGovernance,
diff --git a/test/governance/review-trigger.test.mjs b/test/governance/review-trigger.test.mjs
index 91eb767..b0a2169 100644
--- a/test/governance/review-trigger.test.mjs
+++ b/test/governance/review-trigger.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
buildChangeTestEvidenceFindings,
diff --git a/test/governance/scripts-refactor-contract.test.mjs b/test/governance/scripts-refactor-contract.test.mjs
index b871a3d..e7feb70 100644
--- a/test/governance/scripts-refactor-contract.test.mjs
+++ b/test/governance/scripts-refactor-contract.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
const ROOT = process.cwd();
const CLI = path.join(ROOT, "scripts", "better-harness.mjs");
diff --git a/test/governance/test-ci-reporting.test.mjs b/test/governance/test-ci-reporting.test.mjs
deleted file mode 100644
index 0e1e488..0000000
--- a/test/governance/test-ci-reporting.test.mjs
+++ /dev/null
@@ -1,126 +0,0 @@
-import assert from "node:assert/strict";
-import { spawnSync } from "node:child_process";
-import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
-import os from "node:os";
-import path from "node:path";
-import test from "node:test";
-import { fileURLToPath } from "node:url";
-
-import { githubFailureAnnotation } from "../support/github-actions-reporter.mjs";
-import { discoverTestCategories, nodeTestFileArgument, runCiTests } from "../support/run-ci.mjs";
-
-const REPORTER_PATH = fileURLToPath(new URL("../support/github-actions-reporter.mjs", import.meta.url));
-
-test("CI test discovery finds capability tests without treating support or fixtures as categories", async () => {
- const root = await mkdtemp(path.join(os.tmpdir(), "better-harness-test-discovery-"));
- try {
- await mkdir(path.join(root, "sessions"), { recursive: true });
- await mkdir(path.join(root, "support"), { recursive: true });
- await mkdir(path.join(root, "fixtures"), { recursive: true });
- await writeFile(path.join(root, "sessions", "usage.test.mjs"), "export {};\n");
- await writeFile(path.join(root, "support", "runner.mjs"), "export {};\n");
- await writeFile(path.join(root, "fixtures", "helper.mjs"), "export {};\n");
-
- assert.deepEqual(await discoverTestCategories(root), [{
- name: "sessions",
- files: [path.join(root, "sessions", "usage.test.mjs")],
- }]);
- } finally {
- await rm(root, { recursive: true, force: true });
- }
-});
-
-test("CI test files use portable relative arguments on Windows", () => {
- assert.equal(nodeTestFileArgument(
- "D:\\a\\better-harness\\better-harness\\test\\agents\\example.test.mjs",
- {
- cwd: "D:\\a\\better-harness\\better-harness",
- pathApi: path.win32,
- },
- ), "./test/agents/example.test.mjs");
-});
-
-test("GitHub failure annotations retain location and escape command data", () => {
- const annotation = githubFailureAnnotation({
- name: "rejects bad, input: safely",
- file: path.join(process.cwd(), "test", "governance", "example.test.mjs"),
- line: 17,
- column: 4,
- details: { error: new Error("first line\nsecond 100%") },
- });
-
- assert.ok(annotation.startsWith("::error file=test/governance/example.test.mjs,line=17,col=4,"));
- assert.ok(annotation.includes("title=Test failed%3A rejects bad%2C input%3A safely"));
- assert.ok(annotation.includes("first line%0Asecond 100%25"));
-});
-
-test("the custom reporter turns a real Node test failure into one GitHub annotation", async () => {
- const root = await mkdtemp(path.join(os.tmpdir(), "better-harness-test-reporter-"));
- const failingTest = path.join(root, "failure.test.mjs");
- try {
- await writeFile(failingTest, [
- 'import assert from "node:assert/strict";',
- 'import test from "node:test";',
- 'test("example reporter failure", () => assert.equal(1, 2));',
- "",
- ].join("\n"));
- const childEnv = { ...process.env, GITHUB_ACTIONS: "true" };
- delete childEnv.NODE_TEST_CONTEXT;
- const result = spawnSync(process.execPath, [
- "--test",
- `--test-reporter=${REPORTER_PATH}`,
- "--test-reporter-destination=stdout",
- failingTest,
- ], {
- cwd: root,
- encoding: "utf8",
- env: childEnv,
- });
-
- assert.equal(result.status, 1, `${result.stdout}\n${result.stderr}`);
- assert.equal(result.stdout.split("::error ").length - 1, 1, `${result.stdout}\n${result.stderr}`);
- assert.ok(result.stdout.includes("title=Test failed%3A example reporter failure"));
- assert.ok(result.stdout.includes("Expected values to be strictly equal"));
- } finally {
- await rm(root, { recursive: true, force: true });
- }
-});
-
-test("the CI runner continues after a failed capability and summarizes every result", async () => {
- const root = await mkdtemp(path.join(os.tmpdir(), "better-harness-test-runner-"));
- const resultsRoot = path.join(root, "results");
- const summaryPath = path.join(root, "summary.md");
- try {
- await mkdir(path.join(root, "alpha"));
- await mkdir(path.join(root, "omega"));
- await writeFile(path.join(root, "alpha", "failure.test.mjs"), [
- 'import assert from "node:assert/strict";',
- 'import test from "node:test";',
- 'test("intentional failure", () => assert.equal(1, 2));',
- "",
- ].join("\n"));
- await writeFile(path.join(root, "omega", "success.test.mjs"), [
- 'import test from "node:test";',
- 'test("success after failure", () => {});',
- "",
- ].join("\n"));
-
- const status = await runCiTests([], {
- cwd: root,
- env: { ...process.env, GITHUB_ACTIONS: "false", GITHUB_STEP_SUMMARY: summaryPath },
- resultsRoot,
- stderr: { write: () => true },
- stdio: "pipe",
- stdout: { write: () => true },
- testRoot: root,
- });
-
- assert.equal(status, 1);
- assert.deepEqual((await readdir(resultsRoot)).sort(), ["alpha.xml", "omega.xml"]);
- const summary = await readFile(summaryPath, "utf8");
- assert.ok(summary.includes("| alpha | 1 | fail |"));
- assert.ok(summary.includes("| omega | 1 | pass |"));
- } finally {
- await rm(root, { recursive: true, force: true });
- }
-});
diff --git a/test/governance/test-mapping.test.mjs b/test/governance/test-mapping.test.mjs
index 6ec0e4b..fc540e0 100644
--- a/test/governance/test-mapping.test.mjs
+++ b/test/governance/test-mapping.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { analyzeTestMappings, runHook } from "../../hooks/git-scripts/mapping-gate.mjs";
diff --git a/test/governance/vitest-reporting.test.mjs b/test/governance/vitest-reporting.test.mjs
new file mode 100644
index 0000000..5bcb899
--- /dev/null
+++ b/test/governance/vitest-reporting.test.mjs
@@ -0,0 +1,54 @@
+import assert from "node:assert/strict";
+import { spawnSync } from "node:child_process";
+import { randomUUID } from "node:crypto";
+import { rm, writeFile } from "node:fs/promises";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { test } from "vitest";
+
+const ROOT = fileURLToPath(new URL("../../", import.meta.url));
+const VITEST_CLI_PATH = fileURLToPath(new URL("../../node_modules/vitest/vitest.mjs", import.meta.url));
+
+test("Vitest's default reporter identifies an intentional failure in the main log", async () => {
+ const fixtureName = `.vitest-reporting-${randomUUID()}.test.mjs`;
+ const fixturePath = path.join(ROOT, "test", "governance", fixtureName);
+ const relativeFixture = path.relative(ROOT, fixturePath).split(path.sep).join("/");
+
+ try {
+ await writeFile(fixturePath, [
+ 'import assert from "node:assert/strict";',
+ 'import { test } from "vitest";',
+ 'test("intentional visible failure", () => assert.equal(1, 2));',
+ "",
+ ].join("\n"));
+
+ const childEnv = {
+ ...process.env,
+ FORCE_COLOR: "0",
+ NO_COLOR: "1",
+ };
+ for (const key of ["VITEST", "VITEST_POOL_ID", "VITEST_WORKER_ID"]) {
+ delete childEnv[key];
+ }
+
+ const result = spawnSync(process.execPath, [
+ VITEST_CLI_PATH,
+ "run",
+ relativeFixture,
+ "--reporter=default",
+ ], {
+ cwd: ROOT,
+ encoding: "utf8",
+ env: childEnv,
+ });
+ const output = `${result.stdout}\n${result.stderr}`;
+
+ assert.equal(result.status, 1, output);
+ assert.ok(output.includes(relativeFixture), output);
+ assert.ok(output.includes("intentional visible failure"), output);
+ assert.ok(output.includes("Expected values to be strictly equal"), output);
+ assert.ok(output.includes(`${fixtureName}:3:`), output);
+ } finally {
+ await rm(fixturePath, { force: true });
+ }
+});
diff --git a/test/governance/workflow-demand-diagnostics.test.mjs b/test/governance/workflow-demand-diagnostics.test.mjs
index bfd002b..1a6c62b 100644
--- a/test/governance/workflow-demand-diagnostics.test.mjs
+++ b/test/governance/workflow-demand-diagnostics.test.mjs
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
-import test from "node:test";
+import { test } from "vitest";
import {
WORKFLOW_DEMAND_BUILT_IN_CAPABILITIES,
diff --git a/test/governance/workspace-topology.test.mjs b/test/governance/workspace-topology.test.mjs
index 39d5a1f..ba456c7 100644
--- a/test/governance/workspace-topology.test.mjs
+++ b/test/governance/workspace-topology.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, realpath, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { main as topologyMain, parseArgs as parseTopologyArgs } from "../../scripts/workspace-topology/cli.mjs";
import { pathIdentityKey } from "../../scripts/workspace-topology/contract.mjs";
diff --git a/test/learning/intervention-ledger.test.mjs b/test/learning/intervention-ledger.test.mjs
index a12fe33..c989840 100644
--- a/test/learning/intervention-ledger.test.mjs
+++ b/test/learning/intervention-ledger.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
projectInterventionLedger,
diff --git a/test/learning/learning-capture-evidence.test.mjs b/test/learning/learning-capture-evidence.test.mjs
index 3b1ddf8..1dc15e7 100644
--- a/test/learning/learning-capture-evidence.test.mjs
+++ b/test/learning/learning-capture-evidence.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
buildLearningCaptureEvidence,
diff --git a/test/learning/learning-capture-state.test.mjs b/test/learning/learning-capture-state.test.mjs
index f220665..aa28052 100644
--- a/test/learning/learning-capture-state.test.mjs
+++ b/test/learning/learning-capture-state.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdir, mkdtemp, utimes, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { loadPriorLearningCaptureState } from "../../scripts/harness-analysis/learning-capture-state.mjs";
import { projectInterventionLedger } from "../../scripts/harness-analysis/intervention-ledger.mjs";
diff --git a/test/learning/learning-loop-contract.test.mjs b/test/learning/learning-loop-contract.test.mjs
index bb10dea..0420509 100644
--- a/test/learning/learning-loop-contract.test.mjs
+++ b/test/learning/learning-loop-contract.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
LEARNING_LOOP_CHECK_IDS,
diff --git a/test/learning/learning-loop-review.test.mjs b/test/learning/learning-loop-review.test.mjs
index 41e1ea2..1d99b1e 100644
--- a/test/learning/learning-loop-review.test.mjs
+++ b/test/learning/learning-loop-review.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
buildLearningLoopReview,
diff --git a/test/learning/lifecycle-demand-signals.test.mjs b/test/learning/lifecycle-demand-signals.test.mjs
index 2696623..1c94a56 100644
--- a/test/learning/lifecycle-demand-signals.test.mjs
+++ b/test/learning/lifecycle-demand-signals.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { buildTaskEpisodes } from "../../scripts/session-analysis/episode-contract.mjs";
import {
diff --git a/test/learning/native-learning-candidate-review.test.mjs b/test/learning/native-learning-candidate-review.test.mjs
index f7d42f0..f655eed 100644
--- a/test/learning/native-learning-candidate-review.test.mjs
+++ b/test/learning/native-learning-candidate-review.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
applyNativeLearningCandidateReview,
diff --git a/test/plugins/host-plugin-artifact.test.mjs b/test/plugins/host-plugin-artifact.test.mjs
index 8ec4373..b4b8db1 100644
--- a/test/plugins/host-plugin-artifact.test.mjs
+++ b/test/plugins/host-plugin-artifact.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
artifactPackage,
diff --git a/test/plugins/host-support-profile-model.test.mjs b/test/plugins/host-support-profile-model.test.mjs
index c14bca5..e1b8ea1 100644
--- a/test/plugins/host-support-profile-model.test.mjs
+++ b/test/plugins/host-support-profile-model.test.mjs
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
-import test from "node:test";
+import { test } from "vitest";
import {
defineHostProfile,
diff --git a/test/plugins/host-support-profiles.test.mjs b/test/plugins/host-support-profiles.test.mjs
index 6059339..1b0dd0e 100644
--- a/test/plugins/host-support-profiles.test.mjs
+++ b/test/plugins/host-support-profiles.test.mjs
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { readFileSync, readdirSync } from "node:fs";
-import test from "node:test";
+import { test } from "vitest";
import {
getHostProfile,
diff --git a/test/plugins/host-support.test.mjs b/test/plugins/host-support.test.mjs
index eda7e46..d7b063f 100644
--- a/test/plugins/host-support.test.mjs
+++ b/test/plugins/host-support.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtempSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
getHostDescriptor,
@@ -113,7 +113,7 @@ test("selection profiles route every declared host home through the generic anal
test("asset-integrity CLI accepts every declared asset host and rejects unknown hosts", (t) => {
const isolatedRoot = mkdtempSync(path.join(os.tmpdir(), "better-harness-host-gate-"));
- t.after(() => rmSync(isolatedRoot, { recursive: true, force: true }));
+ t.onTestFinished(() => rmSync(isolatedRoot, { recursive: true, force: true }));
const cliPath = path.join(process.cwd(), "scripts", "better-harness.mjs");
for (const hostId of hostIdsFor(HOST_CAPABILITIES.ASSET_PRACTICES)) {
const host = getHostDescriptor(hostId);
diff --git a/test/plugins/plugin-lifecycle-architecture.test.mjs b/test/plugins/plugin-lifecycle-architecture.test.mjs
index 98385aa..9e1cced 100644
--- a/test/plugins/plugin-lifecycle-architecture.test.mjs
+++ b/test/plugins/plugin-lifecycle-architecture.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import * as lifecycle from "../../scripts/plugin-lifecycle/index.mjs";
diff --git a/test/plugins/plugin-lifecycle-command-definitions.test.mjs b/test/plugins/plugin-lifecycle-command-definitions.test.mjs
index 917c108..171cce0 100644
--- a/test/plugins/plugin-lifecycle-command-definitions.test.mjs
+++ b/test/plugins/plugin-lifecycle-command-definitions.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { groupCommand } from "../../scripts/better-harness-cli/registry.mjs";
import {
diff --git a/test/plugins/plugin-lifecycle-model.test.mjs b/test/plugins/plugin-lifecycle-model.test.mjs
index 6c3002b..d3ed74f 100644
--- a/test/plugins/plugin-lifecycle-model.test.mjs
+++ b/test/plugins/plugin-lifecycle-model.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
assertLifecycleControlled,
diff --git a/test/plugins/plugin-lifecycle-plan-model.test.mjs b/test/plugins/plugin-lifecycle-plan-model.test.mjs
index 3b4eaf0..cda41a2 100644
--- a/test/plugins/plugin-lifecycle-plan-model.test.mjs
+++ b/test/plugins/plugin-lifecycle-plan-model.test.mjs
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { listHostProfiles } from "../../scripts/host-support/index.mjs";
import { buildPluginLifecyclePlan } from "../../scripts/plugin-lifecycle/index.mjs";
diff --git a/test/plugins/plugin-lifecycle-status-row.test.mjs b/test/plugins/plugin-lifecycle-status-row.test.mjs
index 5208a79..e3fd33a 100644
--- a/test/plugins/plugin-lifecycle-status-row.test.mjs
+++ b/test/plugins/plugin-lifecycle-status-row.test.mjs
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { getHostProfile, getHostSurface } from "../../scripts/host-support/index.mjs";
import { inspectPluginLifecycle } from "../../scripts/plugin-lifecycle/index.mjs";
diff --git a/test/plugins/plugin-lifecycle-target-resolution.test.mjs b/test/plugins/plugin-lifecycle-target-resolution.test.mjs
index ab9b304..c9d7fcd 100644
--- a/test/plugins/plugin-lifecycle-target-resolution.test.mjs
+++ b/test/plugins/plugin-lifecycle-target-resolution.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
resolvePlanTarget,
diff --git a/test/plugins/plugin-lifecycle.test.mjs b/test/plugins/plugin-lifecycle.test.mjs
index adebdc2..8852ae2 100644
--- a/test/plugins/plugin-lifecycle.test.mjs
+++ b/test/plugins/plugin-lifecycle.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdir, mkdtemp, readFile, readdir, readlink, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { getHostProfile } from "../../scripts/host-support/index.mjs";
import { runHarnessDoctor } from "../../scripts/harness-doctor/index.mjs";
diff --git a/test/plugins/plugin-manifests.test.mjs b/test/plugins/plugin-manifests.test.mjs
index aed8081..de3a714 100644
--- a/test/plugins/plugin-manifests.test.mjs
+++ b/test/plugins/plugin-manifests.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { existsSync, readFileSync, statSync } from "node:fs";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
const ROOT = process.cwd();
diff --git a/test/reporting/better-harness-evidence-bundle.test.mjs b/test/reporting/better-harness-evidence-bundle.test.mjs
index 42ca35e..f0032e0 100644
--- a/test/reporting/better-harness-evidence-bundle.test.mjs
+++ b/test/reporting/better-harness-evidence-bundle.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdir, mkdtemp, realpath, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
EVIDENCE_BUNDLE_KIND,
diff --git a/test/reporting/cursor-canvas-report.test.mjs b/test/reporting/cursor-canvas-report.test.mjs
index aac5883..716a68d 100644
--- a/test/reporting/cursor-canvas-report.test.mjs
+++ b/test/reporting/cursor-canvas-report.test.mjs
@@ -4,7 +4,7 @@ import { readFileSync } from "node:fs";
import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { renderCursorCanvasTsx } from "../../scripts/harness-analysis/renderers/cursor-canvas.mjs";
import {
diff --git a/test/reporting/episode-evidence-review.test.mjs b/test/reporting/episode-evidence-review.test.mjs
index 2af71e4..4d18578 100644
--- a/test/reporting/episode-evidence-review.test.mjs
+++ b/test/reporting/episode-evidence-review.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
applyEpisodeReviews,
diff --git a/test/reporting/findings-recommend.test.mjs b/test/reporting/findings-recommend.test.mjs
index 410e3de..ba380f9 100644
--- a/test/reporting/findings-recommend.test.mjs
+++ b/test/reporting/findings-recommend.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
enrichFindingWithRecommendation,
diff --git a/test/reporting/harness-canvas-validation.test.mjs b/test/reporting/harness-canvas-validation.test.mjs
index f6b05c0..fbf297a 100644
--- a/test/reporting/harness-canvas-validation.test.mjs
+++ b/test/reporting/harness-canvas-validation.test.mjs
@@ -3,7 +3,7 @@ import { execFile } from "node:child_process";
import { cp, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { promisify } from "node:util";
import { analyzeCanvasModuleBoundaries } from "../../scripts/harness-analysis/canvas-module-boundaries.mjs";
diff --git a/test/reporting/harness-component-snapshot-cli.test.mjs b/test/reporting/harness-component-snapshot-cli.test.mjs
index 97e3f0a..c12d8e0 100644
--- a/test/reporting/harness-component-snapshot-cli.test.mjs
+++ b/test/reporting/harness-component-snapshot-cli.test.mjs
@@ -3,7 +3,7 @@ import { cp, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
-import test from "node:test";
+import { test } from "vitest";
const ROOT = process.cwd();
const CLI = path.join(ROOT, "scripts", "harness-component-snapshot", "cli.mjs");
@@ -13,7 +13,7 @@ async function fixtureWorkspace(t) {
const root = await mkdtemp(path.join(os.tmpdir(), "better-harness-component-cli-"));
const workspace = path.join(root, "workspace");
await cp(FIXTURE, workspace, { recursive: true });
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
return { root, workspace };
}
diff --git a/test/reporting/harness-component-snapshot.test.mjs b/test/reporting/harness-component-snapshot.test.mjs
index de200ec..9e6e971 100644
--- a/test/reporting/harness-component-snapshot.test.mjs
+++ b/test/reporting/harness-component-snapshot.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { cp, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { collectAgentCustomizeInventory } from "../../scripts/agent-customize/index.mjs";
import {
@@ -28,7 +28,7 @@ async function fixtureWorkspace(t) {
const root = await mkdtemp(path.join(os.tmpdir(), "better-harness-component-snapshot-"));
const workspace = path.join(root, "workspace");
await cp(FIXTURE, workspace, { recursive: true });
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
return workspace;
}
@@ -228,7 +228,7 @@ test("population references reject cross-project reuse and preserve explicit rel
cp(FIXTURE, firstWorkspace, { recursive: true }),
cp(FIXTURE, secondWorkspace, { recursive: true }),
]);
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
const first = await createHarnessComponentSnapshot({ workspace: firstWorkspace });
const second = await createHarnessComponentSnapshot({ workspace: secondWorkspace });
@@ -255,7 +255,7 @@ test("workspace evidence roots are scoped to each snapshot assembly", async (t)
cp(FIXTURE, secondWorkspace, { recursive: true }),
]);
await writeFile(path.join(secondWorkspace, ".qoder", "workflows", "review.yml"), "name: changed\n", "utf8");
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
try {
await symlink(firstWorkspace, workspaceAlias, process.platform === "win32" ? "junction" : "dir");
@@ -330,7 +330,7 @@ test("workspace scope rejects user-home aliases and regular files", async (t) =>
const workspace = await fixtureWorkspace(t);
const previousQoderHome = process.env.QODER_HOME;
process.env.QODER_HOME = path.join(workspace, ".qoder");
- t.after(() => {
+ t.onTestFinished(() => {
if (previousQoderHome === undefined) delete process.env.QODER_HOME;
else process.env.QODER_HOME = previousQoderHome;
});
@@ -359,7 +359,7 @@ test("tilde QODER_HOME aliases cannot masquerade as project scope", async (t) =>
const originalHomedir = os.homedir;
process.env.QODER_HOME = "~/.qoder";
os.homedir = () => root;
- t.after(async () => {
+ t.onTestFinished(async () => {
os.homedir = originalHomedir;
if (previousQoderHome === undefined) delete process.env.QODER_HOME;
else process.env.QODER_HOME = previousQoderHome;
diff --git a/test/reporting/harness-findings-repair.test.mjs b/test/reporting/harness-findings-repair.test.mjs
index d5500ad..5aed5f1 100644
--- a/test/reporting/harness-findings-repair.test.mjs
+++ b/test/reporting/harness-findings-repair.test.mjs
@@ -3,7 +3,7 @@ import { execFile } from "node:child_process";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { promisify } from "node:util";
import { repairFindingsJsonData } from "../../scripts/harness-analysis/repair-findings-json.mjs";
diff --git a/test/reporting/harness-inspector.test.mjs b/test/reporting/harness-inspector.test.mjs
index b2b3198..d3d4a45 100644
--- a/test/reporting/harness-inspector.test.mjs
+++ b/test/reporting/harness-inspector.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { fileURLToPath } from "node:url";
import {
diff --git a/test/reporting/harness-memory-review.test.mjs b/test/reporting/harness-memory-review.test.mjs
index 77f655a..6e3fca6 100644
--- a/test/reporting/harness-memory-review.test.mjs
+++ b/test/reporting/harness-memory-review.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { validateHarnessReportSource } from "../../scripts/harness-analysis/report-source.mjs";
import {
diff --git a/test/reporting/harness-report-quality.test.mjs b/test/reporting/harness-report-quality.test.mjs
index 01c4c54..f15ef95 100644
--- a/test/reporting/harness-report-quality.test.mjs
+++ b/test/reporting/harness-report-quality.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
evaluateHarnessCanvasQuality,
diff --git a/test/reporting/harness-report-render-cli.test.mjs b/test/reporting/harness-report-render-cli.test.mjs
index c568f7f..ae33372 100644
--- a/test/reporting/harness-report-render-cli.test.mjs
+++ b/test/reporting/harness-report-render-cli.test.mjs
@@ -4,7 +4,7 @@ import { existsSync, readFileSync } from "node:fs";
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { evaluateHtmlReport, renderHtml } from "../../scripts/harness-analysis/renderers/html.mjs";
import { RENDER_REPORT_PLATFORMS, renderReport } from "../../scripts/harness-analysis/render-report.mjs";
diff --git a/test/reporting/harness-report-run.test.mjs b/test/reporting/harness-report-run.test.mjs
index cce04b2..e8b4ca4 100644
--- a/test/reporting/harness-report-run.test.mjs
+++ b/test/reporting/harness-report-run.test.mjs
@@ -4,7 +4,7 @@ import { readFileSync } from "node:fs";
import { mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
ANALYZE_EVIDENCE_TOKEN_BUDGET,
diff --git a/test/reporting/harness-report-source.test.mjs b/test/reporting/harness-report-source.test.mjs
index 028953a..728469a 100644
--- a/test/reporting/harness-report-source.test.mjs
+++ b/test/reporting/harness-report-source.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { createHarnessReportSource, validateHarnessReportSource } from "../../scripts/harness-analysis/report-source.mjs";
import { buildObservationManifest } from "../../scripts/session-analysis/observation-manifest.mjs";
diff --git a/test/reporting/html-report-interactions.test.mjs b/test/reporting/html-report-interactions.test.mjs
index 002d1ca..4e02d03 100644
--- a/test/reporting/html-report-interactions.test.mjs
+++ b/test/reporting/html-report-interactions.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
installHtmlReportInteractions,
diff --git a/test/reporting/inspector-cross-provider.test.mjs b/test/reporting/inspector-cross-provider.test.mjs
index b53f555..bc5d36f 100644
--- a/test/reporting/inspector-cross-provider.test.mjs
+++ b/test/reporting/inspector-cross-provider.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import os from "node:os";
-import test from "node:test";
+import { test } from "vitest";
import { fileURLToPath } from "node:url";
import { collectMultiPlatformSessionSummaries } from "../../scripts/commit-session-link/index.mjs";
diff --git a/test/reporting/maturity-models.test.mjs b/test/reporting/maturity-models.test.mjs
index 8e6689b..8a4f12a 100644
--- a/test/reporting/maturity-models.test.mjs
+++ b/test/reporting/maturity-models.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
AGENT_WORK_LOOP_DIMENSIONS,
diff --git a/test/reporting/practice-findings.test.mjs b/test/reporting/practice-findings.test.mjs
index 2941a6e..9a944c0 100644
--- a/test/reporting/practice-findings.test.mjs
+++ b/test/reporting/practice-findings.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { projectAgentLintPracticeEvidence } from "../../scripts/harness-analysis/practice-findings.mjs";
import { collectAgentLintPracticeEvidence } from "../../scripts/harness-analysis/task-loop-source.mjs";
diff --git a/test/reporting/preview-servers.test.mjs b/test/reporting/preview-servers.test.mjs
index 68b9232..f93f2e7 100644
--- a/test/reporting/preview-servers.test.mjs
+++ b/test/reporting/preview-servers.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
canvasMediaCandidatesForApplication,
diff --git a/test/reporting/report-source-review.test.mjs b/test/reporting/report-source-review.test.mjs
index 5e8eebb..7e3d740 100644
--- a/test/reporting/report-source-review.test.mjs
+++ b/test/reporting/report-source-review.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { applyReportSourceReview } from "../../scripts/harness-analysis/apply-source-review.mjs";
import { buildHarnessReviewPacket, compileHarnessLeadDecision } from "../../scripts/harness-analysis/report-review-packet.mjs";
diff --git a/test/reporting/score-finding-consistency.test.mjs b/test/reporting/score-finding-consistency.test.mjs
index c00e61e..cce9433 100644
--- a/test/reporting/score-finding-consistency.test.mjs
+++ b/test/reporting/score-finding-consistency.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
AGENT_WORK_LOOP_CURRENT_TASK_DIMENSION_IDS,
diff --git a/test/reporting/task-loop-report.test.mjs b/test/reporting/task-loop-report.test.mjs
index d2d0db3..307a293 100644
--- a/test/reporting/task-loop-report.test.mjs
+++ b/test/reporting/task-loop-report.test.mjs
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
-import test from "node:test";
+import { test } from "vitest";
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
diff --git a/test/reporting/task-loop-repository-evidence.test.mjs b/test/reporting/task-loop-repository-evidence.test.mjs
index a56aa7c..133315d 100644
--- a/test/reporting/task-loop-repository-evidence.test.mjs
+++ b/test/reporting/task-loop-repository-evidence.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { buildTaskLoopRepositoryEvidence } from "../../scripts/harness-analysis/task-loop-repository-evidence.mjs";
diff --git a/test/reporting/task-loop-source.test.mjs b/test/reporting/task-loop-source.test.mjs
index 2676efc..2181c56 100644
--- a/test/reporting/task-loop-source.test.mjs
+++ b/test/reporting/task-loop-source.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { buildCheckupScan } from "../../scripts/coding-agent-practices/checkup/scan.mjs";
import { freezeSessionPopulation } from "../../scripts/session-analysis/session-population.mjs";
diff --git a/test/sessions/commit-session-link.test.mjs b/test/sessions/commit-session-link.test.mjs
index 184b8c0..a0d9a16 100644
--- a/test/sessions/commit-session-link.test.mjs
+++ b/test/sessions/commit-session-link.test.mjs
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { fileURLToPath } from "node:url";
import {
@@ -158,7 +158,7 @@ function fixtureSession(overrides = {}) {
test("collectCommitFacts parses numstat, subject, and session trailers (AC-1, AC-2)", async (t) => {
const root = await createFixtureRepo();
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
const { repoRoot, commits } = collectCommitFacts({ workspace: root, limit: 10 });
assert.equal(commits.length, 2);
@@ -177,7 +177,7 @@ test("collectCommitFacts parses numstat, subject, and session trailers (AC-1, AC
test("collectCommitFacts rejects unknown commits", async (t) => {
const root = await createFixtureRepo();
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
assert.throws(
() => collectCommitFacts({ workspace: root, commit: "0000000000000000000000000000000000000000" }),
@@ -222,7 +222,7 @@ test("collectCommitFacts preserves adversarial Git paths through --numstat -z (A
return;
}
const root = await mkdtemp(path.join(os.tmpdir(), "commit-session-paths-"));
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
git(root, ["init", "--initial-branch=main"]);
await mkdir(path.join(root, "src"), { recursive: true });
const paths = ["src/tab\tname.mjs", "src/line\nname.mjs"];
@@ -235,7 +235,7 @@ test("collectCommitFacts preserves adversarial Git paths through --numstat -z (A
test("reference-shape checkpoint refs resolve three ULIDs to a distinct session UUID (AC-9, AC-13)", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "commit-session-checkpoints-"));
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
git(root, ["init", "--initial-branch=main"]);
await mkdir(path.join(root, "src"), { recursive: true });
await writeFile(path.join(root, "src", "app.mjs"), "export const value = 0;\n");
@@ -272,7 +272,7 @@ test("reference-shape checkpoint refs resolve three ULIDs to a distinct session
test("legacy entire/checkpoints/v1 metadata resolves without checking out the branch (AC-9)", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "commit-session-checkpoint-branch-"));
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
git(root, ["init", "--initial-branch=main"]);
const checkpointId = "a3b2c4d5e6f7";
const sessionId = "legacy-session-id";
@@ -288,7 +288,7 @@ test("legacy entire/checkpoints/v1 metadata resolves without checking out the br
test("render-session prefers the newest self-contained transcript when one Entire session has several checkpoints (AC-9, AC-12)", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "commit-session-entire-transcript-"));
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
git(root, ["init", "--initial-branch=main"]);
await mkdir(path.join(root, "src"), { recursive: true });
await writeFile(path.join(root, "src", "app.mjs"), "export const value = 1;\n");
@@ -1084,7 +1084,7 @@ test("miniMarkdownToHtml renders the bounded subset and escapes HTML (AC-7)", ()
test("cli correlate runs end-to-end against a fixture repo (AC-1)", async (t) => {
const root = await createFixtureRepo();
- t.after(() => rm(root, { recursive: true, force: true }));
+ t.onTestFinished(() => rm(root, { recursive: true, force: true }));
const result = spawnSync(
process.execPath,
diff --git a/test/sessions/session-analysis-claude-facets.test.mjs b/test/sessions/session-analysis-claude-facets.test.mjs
index 793a9e2..3df80a3 100644
--- a/test/sessions/session-analysis-claude-facets.test.mjs
+++ b/test/sessions/session-analysis-claude-facets.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { access, writeFile } from "node:fs/promises";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
analyzeClaudeStyleSessions,
diff --git a/test/sessions/session-analysis-core-facts.test.mjs b/test/sessions/session-analysis-core-facts.test.mjs
index 40ad85b..9e2b298 100644
--- a/test/sessions/session-analysis-core-facts.test.mjs
+++ b/test/sessions/session-analysis-core-facts.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { buildTaskEpisodes } from "../../scripts/session-analysis/episode-contract.mjs";
import {
diff --git a/test/sessions/session-analysis-fs.test.mjs b/test/sessions/session-analysis-fs.test.mjs
index 2190f9d..2a39bac 100644
--- a/test/sessions/session-analysis-fs.test.mjs
+++ b/test/sessions/session-analysis-fs.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdtemp, mkdir, realpath, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { walkFiles } from "../../scripts/session-analysis/fs.mjs";
import { collectSkillFiles } from "../../scripts/agent-customize/core/items.mjs";
diff --git a/test/sessions/session-analysis-providers.test.mjs b/test/sessions/session-analysis-providers.test.mjs
index 892640e..b8c4e35 100644
--- a/test/sessions/session-analysis-providers.test.mjs
+++ b/test/sessions/session-analysis-providers.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
createAnalyzer,
diff --git a/test/sessions/session-analysis-time.test.mjs b/test/sessions/session-analysis-time.test.mjs
index 32425b2..372705b 100644
--- a/test/sessions/session-analysis-time.test.mjs
+++ b/test/sessions/session-analysis-time.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { normalizeTimestamp, timestampMillis } from "../../scripts/session-analysis/time.mjs";
diff --git a/test/sessions/session-analysis.test.mjs b/test/sessions/session-analysis.test.mjs
index 26483cb..f095f99 100644
--- a/test/sessions/session-analysis.test.mjs
+++ b/test/sessions/session-analysis.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { createAnalyzer } from "../../scripts/session-analysis.mjs";
import {
diff --git a/test/sessions/session-episode-contract.test.mjs b/test/sessions/session-episode-contract.test.mjs
index 85ef851..b4d3a93 100644
--- a/test/sessions/session-episode-contract.test.mjs
+++ b/test/sessions/session-episode-contract.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
buildTaskEpisodes,
diff --git a/test/sessions/session-population.test.mjs b/test/sessions/session-population.test.mjs
index 1938bd3..74c9ab4 100644
--- a/test/sessions/session-population.test.mjs
+++ b/test/sessions/session-population.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
async function populationModule() {
return import("../../scripts/session-analysis/session-population.mjs");
diff --git a/test/sessions/session-result-facts.test.mjs b/test/sessions/session-result-facts.test.mjs
index 78fe44a..542fded 100644
--- a/test/sessions/session-result-facts.test.mjs
+++ b/test/sessions/session-result-facts.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { parseResultFacts } from "../../scripts/session-analysis/result-facts.mjs";
diff --git a/test/sessions/session-selection-plan.test.mjs b/test/sessions/session-selection-plan.test.mjs
index 70ade64..aa016fe 100644
--- a/test/sessions/session-selection-plan.test.mjs
+++ b/test/sessions/session-selection-plan.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { buildObservationManifest } from "../../scripts/session-analysis/observation-manifest.mjs";
import { CodexSessionAnalyzer } from "../../scripts/session-analysis/platforms/codex.mjs";
@@ -407,7 +407,10 @@ test("selection entry collection bounds concurrency and never asks adapters for
assert.ok(optionsSeen.every((options) => options.includeContent === false));
});
-test("Qoder and Codex insight adapters apply the same validated AI plan", async (context) => {
+test.each([
+ { label: "Qoder", platform: "qoder", Analyzer: QoderSessionAnalyzer },
+ { label: "Codex", platform: "codex", Analyzer: CodexSessionAnalyzer },
+])("$label insight adapter applies the same validated AI plan", async ({ platform, Analyzer }) => {
const sessions = Array.from({ length: 6 }, (_, index) => ({
sessionId: `session-${index}`,
workspace: "/workspace/project",
@@ -454,38 +457,34 @@ test("Qoder and Codex insight adapters apply the same validated AI plan", async
const precomputedEntries = sessions.map((session) =>
buildSessionSelectionEntry(session, eventsBySession.get(session.sessionId) ?? []));
- for (const [name, Analyzer] of [["qoder", QoderSessionAnalyzer], ["codex", CodexSessionAnalyzer]]) {
- await context.test(name, async () => {
- const analyzer = new Analyzer();
- let readCount = 0;
- analyzer.resolveScope = async () => ({
- platform: name,
- workspace: "/workspace/project",
- since: null,
- until: null,
- sinceTime: null,
- untilTime: null,
- includeGlobalCapabilities: false,
- });
- analyzer.discoverSourceRoots = async () => [];
- analyzer.discoverSessions = async () => sessions;
- analyzer.readSession = async (session) => {
- readCount += 1;
- return eventsBySession.get(session.sessionId) ?? [];
- };
+ const analyzer = new Analyzer();
+ let readCount = 0;
+ analyzer.resolveScope = async () => ({
+ platform,
+ workspace: "/workspace/project",
+ since: null,
+ until: null,
+ sinceTime: null,
+ untilTime: null,
+ includeGlobalCapabilities: false,
+ });
+ analyzer.discoverSourceRoots = async () => [];
+ analyzer.discoverSessions = async () => sessions;
+ analyzer.readSession = async (session) => {
+ readCount += 1;
+ return eventsBySession.get(session.sessionId) ?? [];
+ };
- const result = await analyzer.analyze({
- command: "insights",
- selectionPlan: adapterPlan,
- selectionEntries: precomputedEntries,
- });
+ const result = await analyzer.analyze({
+ command: "insights",
+ selectionPlan: adapterPlan,
+ selectionEntries: precomputedEntries,
+ });
- assert.equal(result.selection.requestedStrategy, "ai-plan");
- assert.equal(result.selection.analyzedCount, 2);
- assert.deepEqual(result.sessions.map((session) => session.sessionId), ["session-0", "session-1"]);
- assert.deepEqual(result.selection.strata, ["closed", "unvalidated"]);
- assert.equal(result.selection.plan.planner, "ai");
- assert.equal(readCount, 2);
- });
- }
+ assert.equal(result.selection.requestedStrategy, "ai-plan");
+ assert.equal(result.selection.analyzedCount, 2);
+ assert.deepEqual(result.sessions.map((session) => session.sessionId), ["session-0", "session-1"]);
+ assert.deepEqual(result.selection.strata, ["closed", "unvalidated"]);
+ assert.equal(result.selection.plan.planner, "ai");
+ assert.equal(readCount, 2);
});
diff --git a/test/sessions/session-selection.test.mjs b/test/sessions/session-selection.test.mjs
index 6207618..8d241d5 100644
--- a/test/sessions/session-selection.test.mjs
+++ b/test/sessions/session-selection.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { selectSessions, selectionSummary } from "../../scripts/session-analysis/selection.mjs";
import { buildObservationManifest } from "../../scripts/session-analysis/observation-manifest.mjs";
diff --git a/test/sessions/session-usage-activity.test.mjs b/test/sessions/session-usage-activity.test.mjs
index dbd4b8f..fb258e6 100644
--- a/test/sessions/session-usage-activity.test.mjs
+++ b/test/sessions/session-usage-activity.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import { buildTaskLoopSourceCandidate } from "../../scripts/harness-analysis/task-loop-source.mjs";
import { buildDailyUsageActivity } from "../../scripts/session-analysis/daily-usage.mjs";
diff --git a/test/sessions/session-usage-report.test.mjs b/test/sessions/session-usage-report.test.mjs
index 0133219..05150dc 100644
--- a/test/sessions/session-usage-report.test.mjs
+++ b/test/sessions/session-usage-report.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { fileURLToPath } from "node:url";
import {
diff --git a/test/sessions/session-usage-summary.test.mjs b/test/sessions/session-usage-summary.test.mjs
index d6093b8..dfab1a0 100644
--- a/test/sessions/session-usage-summary.test.mjs
+++ b/test/sessions/session-usage-summary.test.mjs
@@ -4,7 +4,7 @@ import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
-import test from "node:test";
+import { test } from "vitest";
import { workspaceToQoderSlug } from "../../scripts/session-analysis/platforms/qoder.mjs";
import { buildUsageSummary } from "../../scripts/session-analysis/usage-summary.mjs";
diff --git a/test/sessions/session-workspace-match.test.mjs b/test/sessions/session-workspace-match.test.mjs
index 9155c7e..e726a6f 100644
--- a/test/sessions/session-workspace-match.test.mjs
+++ b/test/sessions/session-workspace-match.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import test from "node:test";
+import { test } from "vitest";
import {
SESSION_WORKSPACE_SCOPE_KIND,
diff --git a/test/sessions/session-workspace-provider.test.mjs b/test/sessions/session-workspace-provider.test.mjs
index 254fd5d..2014444 100644
--- a/test/sessions/session-workspace-provider.test.mjs
+++ b/test/sessions/session-workspace-provider.test.mjs
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { CodexSessionAnalyzer } from "../../scripts/session-analysis/platforms/codex.mjs";
import {
diff --git a/test/skills-docs/better-harness-skill.test.mjs b/test/skills-docs/better-harness-skill.test.mjs
index bef93e2..f41b085 100644
--- a/test/skills-docs/better-harness-skill.test.mjs
+++ b/test/skills-docs/better-harness-skill.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
const ROOT = process.cwd();
const SKILL_PATH = "skills/better-harness/SKILL.md";
diff --git a/test/skills-docs/coding-agent-platform-notes.test.mjs b/test/skills-docs/coding-agent-platform-notes.test.mjs
index b286b3f..11879ab 100644
--- a/test/skills-docs/coding-agent-platform-notes.test.mjs
+++ b/test/skills-docs/coding-agent-platform-notes.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
const ROOT = process.cwd();
diff --git a/test/skills-docs/doc-link-graph.test.mjs b/test/skills-docs/doc-link-graph.test.mjs
index 6777300..a17873e 100644
--- a/test/skills-docs/doc-link-graph.test.mjs
+++ b/test/skills-docs/doc-link-graph.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync, existsSync } from "node:fs";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import {
buildGraph,
diff --git a/test/skills-docs/pi-prompt-template.test.mjs b/test/skills-docs/pi-prompt-template.test.mjs
index aabc1e6..69f4f84 100644
--- a/test/skills-docs/pi-prompt-template.test.mjs
+++ b/test/skills-docs/pi-prompt-template.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
-import test from "node:test";
+import { test } from "vitest";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
diff --git a/test/support/github-actions-reporter.mjs b/test/support/github-actions-reporter.mjs
deleted file mode 100644
index f25370c..0000000
--- a/test/support/github-actions-reporter.mjs
+++ /dev/null
@@ -1,68 +0,0 @@
-import path from "node:path";
-import { fileURLToPath } from "node:url";
-
-function escapeCommandData(value) {
- return String(value)
- .replaceAll("%", "%25")
- .replaceAll("\r", "%0D")
- .replaceAll("\n", "%0A");
-}
-
-function escapeCommandProperty(value) {
- return escapeCommandData(value)
- .replaceAll(":", "%3A")
- .replaceAll(",", "%2C");
-}
-
-function errorDetail(error) {
- const cause = error?.cause ?? error;
- if (!cause) return "Test failed without an error detail.";
- return String(cause.stack ?? cause.message ?? cause).slice(0, 6000);
-}
-
-function repositoryPath(file, cwd) {
- if (!file) return null;
- const filePath = file.startsWith("file:") ? fileURLToPath(file) : file;
- const relative = path.relative(cwd, filePath);
- return relative && !relative.startsWith(`..${path.sep}`) && relative !== ".."
- ? relative.split(path.sep).join("/")
- : filePath.split(path.sep).join("/");
-}
-
-export function githubFailureAnnotation(data, { cwd = process.cwd() } = {}) {
- const properties = [];
- const file = repositoryPath(data.file, cwd);
- if (file) properties.push(`file=${escapeCommandProperty(file)}`);
- if (Number.isInteger(data.line)) properties.push(`line=${data.line}`);
- if (Number.isInteger(data.column)) properties.push(`col=${data.column}`);
- properties.push(`title=${escapeCommandProperty(`Test failed: ${data.name}`)}`);
- return `::error ${properties.join(",")}::${escapeCommandData(errorDetail(data.details?.error))}\n`;
-}
-
-export function plainFailure(data, { cwd = process.cwd() } = {}) {
- const file = repositoryPath(data.file, cwd) ?? "unknown file";
- const location = Number.isInteger(data.line) ? `${file}:${data.line}` : file;
- return `\nFAIL ${location} — ${data.name}\n${errorDetail(data.details?.error)}\n`;
-}
-
-export default async function* githubActionsReporter(source) {
- const seen = new Set();
- for await (const event of source) {
- if (event.type === "test:pass" && !event.data.skip && !event.data.todo) {
- yield ".";
- continue;
- }
- if (event.type === "test:summary") {
- yield "\n";
- continue;
- }
- if (event.type !== "test:fail" || event.data.skip || event.data.todo) continue;
- const key = `${event.data.file ?? ""}:${event.data.line ?? ""}:${event.data.name}`;
- if (seen.has(key)) continue;
- seen.add(key);
- yield "X";
- yield process.env.GITHUB_ACTIONS === "true"
- ? githubFailureAnnotation(event.data)
- : plainFailure(event.data);
- }
-}
diff --git a/test/support/run-ci.mjs b/test/support/run-ci.mjs
deleted file mode 100644
index be67f9e..0000000
--- a/test/support/run-ci.mjs
+++ /dev/null
@@ -1,118 +0,0 @@
-import { appendFile, mkdir, readdir, rm } from "node:fs/promises";
-import path from "node:path";
-import { performance } from "node:perf_hooks";
-import { spawnSync } from "node:child_process";
-import { fileURLToPath, pathToFileURL } from "node:url";
-
-const SUPPORT_DIR = path.dirname(fileURLToPath(import.meta.url));
-const REPO_ROOT = path.resolve(SUPPORT_DIR, "../..");
-const TEST_ROOT = path.join(REPO_ROOT, "test");
-const RESULTS_ROOT = path.join(REPO_ROOT, "test-results");
-const REPORTER_PATH = path.join(SUPPORT_DIR, "github-actions-reporter.mjs");
-const REPORTER_SPECIFIER = pathToFileURL(REPORTER_PATH).href;
-
-export function nodeTestFileArgument(filePath, {
- cwd = REPO_ROOT,
- pathApi = path,
-} = {}) {
- const relativePath = pathApi.relative(cwd, filePath);
- if (!relativePath || pathApi.isAbsolute(relativePath)) {
- throw new Error("test files must be on the same filesystem root as the test runner");
- }
- const portablePath = relativePath.split(pathApi.sep).join("/");
- return portablePath.startsWith(".") ? portablePath : `./${portablePath}`;
-}
-
-export async function discoverTestCategories(testRoot = TEST_ROOT) {
- const entries = await readdir(testRoot, { withFileTypes: true });
- const categories = [];
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
- if (!entry.isDirectory()) continue;
- const directory = path.join(testRoot, entry.name);
- const files = (await readdir(directory, { withFileTypes: true }))
- .filter((file) => file.isFile() && file.name.endsWith(".test.mjs"))
- .map((file) => path.join(directory, file.name))
- .sort();
- if (files.length > 0) categories.push({ name: entry.name, files });
- }
- return categories;
-}
-
-function durationLabel(durationMs) {
- return `${(durationMs / 1000).toFixed(1)}s`;
-}
-
-async function writeStepSummary(rows, summaryPath) {
- if (!summaryPath) return;
- const markdown = [
- "## Test results by capability",
- "",
- "| Capability | Files | Status | Duration |",
- "| --- | ---: | --- | ---: |",
- ...rows.map((row) => `| ${row.name} | ${row.files} | ${row.status === 0 ? "pass" : "fail"} | ${durationLabel(row.durationMs)} |`),
- "",
- ].join("\n");
- await appendFile(summaryPath, markdown, "utf8");
-}
-
-export async function runCiTests(selectedNames = process.argv.slice(2), {
- cwd = REPO_ROOT,
- env = process.env,
- resultsRoot = RESULTS_ROOT,
- stderr = process.stderr,
- stdio = "inherit",
- stdout = process.stdout,
- testRoot = TEST_ROOT,
-} = {}) {
- const discovered = await discoverTestCategories(testRoot);
- const selected = selectedNames.length === 0
- ? discovered
- : discovered.filter((category) => selectedNames.includes(category.name));
- const missing = selectedNames.filter((name) => !discovered.some((category) => category.name === name));
- if (missing.length > 0) {
- stderr.write(`Unknown test capabilities: ${missing.join(", ")}\n`);
- return 64;
- }
-
- await mkdir(resultsRoot, { recursive: true });
- const childEnv = { ...env };
- delete childEnv.NODE_TEST_CONTEXT;
- const rows = [];
- for (const category of selected) {
- const junitPath = path.join(resultsRoot, `${category.name}.xml`);
- await rm(junitPath, { force: true });
- const grouped = env.GITHUB_ACTIONS === "true";
- if (grouped) stdout.write(`::group::Tests: ${category.name}\n`);
- else stdout.write(`\n## Tests: ${category.name}\n`);
-
- const started = performance.now();
- const result = spawnSync(process.execPath, [
- "--test",
- `--test-reporter=${REPORTER_SPECIFIER}`,
- "--test-reporter=junit",
- "--test-reporter-destination=stdout",
- `--test-reporter-destination=${junitPath}`,
- ...category.files.map((filePath) => nodeTestFileArgument(filePath, { cwd })),
- ], {
- cwd,
- env: childEnv,
- stdio,
- windowsHide: true,
- });
- const durationMs = performance.now() - started;
- const status = Number.isInteger(result.status) ? result.status : 1;
- rows.push({ name: category.name, files: category.files.length, status, durationMs });
- if (result.error) stderr.write(`${result.error.message}\n`);
- if (grouped) stdout.write("::endgroup::\n");
- }
-
- await writeStepSummary(rows, env.GITHUB_STEP_SUMMARY);
- const failed = rows.filter((row) => row.status !== 0);
- stdout.write(`\nTest capabilities: ${rows.length - failed.length} passed, ${failed.length} failed.\n`);
- if (failed.length > 0) stderr.write(`Failed capabilities: ${failed.map((row) => row.name).join(", ")}\n`);
- return failed.length === 0 ? 0 : 1;
-}
-
-const isMain = process.argv[1]
- && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url;
-if (isMain) process.exitCode = await runCiTests();
diff --git a/vitest.ci.config.mjs b/vitest.ci.config.mjs
new file mode 100644
index 0000000..0b751a5
--- /dev/null
+++ b/vitest.ci.config.mjs
@@ -0,0 +1,13 @@
+import { defineConfig, mergeConfig } from "vitest/config";
+
+import baseConfig from "./vitest.config.mjs";
+
+const reporters = [
+ "default",
+ ...(process.env.GITHUB_ACTIONS === "true" ? ["github-actions"] : []),
+ ["junit", { outputFile: "test-results/vitest.junit.xml" }],
+];
+
+export default mergeConfig(baseConfig, defineConfig({
+ test: { reporters },
+}));
diff --git a/vitest.config.mjs b/vitest.config.mjs
new file mode 100644
index 0000000..978c012
--- /dev/null
+++ b/vitest.config.mjs
@@ -0,0 +1,13 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ exclude: ["test/fixtures/**", "test/support/**"],
+ hookTimeout: 120_000,
+ include: ["test/**/*.test.mjs"],
+ pool: "forks",
+ testTimeout: 120_000,
+ teardownTimeout: 30_000,
+ },
+});