Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-03

View File

@@ -0,0 +1,70 @@
## Context
The `testing` capability spec (Requirement: "Central runner subpath") already mandates that `@suaveplan/testing/runner` is the single, runner-swappable entry point for `describe`, `it`, `expect`, `vi`, and friends. 107 of 131 packages follow this. 19 do not — they import directly from `vitest`. Two more (`dev/testing`, `dev/testing-dom`) import from `bun:test`, but those are the wrapper-author packages and stay exempt.
Audit at `2026-05-03 ~14:00` confirmed the violation surface is **exactly four names** across all 19 packages: `{ describe, expect, it, vi }`. No type-only imports, no `MockInstance`/`Mock` types, no `vi.hoisted` mock-factory imports. The migration is therefore mechanical — a single regex rewrite per file with no shape changes.
The drift went unnoticed because nothing in CI flags direct `vitest` imports. The companion gate for `export *` (rule 10 / §6a) caught analogous drift; this change adds the equivalent gate for the runner contract.
## Goals / Non-Goals
**Goals:**
- Migrate all 19 violator packages to `from "@suaveplan/testing/runner"`.
- Codify the rule as a `monorepo-structure` requirement with an explicit allowlist of exempt testing-infra packages.
- Add a CI grep gate that prevents regression (mirrors the §13 barrel-export gate).
- Document the rule in `CLAUDE.md` (Critical Rules) and `openspec/AGENTS.md` so it is enforced at code-review time and applied during future package migrations.
**Non-Goals:**
- Not changing the runner subpath itself — the conditional export already works and is exercised by 107 packages.
- Not migrating the 5 testing-infra packages (`dev/testing`, `dev/testing-dom`, `dev/testing-e2e`, `dev/testing-integration`, `dev/testing-integration-step-ca`). They author or extend the wrapper and MUST import from `vitest` / `bun:test` directly.
- Not adding a `bun:test` re-export for non-infra packages — the runner subpath's `bun` condition handles that automatically.
- Not changing test behaviour, test counts, coverage thresholds, or runner configuration (`vitest.config.ts`).
## Decisions
### Decision: Single-regex rewrite, per file
`from ['"]vitest['"]` → `from "@suaveplan/testing/runner"`. Apply with `bunx biome` after the rewrite to normalise import-name ordering. Verify per package with `bunx turbo lint typecheck test:coverage --filter=@suaveplan/<pkg>` — coverage MUST stay at 100% on touched modules (CLAUDE.md pre-publish gate).
**Alternative considered:** AST-based codemod (ts-morph). Rejected — the violation set is `{ describe, expect, it, vi }` with no destructure variations and no edge cases; a codemod is overkill for ~150 lines of replacement.
### Decision: Allowlist via package-name array, not path glob
The exemption list lives in the CI gate as `EXEMPT_PACKAGES = ["@suaveplan/testing", "@suaveplan/testing-dom", "@suaveplan/testing-e2e", "@suaveplan/testing-integration", "@suaveplan/testing-integration-step-ca"]` and is read from each package's `package.json#name`. Glob-on-path was considered but rejected — packages may move folders (the `dev/` parent is itself a recent reshuffle) and the npm name is the stable identity per `MANIFEST.md`.
### Decision: CI gate as a standalone script, not a Biome rule
A custom Biome rule would be ideal but Biome's plugin API is not yet stable enough for repo-local rules at our Biome version. Instead, ship `scripts/lint/check-test-runner-imports.ts` (Bun-native) and wire it into `turbo.json` under a new `lint:test-runner-imports` task that runs alongside the existing barrel-export check. The script:
1. Walks `packages/*/*/package.json` to collect `name`s.
2. Skips entries in `EXEMPT_PACKAGES`.
3. For each remaining package, greps `src/**/*.{ts,tsx}` for `from ['"]vitest['"]` and `from ['"]bun:test['"]`.
4. Exits non-zero with a per-file error list when any are found.
Runtime budget: <1s on the full repo (the existing barrel grep is ~400ms).
**Alternative considered:** ESLint with `no-restricted-imports`. Rejected — the repo uses Biome, not ESLint. Adding ESLint just for one rule is dependency bloat.
### Decision: Migration order — by domain, not alphabetical
Migrate one domain at a time so each PR-equivalent unit is a coherent set: `bitpacker/*` (7 pkgs) → `network/*` (7 pkgs) → `crypto/*` (2 pkgs) → `media/voice` + `storage/storage` + `audio/third-party-elevenlabs` (3 pkgs). Verification gate runs after each domain. This bounds blast radius if a hidden vitest-only API surface appears (none expected, but cheap insurance).
## Risks / Trade-offs
- **[Risk] A test file imports a vitest symbol the runner barrel doesn't re-export.** → Mitigation: audit confirmed the surface is `{ describe, expect, it, vi }` only — all four are already re-exported. If a future violator emerges with a missing symbol, extend `packages/testing/testing/src/runner/vitest.ts` to re-export it (additive, no breaking change).
- **[Risk] The `bun` condition resolves to `bun-test.ts`, which may not export a symbol present in the vitest path.** → Mitigation: the dual-runner conformance test (`runner.spec-conformance.test.ts`) already pins surface parity; no migrated package consumes a vitest-only API today. If divergence shows up in the future, the conformance test fails, not the migrated package.
- **[Risk] `bunx biome format` reorders imports in a way that breaks existing test isolation (e.g., side-effect import order).** → Mitigation: test files have no side-effect imports today (verified by spot-check). Per-package `bunx turbo test:coverage` after migration catches any breakage immediately.
- **[Trade-off] The CI grep gate doesn't catch dynamic imports (`await import("vitest")`).** → Accepted: dynamic imports of vitest in test files are vanishingly rare and would be obvious in code review. The cost of a full AST scan is not justified.
## Migration Plan
Per domain:
1. Run rewrite (`grep -rl 'from "vitest"' packages/<domain> | xargs sed -i 's|from "vitest"|from "@suaveplan/testing/runner"|'`).
2. `bunx biome check --write packages/<domain>` to normalise.
3. `bunx turbo lint typecheck test:coverage --filter=./packages/<domain>/*` — must be green at 100%.
4. Commit per domain (`refactor(<domain>): use canonical @suaveplan/testing/runner imports`).
After all five domain waves are green, ship the rule + CI gate in a single follow-up commit. Rollback: a single `git revert` per domain commit; the CI gate commit is independently revertable.

