Files
2026-05-27 23:29:53 +01:00

104 lines
5.8 KiB
Markdown

<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: audio-plugins-fx (split-audio-plugins-fx)
**Package**: `@suaveplan/audio-fx-plugin-base`, `@suaveplan/audio-fx-plugin-*`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions. (See `openspec/AGENTS.md` Section 13.)
## ADDED Requirements
### Requirement: Per-plugin package layout
The audio FX capability SHALL be distributed as one foundation package (`@suaveplan/audio-fx-plugin-base`) plus one package per concrete plugin (`@suaveplan/audio-fx-plugin-<name>`). No package SHALL bundle more than one plugin's DSP, parameter surface, or registry entry.
#### Scenario: each plugin is independently installable
- **WHEN** a consumer adds only `@suaveplan/audio-fx-plugin-eq` to its `package.json`
- **THEN** `bun install` resolves `audio-fx-plugin-eq` + `audio-fx-plugin-base` + their transitive deps only, and no other `audio-plugin-*` package appears in the lockfile
#### Scenario: each plugin advances independently
- **WHEN** `audio-fx-plugin-eq` graduates to `1.0.0` while `audio-fx-plugin-reverb` remains at `0.1.0`
- **THEN** both packages publish from the same monorepo commit without forcing the other to bump
**Verified by:** `packages/audio-fx/audio-fx-plugin-base/tests/dep-graph.test.ts::"audio-fx-plugin-base declares no @suaveplan/audio-fx-plugin-* deps"` AND `packages/audio-fx/audio-fx-plugin-eq/tests/dep-graph.test.ts::"audio-fx-plugin-eq depends on audio-fx-plugin-base only among audio-plugin-* packages"`
---
### Requirement: `audio-fx-plugin-base` owns the shared contract surface
`@suaveplan/audio-fx-plugin-base` SHALL export the `AudioPlugin` contract, `PluginChain`, `ChainContext`, `AmbientState`, `BusRegistry`, `PluginRegistry`, `ChainSnapshot`, and the shared `error-codes.ts` registry. No concrete plugin SHALL re-implement these primitives.
#### Scenario: contract is the single source of truth
- **WHEN** a new plugin package is authored
- **THEN** it imports `AudioPlugin` from `@suaveplan/audio-fx-plugin-base/base` and never declares its own variant
#### Scenario: chain composition stays in base
- **WHEN** a consumer composes `audio-fx-plugin-eq`, `audio-fx-plugin-compressor`, and `audio-fx-plugin-reverb` into a chain
- **THEN** it imports `PluginChain` from `@suaveplan/audio-fx-plugin-base/chain` and the chain accepts every plugin without per-plugin glue
**Verified by:** `packages/audio-fx/audio-fx-plugin-base/src/chain/chain.test.ts::"PluginChain accepts any AudioPlugin implementation"` AND `packages/audio-fx/audio-fx-plugin-base/src/registry/registry.spec-conformance.test.ts::"PluginRegistry register/has/create/list invariants"`
---
### Requirement: `audio-fx-plugin-base` is dep-graph-isolated from concrete plugins
`@suaveplan/audio-fx-plugin-base` SHALL declare zero dependencies on any `@suaveplan/audio-fx-plugin-*` package. The dependency arrow MUST point only from concrete plugins to base, never base to plugins.
#### Scenario: no upward dep
- **WHEN** CI runs `tests/dep-graph.test.ts` in `audio-fx-plugin-base`
- **THEN** the test fails if `package.json` contains any `@suaveplan/audio-fx-plugin-*` entry other than `audio-fx-plugin-base` itself (which would be a self-dep and is also rejected)
**Verified by:** `packages/audio-fx/audio-fx-plugin-base/tests/dep-graph.test.ts::"audio-fx-plugin-base declares no @suaveplan/audio-fx-plugin-* deps"`
---
### Requirement: Error-code namespace preserved across the split
Every error code that existed in `@suaveplan/audio-plugins-fx` SHALL remain reachable under the same `AUDIO_PLUGIN.*` namespace from either `@suaveplan/audio-fx-plugin-base/error-codes` (shared codes) or the relevant `@suaveplan/audio-fx-plugin-<name>/error-codes` (plugin-specific codes). No code SHALL be renamed, removed, or re-numbered by this change.
#### Scenario: shared codes resolve from base
- **WHEN** a caller imports `AUDIO_PLUGIN.CHAIN_SLOT_NOT_FOUND` from `@suaveplan/audio-fx-plugin-base/error-codes`
- **THEN** the code value is byte-identical to the value previously exported from `@suaveplan/audio-plugins-fx/error-codes`
#### Scenario: plugin-specific codes resolve from the plugin
- **WHEN** a caller imports an EQ-specific code from `@suaveplan/audio-fx-plugin-eq/error-codes`
- **THEN** the code is in the `AUDIO_PLUGIN.EQ.*` namespace and is thrown by at least one production path in `audio-fx-plugin-eq`
**Verified by:** `packages/audio-fx/audio-fx-plugin-base/src/error-codes.test.ts::"every shared AUDIO_PLUGIN.* code preserved across split"` AND `packages/audio-fx/audio-fx-plugin-eq/src/error-codes.test.ts::"every AUDIO_PLUGIN.EQ.* code thrown by a production path"`
---
### Requirement: Migration leaves zero references to the old package
After this change ships, no file under `packages/`, `apps/`, `scripts/`, or `docs/` SHALL import from `@suaveplan/audio-plugins-fx`. The old package directory SHALL be deleted.
#### Scenario: no stale imports
- **WHEN** CI runs `rg "@suaveplan/audio-plugins-fx" packages/ apps/ scripts/ docs/`
- **THEN** the command exits with status 1 (no matches found)
#### Scenario: old package directory removed
- **WHEN** the post-archive tree is inspected
- **THEN** `packages/audio/audio-plugins-fx/` does not exist
**Verified by:** `scripts/openspec-conformance.ts` audit for change `split-audio-plugins-fx` AND `bunx turbo build` whole-monorepo green
## REMOVED Requirements
### Requirement: Single bundled FX package
**Reason:** Superseded by per-plugin packaging — see ADDED requirement "Per-plugin package layout" above.
**Migration:** Replace `import { Eq } from "@suaveplan/audio-plugins-fx/eq"` with `import { Eq } from "@suaveplan/audio-fx-plugin-eq"`. Replace `import { PluginChain } from "@suaveplan/audio-plugins-fx/chain"` with `import { PluginChain } from "@suaveplan/audio-fx-plugin-base/chain"`. No runtime behavior changes.
<!-- markdownlint-enable MD024 -->