From cf4915e60425d5dbbdb7780fee3cbf84a93ecff5 Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Thu, 13 Aug 2026 13:45:23 +0800 Subject: [PATCH] test(ci): migrate suite to Vitest Replace the custom node:test scheduler and reporter with Vitest default, GitHub Actions, and JUnit reporting so failures identify their owning file, test, assertion, and source location in the main CI log. Migrate the existing lifecycle APIs without rewriting assertions, document the new focused-test commands, and retain fork isolation across the 91-file suite. Validated with npm test (91 files, 1305 tests), focused CI/JUnit and Job Summary checks, npm ci, the doc-link graph suite, and npm run pack:verify. Co-authored-by: Codex (GPT 5.6 Sol) --- .github/workflows/ci.yml | 4 +- AGENTS.md | 2 +- CONTRIBUTING.md | 6 +- .../adapters/contributing-new-coding-agent.md | 2 +- docs/community.md | 2 +- docs/specs/2026-08-13-vitest-migration.md | 97 ++ package-lock.json | 1190 +++++++++++++++++ package.json | 7 +- references/agent-customize/skill-eval.md | 2 +- test/README.md | 19 +- test/agents/agent-asset-baseline.test.mjs | 2 +- test/agents/agent-asset-integrity.test.mjs | 2 +- .../agent-customize-architecture.test.mjs | 2 +- test/agents/agent-customize.test.mjs | 2 +- .../agent-guardrails-secret-scan.test.mjs | 12 +- test/agents/agent-lint-monorepo.test.mjs | 2 +- test/agents/agent-lint.test.mjs | 2 +- test/agents/coding-agent-asset-eval.test.mjs | 2 +- .../coding-agent-practices-inventory.test.mjs | 2 +- test/agents/harness-checkup.test.mjs | 6 +- test/cli/better-harness-cli.test.mjs | 4 +- test/cli/harness-doctor.test.mjs | 2 +- test/cli/harness-quickstart.test.mjs | 2 +- test/cli/read-only-command.test.mjs | 2 +- test/cli/terminal-demo.test.mjs | 2 +- .../ai-contribution-census.test.mjs | 6 +- test/governance/blast-radius.test.mjs | 2 +- test/governance/cloc.test.mjs | 2 +- .../core-change-watch-scope.test.mjs | 2 +- test/governance/core-change-watch.test.mjs | 2 +- .../governance/dependency-governance.test.mjs | 2 +- test/governance/review-trigger.test.mjs | 2 +- .../scripts-refactor-contract.test.mjs | 2 +- test/governance/test-ci-reporting.test.mjs | 126 -- test/governance/test-mapping.test.mjs | 2 +- test/governance/vitest-reporting.test.mjs | 54 + .../workflow-demand-diagnostics.test.mjs | 2 +- test/governance/workspace-topology.test.mjs | 2 +- test/learning/intervention-ledger.test.mjs | 2 +- .../learning-capture-evidence.test.mjs | 2 +- test/learning/learning-capture-state.test.mjs | 2 +- test/learning/learning-loop-contract.test.mjs | 2 +- test/learning/learning-loop-review.test.mjs | 2 +- .../lifecycle-demand-signals.test.mjs | 2 +- .../native-learning-candidate-review.test.mjs | 2 +- test/plugins/host-plugin-artifact.test.mjs | 2 +- .../host-support-profile-model.test.mjs | 2 +- test/plugins/host-support-profiles.test.mjs | 2 +- test/plugins/host-support.test.mjs | 4 +- .../plugin-lifecycle-architecture.test.mjs | 2 +- ...gin-lifecycle-command-definitions.test.mjs | 2 +- test/plugins/plugin-lifecycle-model.test.mjs | 2 +- .../plugin-lifecycle-plan-model.test.mjs | 2 +- .../plugin-lifecycle-status-row.test.mjs | 2 +- ...lugin-lifecycle-target-resolution.test.mjs | 2 +- test/plugins/plugin-lifecycle.test.mjs | 2 +- test/plugins/plugin-manifests.test.mjs | 2 +- .../better-harness-evidence-bundle.test.mjs | 2 +- test/reporting/cursor-canvas-report.test.mjs | 2 +- .../episode-evidence-review.test.mjs | 2 +- test/reporting/findings-recommend.test.mjs | 2 +- .../harness-canvas-validation.test.mjs | 2 +- .../harness-component-snapshot-cli.test.mjs | 4 +- .../harness-component-snapshot.test.mjs | 12 +- .../harness-findings-repair.test.mjs | 2 +- test/reporting/harness-inspector.test.mjs | 2 +- test/reporting/harness-memory-review.test.mjs | 2 +- .../reporting/harness-report-quality.test.mjs | 2 +- .../harness-report-render-cli.test.mjs | 2 +- test/reporting/harness-report-run.test.mjs | 2 +- test/reporting/harness-report-source.test.mjs | 2 +- .../html-report-interactions.test.mjs | 2 +- .../inspector-cross-provider.test.mjs | 2 +- test/reporting/maturity-models.test.mjs | 2 +- test/reporting/practice-findings.test.mjs | 2 +- test/reporting/preview-servers.test.mjs | 2 +- test/reporting/report-source-review.test.mjs | 2 +- .../score-finding-consistency.test.mjs | 2 +- test/reporting/task-loop-report.test.mjs | 2 +- .../task-loop-repository-evidence.test.mjs | 2 +- test/reporting/task-loop-source.test.mjs | 2 +- test/sessions/commit-session-link.test.mjs | 16 +- .../session-analysis-claude-facets.test.mjs | 2 +- .../session-analysis-core-facts.test.mjs | 2 +- test/sessions/session-analysis-fs.test.mjs | 2 +- .../session-analysis-providers.test.mjs | 2 +- test/sessions/session-analysis-time.test.mjs | 2 +- test/sessions/session-analysis.test.mjs | 2 +- .../session-episode-contract.test.mjs | 2 +- test/sessions/session-population.test.mjs | 2 +- test/sessions/session-result-facts.test.mjs | 2 +- test/sessions/session-selection-plan.test.mjs | 67 +- test/sessions/session-selection.test.mjs | 2 +- test/sessions/session-usage-activity.test.mjs | 2 +- test/sessions/session-usage-report.test.mjs | 2 +- test/sessions/session-usage-summary.test.mjs | 2 +- .../sessions/session-workspace-match.test.mjs | 2 +- .../session-workspace-provider.test.mjs | 2 +- .../skills-docs/better-harness-skill.test.mjs | 2 +- .../coding-agent-platform-notes.test.mjs | 2 +- test/skills-docs/doc-link-graph.test.mjs | 2 +- test/skills-docs/pi-prompt-template.test.mjs | 2 +- test/support/github-actions-reporter.mjs | 68 - test/support/run-ci.mjs | 118 -- vitest.ci.config.mjs | 13 + vitest.config.mjs | 13 + 106 files changed, 1537 insertions(+), 479 deletions(-) create mode 100644 docs/specs/2026-08-13-vitest-migration.md delete mode 100644 test/governance/test-ci-reporting.test.mjs create mode 100644 test/governance/vitest-reporting.test.mjs delete mode 100644 test/support/github-actions-reporter.mjs delete mode 100644 test/support/run-ci.mjs create mode 100644 vitest.ci.config.mjs create mode 100644 vitest.config.mjs 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, + }, +});