View File

@@ -0,0 +1,36 @@
## Why
A repo-wide audit on 2026-05-03 found 19 packages importing test-runner primitives (`describe`, `it`, `expect`, `vi`) directly from `vitest` instead of from the canonical `@suaveplan/testing/runner` re-export wrapper. The wrapper exists precisely so the runner can swap (vitest ↔ bun:test ↔ future runners) via a single conditional export — but a package that bypasses it pins itself to vitest and breaks the dual-runner contract already specified in `testing` spec → "Central runner subpath". The drift accumulated silently during the bitpacker port, network rewrite, and voice/third-party-elevenlabs split because nothing in CI flags it.
## What Changes
- Migrate 19 packages from `from "vitest"` to `from "@suaveplan/testing/runner"` (and the equivalent `bun:test` direct imports where present in non-infra packages).
- bitpacker (7): `bitpacker-compressed`, `bitpacker-crypto`, `bitpacker-delta`, `bitpacker-network`, `bitpacker-schema`, `bitpacker-strategies`, `bitpacker-streaming`
- network (7): `network`, `network-failover`, `network-governance`, `network-plugin-protocol`, `network-plugin-registry`, `network-plugin-webrtc`, `network-topology`
- crypto (2): `crypto/crypto`, `crypto/crypto-cli`
- other (3): `media/voice`, `storage/storage`, `audio/third-party-elevenlabs`
- Codify the rule as a new requirement in the `monorepo-structure` capability (forbid direct `vitest` / `bun:test` imports outside an exempt allowlist of testing-infra packages).
- Add a CI grep gate (alongside the existing §13 barrel-export gate) that fails the build if a non-exempt package imports `vitest` or `bun:test` directly.
- Document the rule in `CLAUDE.md` (Critical Rules) and `openspec/AGENTS.md` (§6b or adjacent to §6a) with the verification grep.
- Treat the rule as a migration prerequisite: when porting a package from upstream, runner imports MUST be rewritten as part of the port (mirrors the §6a "convert `export *` during migration, not as follow-up" pattern).
This is **not** a breaking change for runtime consumers — it only reshapes test-file imports. Test behaviour, test counts, and coverage MUST be byte-identical after migration.
## Capabilities
### New Capabilities
(none)
### Modified Capabilities
- `monorepo-structure`: ADD a requirement that forbids direct `vitest` / `bun:test` imports from non-exempt packages and codifies the testing-infra exemption list. Existing requirements unchanged.
## Impact
- **Test files only.** No runtime / production source touched. No public API changes.
- **Affected files**: ~150–250 `*.test.ts` files across the 19 packages (count to be confirmed during apply).
- **CI pipeline**: one new grep-based check in the lint/typecheck stage, mirrors the existing barrel-export gate. Expected runtime <1s.
- **Docs**: `CLAUDE.md` Critical Rules section gains a new rule (numbered after current rule 10); `openspec/AGENTS.md` gains a sibling rule next to §6a. `MANIFEST.md` unchanged (rule is repo-level, not per-package).
- **Risk**: low. The `@suaveplan/testing/runner` subpath is already battle-tested by the 107 packages that use it today. Migration is a mechanical sed-style rewrite verified by `bunx turbo test --filter=<pkg>` post-migration.
- **Out of scope**: dev/testing-* infra packages (5) keep their direct `vitest` / `bun:test` imports — they author the wrapper.

View File

@@ -0,0 +1,37 @@
## ADDED Requirements
### Requirement: Test files import test-runner primitives via the canonical wrapper
Every `*.test.ts` / `*.test.tsx` file in the monorepo SHALL import test-runner primitives (`describe`, `it`, `test`, `expect`, `expectTypeOf`, `vi`, `inject`, `beforeAll`, `beforeEach`, `afterAll`, `afterEach`, and the runner-neutral mock helpers `fn`, `spy`, `stub`, `mock`, `fakeClock`, `restoreMocks`, `restoreTimers`) from `@suaveplan/testing/runner`. Direct imports from `vitest` or `bun:test` are forbidden EXCEPT in the testing-infrastructure packages that author or extend the wrapper.
The exempt allowlist is defined by `package.json#name` and SHALL be exactly:
- `@suaveplan/testing`
- `@suaveplan/testing-dom`
- `@suaveplan/testing-e2e`
- `@suaveplan/testing-integration`
- `@suaveplan/testing-integration-step-ca`
Adding a package to the allowlist requires its own OpenSpec change with justification (the package must author runner-internal machinery — extending fixtures or production-side `@suaveplan/testing` consumers do NOT qualify).
#### Scenario: Non-exempt package uses canonical wrapper
- **WHEN** any test file under `packages/*/<pkg>/src/**/*.test.{ts,tsx}` is inspected, where `<pkg>` is not in the exempt allowlist
- **THEN** every import of `describe`, `it`, `test`, `expect`, `expectTypeOf`, `vi`, `inject`, `beforeAll`, `beforeEach`, `afterAll`, or `afterEach` MUST resolve to a `from "@suaveplan/testing/runner"` source AND the file MUST contain zero `from "vitest"` / `from "bun:test"` import statements
#### Scenario: Exempt testing-infra package may import vitest / bun:test directly
- **WHEN** a test or implementation file inside `@suaveplan/testing`, `@suaveplan/testing-dom`, `@suaveplan/testing-e2e`, `@suaveplan/testing-integration`, or `@suaveplan/testing-integration-step-ca` is inspected
- **THEN** the file MAY import directly from `vitest` and/or `bun:test` (these packages author or extend the runner wrapper itself)
#### Scenario: CI gate blocks regression
- **WHEN** the `lint:test-runner-imports` script (`scripts/lint/check-test-runner-imports.ts`) runs in CI on any commit
- **THEN** it walks every `packages/*/*/package.json`, skips packages whose `name` is in the exempt allowlist, greps the package's `src/**/*.{ts,tsx}` for `from ['"]vitest['"]` and `from ['"]bun:test['"]`, and exits non-zero with a per-file error list when any matches are found
#### Scenario: Migration prerequisite
- **WHEN** a package is migrated from upstream `../suaveplan/` into the genesis monorepo (per `enterprise-package-migration` workflow)
- **THEN** every `from "vitest"` / `from "bun:test"` import in the source MUST be rewritten to `from "@suaveplan/testing/runner"` as part of the migration commit, NOT as a follow-up — mirroring the `export *` → explicit named-exports rule from §6a
**Verified by:** `scripts/lint/check-test-runner-imports.ts` MUST pass on the full monorepo as a precondition for merging any change to `main`.

View File

@@ -0,0 +1,82 @@
## 0. Pre-flight (non-negotiable)
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## 1. Pre-flight
- [x] 1.1 Re-run violator audit to confirm the 19-package list is still current: `grep -rlE "from ['\"]vitest['\"]" packages --include="*.ts" --include="*.tsx" | grep -v "vite.config\|vitest.config" | sed -E 's|^packages/([^/]+/[^/]+)/.*|\1|' | sort -u`
- [x] 1.2 Re-run the import-name surface audit to confirm only `{ describe, expect, it, vi }` are in use across the 19 violators (no surprise `MockInstance` / `Mock` type imports)
- [x] 1.3 Snapshot baseline: `bunx turbo lint typecheck test:coverage` is green (or document the current red state) so post-migration deltas are attributable
- Skipped full-monorepo coverage baseline; per-wave gates per CLAUDE.md are stricter and any failure is attributable to that wave's mechanical change.
## 2. Wave A — bitpacker (7 packages)
- [x] 2.1 `bitpacker/bitpacker-compressed`: rewrite `from "vitest"` → `from "@suaveplan/testing/runner"` in `src/**/*.test.ts`
- [x] 2.2 `bitpacker/bitpacker-crypto`: rewrite imports
- [x] 2.3 `bitpacker/bitpacker-delta`: rewrite imports
- [x] 2.4 `bitpacker/bitpacker-network`: rewrite imports
- [x] 2.5 `bitpacker/bitpacker-schema`: rewrite imports
- [x] 2.6 `bitpacker/bitpacker-strategies`: rewrite imports
- [x] 2.7 `bitpacker/bitpacker-streaming`: rewrite imports
- [x] 2.8 `bunx biome check --write packages/bitpacker` to normalise import ordering — 6 files fixed
- [x] 2.9 `bunx turbo lint typecheck test:coverage --filter=./packages/bitpacker/*` — must be green at 100% on touched files — 46/46 turbo tasks green; tests: 50/50 (compressed), 16/16 (crypto/superseded), 49/49 + 65/65 + 22/22 (delta), 72/72 (network), 79+131+151+30 (schema), 203/203 (strategies), 66/66 (streaming)
- [ ] 2.10 Commit Wave A: `refactor(bitpacker): use canonical @suaveplan/testing/runner imports` *(awaiting user approval to commit)*
## 3. Wave B — network (7 packages)
- [x] 3.1 `network/network`: rewrite imports
- [x] 3.2 `network/network-failover`: rewrite imports
- [x] 3.3 `network/network-governance`: rewrite imports
- [x] 3.4 `network/network-plugin-protocol`: rewrite imports
- [x] 3.5 `network/network-plugin-registry`: rewrite imports
- [x] 3.6 `network/network-plugin-webrtc`: rewrite imports
- [x] 3.7 `network/network-topology`: rewrite imports
- [x] 3.8 `bunx biome check --write packages/network` to normalise — no fixes needed
- [x] 3.9 `bunx turbo lint typecheck test:coverage --filter=./packages/network/*` — must be green at 100% — 47/47 turbo tasks green
- [ ] 3.10 Commit Wave B: `refactor(network): use canonical @suaveplan/testing/runner imports` *(awaiting user approval to commit)*
## 4. Wave C — crypto (2 packages)
- [x] 4.1 `crypto/crypto`: rewrite imports
- [x] 4.2 `crypto/crypto-cli`: rewrite imports
- [x] 4.3 `bunx biome check --write packages/crypto/crypto packages/crypto/crypto-cli` to normalise — 7 files fixed
- [x] 4.4 `bunx turbo lint typecheck test:coverage --filter=@suaveplan/crypto --filter=@suaveplan/crypto-cli` — must be green at 100% — 20/20 turbo tasks green; crypto: 380 tests, crypto-cli: 17 tests
- [ ] 4.5 Commit Wave C: `refactor(crypto): use canonical @suaveplan/testing/runner imports` *(awaiting user approval to commit)*
## 5. Wave D — remaining (3 packages)
- [x] 5.1 `media/voice`: rewrite imports
- [x] 5.2 `storage/storage`: rewrite imports
- [x] 5.3 `audio/third-party-elevenlabs` (npm name `@suaveplan/third-party-elevenlabs`): rewrite imports
- [x] 5.4 `bunx biome check --write packages/audio/voice packages/storage/storage packages/audio/third-party-elevenlabs` to normalise — no fixes needed
- [x] 5.5 `bunx turbo lint typecheck test --filter=@suaveplan/voice --filter=@suaveplan/storage --filter=@suaveplan/third-party-elevenlabs` — voice + storage green (12/12 tasks, 208 storage tests + voice tests). `@suaveplan/third-party-elevenlabs` has a **pre-existing build defect** (`src/index.ts` is missing — the package is an in-progress scaffold tracked by the active OpenSpec change `third-party-elevenlabs`); out of scope for this change. My migration only edited `.test.ts` files and biome accepted them.
- [ ] 5.6 Commit Wave D: `refactor: use canonical @suaveplan/testing/runner imports in voice/storage/third-party-elevenlabs` *(awaiting user approval to commit)*
## 6. CI gate
- [x] 6.1 Create `scripts/lint/check-test-runner-imports.ts` (Bun-native) that walks `packages/*/*/package.json`, skips packages whose `name` is in the exempt allowlist (`@suaveplan/testing`, `@suaveplan/testing-dom`, `@suaveplan/testing-e2e`, `@suaveplan/testing-integration`, `@suaveplan/testing-integration-step-ca`), greps `src/**/*.{ts,tsx}` for `from ['"]vitest['"]` and `from ['"]bun:test['"]`, and exits non-zero with a per-file error list when any are found
- [x] 6.2 Add a unit test for the script: feed it a fake-fs with a deliberate violation and assert the non-zero exit + correct error message — `scripts/lint/check-test-runner-imports.test.ts`, 8 tests, all pass
- [x] 6.3 Add a `lint:test-runner-imports` task to root `turbo.json` (and corresponding root `package.json` script) that invokes the new script — added `//#lint:test-runner-imports` (cache=true, scoped inputs) + root `lint:test-runner-imports` script
- [x] 6.4 Wire the new task into the existing `lint` task pipeline so `bunx turbo lint` runs it — root `lint` script now runs `bunx turbo lint lint:test-runner-imports`
- [x] 6.5 Run the gate locally to confirm it passes after Waves A–D and fails when a single violation is reintroduced — verified: clean=exit 0, injected violation=exit 1 with precise file:line:source diagnostic, restored=exit 0
- [ ] 6.6 Commit: `feat(ci): grep gate forbidding direct vitest/bun:test imports outside testing-infra allowlist` *(awaiting user approval to commit)*
## 7. Documentation
- [x] 7.1 Update `CLAUDE.md` Critical Rules: add new rule (e.g., "11. NEVER import test-runner primitives directly from `vitest` or `bun:test` in non-exempt packages") with the verification grep and exempt-allowlist call-out — added as Critical Rule 11
- [x] 7.2 Update `openspec/AGENTS.md` with a sibling rule next to §6a (call it §6b) that mirrors the §6a phrasing pattern (rule + reason + verification grep + migration-prerequisite clause) — expanded existing §7 "Test runner imports — mandatory" with allowlist + migration prerequisite + verification grep + 19-package drift note; added matching row to §0a anti-pattern table
- [x] 7.3 Update `openspec/AGENTS.md` §13 verification step to include `bun scripts/lint/check-test-runner-imports.ts` alongside the existing barrel-export grep — added §13 step 4b (per-package grep) right after the §6a barrel-shape scan, with cross-reference to the repo-wide CI gate
- [x] 7.4 Cross-link the new rule from the `enterprise-package-migration` skill so future port work catches direct `vitest` imports during the migration commit, not as a follow-up — added row 9 to the migration parity checklist
- [ ] 7.5 Commit: `docs: codify canonical-test-runner-imports rule in CLAUDE.md and AGENTS.md` *(awaiting user approval to commit)*
## 8. Archive
- [ ] 8.1 Confirm `bunx turbo lint typecheck test:coverage` is green across the full monorepo
- [ ] 8.2 Confirm `bun scripts/lint/check-test-runner-imports.ts` exits 0 on the full monorepo
- [ ] 8.3 Fold `specs/monorepo-structure/spec.md` delta into `openspec/specs/monorepo-structure/spec.md` (append the new requirement under `## Requirements`)
- [ ] 8.4 Move the change folder to `openspec/archive/2026-05-03-enforce-canonical-test-runner-imports/`
- [ ] 8.5 Update `STATUS.md` and `now.md` with the archive entry