# Tasks: `@suaveplan/audio-codec-ffmpeg-wasm` Ordered implementation checklist for the `enterprise-audio-codec-ffmpeg-wasm` change. Every numbered task corresponds to scenarios in `./specs/audio-codec-ffmpeg-wasm/spec.md`. Do not start a task whose prerequisites are still pending in `STATUS.md`. --- ## 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. Scaffold the package - [ ] 1.1 Add `@ffmpeg/ffmpeg` and `@ffmpeg/core` to the root `package.json` `catalog` block at exact pinned versions. Note in the PR description that this is the only catalog addition required by this change. - [ ] 1.2 Create `packages/media/audio/audio-codec-ffmpeg-wasm/` with `package.json` (`"name": "@suaveplan/audio-codec-ffmpeg-wasm"`, `"version": "0.1.0"`, `"browser": true`, `"sideEffects": false`, `"type": "module"`), workspace deps on `@suaveplan/audio-codecs`, `audio-core`, `error`, `types`, `streams` all at `workspace:^`, runtime deps on `@ffmpeg/ffmpeg`, `@ffmpeg/core`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`, and `optionalDependencies: { "@ffmpeg/core-mt": "" }`. - [ ] 1.3 Declare one `exports` entry per feature module plus the root `.` barrel (see proposal "Module layout"). - [ ] 1.4 Add `tsconfig.json` extending the shared base; add `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config` with `pool: 'forks'` and `setupFiles: ['@suaveplan/testing/polyfills']`. Configure `happy-dom` for any test that touches `Worker`. - [ ] 1.5 Add `README.md` at the package root only (no category READMEs). - [ ] 1.6 Run `bun scripts/folder-reorg/generate-manifest.ts` to refresh `MANIFEST.md`. ## 2. Types and error codes - [ ] 2.1 Author `src/codec/ffmpeg-wasm-codec.types.ts` with `FfmpegWasmCodecOptions`, `LoadedCore`, `WorkerMessage` union, and internal pipeline types. - [ ] 2.2 Author `src/codec/ffmpeg-wasm-codec.schemas.ts` exporting `FfmpegWasmCodecOptionsSchema` (Zod) — uses `import z from "zod"` per `MEMORY.md`. Reject `coreUrl` and `corePath` set simultaneously with `.superRefine`. - [ ] 2.3 Author `src/error-codes.ts` defining `AudioCodecFfmpegWasmError`, `AudioCodecFfmpegWasmErrorCode`, `AUDIO_CODEC_FFMPEG_WASM_ERROR_METADATA`, and `registerAudioCodecFfmpegWasmErrorCodes()` via `@suaveplan/error`'s `defineCodes`. Cover all 9 codes from the spec. - [ ] 2.4 Author `src/error-codes.test.ts` asserting every code is reachable from a real call site (one assertion per code; cross-references the other test files). ## 3. Worker host (`src/worker/`) - [ ] 3.1 Implement `worker-host.ts`: wraps `@ffmpeg/ffmpeg`'s `FFmpeg` instance, owns the `Worker` lifecycle, exposes `submitDecode`, `submitEncode`, `terminate`, and a `progress` event hook. - [ ] 3.2 `AbortSignal` plumbing: pre-aborted signal short-circuits before any worker message; mid-call abort calls `worker.terminate()` and rejects pending calls with `AUDIO_CODEC_FFMPEG_WASM.ABORTED`. - [ ] 3.3 Timeout watchdog using injectable `Clock`; defaults 60s decode / 120s encode; maps to `AUDIO_CODEC_FFMPEG_WASM.TIMEOUT`. - [ ] 3.4 Crash + OOM detection: parse stderr for `Cannot enlarge memory arrays` / `OutOfMemory` → `AUDIO_CODEC_FFMPEG_WASM.MEMORY_LIMIT_EXCEEDED`; pre-`load` worker error → `AUDIO_CODEC_FFMPEG_WASM.WORKER_SPAWN_FAILED`. - [ ] 3.5 Honour `opts.workerFactory` test seam. - [ ] 3.6 Tests in `worker-host.test.ts` covering scenarios from spec Requirements "Worker isolation", "AbortSignal propagation", "Timeout watchdog", and "Memory-limit detection". ## 4. Core loader (`src/core-loader/`) - [ ] 4.1 Implement `core-loader.ts`: resolve core URL (precedence corePath → coreUrl → bundled default), memoise the loaded core per codec instance. - [ ] 4.2 COI gate: when `enableMultithread === true` and `globalThis.crossOriginIsolated !== true`, throw `AUDIO_CODEC_FFMPEG_WASM.CROSS_ORIGIN_ISOLATION_REQUIRED` *before* attempting `import("@ffmpeg/core-mt")`. - [ ] 4.3 Optional-dep fallback: when COI is true but `import("@ffmpeg/core-mt")` rejects, log a warning via `opts.logger` and fall back to `@ffmpeg/core`. Do not throw. - [ ] 4.4 Failure path: any load failure throws `AUDIO_CODEC_FFMPEG_WASM.CORE_LOAD_FAILED` with the resolved URL in `context`. - [ ] 4.5 Tests in `core-loader.test.ts` covering "Lazy core load on first use" and "Cross-origin-isolation gate" scenarios. ## 5. Decode pipeline (`src/decode/`) - [ ] 5.1 Implement `decode-pipeline.ts`: sniff format from magic bytes → `ffmpeg.writeFile("input.", bytes)` → run probe pass for `sampleRate`/`channels` → run `-f f32le` extraction → `ffmpeg.readFile("output.raw")` → deinterleave to `Float32Array[]`. - [ ] 5.2 MEMFS cleanup: `deleteFile` both entries on success and on failure (try/finally). - [ ] 5.3 ffmpeg non-zero exit → `AUDIO_CODEC_FFMPEG_WASM.DECODE_FAILED` with stderr tail. - [ ] 5.4 Tests in `decode-pipeline.test.ts` covering "Decode pipeline" scenarios. ## 6. Encode pipeline (`src/encode/`) - [ ] 6.1 Implement `encode-pipeline.ts`: per-format command builders for `wav`, `mp3`, `ogg`, `opus`, `flac`, `aac`, `webm`, honouring `bitrate`/`quality` options. - [ ] 6.2 Interleave `Float32Array[]` → single `Float32Array` → `Uint8Array` view → MEMFS write. - [ ] 6.3 Unsupported format check **before** posting any worker message → `AUDIO_CODEC_FFMPEG_WASM.UNSUPPORTED_CODEC`. - [ ] 6.4 MEMFS cleanup (try/finally on both entries). - [ ] 6.5 ffmpeg non-zero exit → `AUDIO_CODEC_FFMPEG_WASM.ENCODE_FAILED`. - [ ] 6.6 Tests in `encode-pipeline.test.ts` covering "Encode pipeline" scenarios for at least `wav`, `mp3`, and `opus`. ## 7. Streaming emulation (`src/streaming/`) - [ ] 7.1 Implement `streaming-decoder.ts`: `write(chunk)` appends to internal buffer and returns `null`; `flush()` runs through the decode pipeline once. - [ ] 7.2 Implement `streaming-encoder.ts` as the symmetric mirror. - [ ] 7.3 Idempotent `flush()`: subsequent calls return `null` / empty bytes. - [ ] 7.4 Progress events fire only during `flush()`. - [ ] 7.5 Tests in `streaming-decoder.test.ts` and `streaming-encoder.test.ts` covering "Buffered streaming emulation" scenarios. ## 8. Codec assembly (`src/codec/`) - [ ] 8.1 Implement `ffmpeg-wasm-codec.ts`: `FfmpegWasmCodec` class wiring constructor validation (Zod) → core loader → worker host → decode/encode pipelines → `createDecoder` / `createEncoder` → `dispose`. - [ ] 8.2 `canDecode(bytes)` uses `@suaveplan/audio-codecs`'s magic sniff plus the probed encoder list to filter unsupported variants. - [ ] 8.3 Progress callback wiring with try/catch around the user callback; exceptions logged via `opts.logger`, never aborts the call. - [ ] 8.4 Tests in `ffmpeg-wasm-codec.test.ts` covering "AudioCodec contract conformance", "Progress callback wiring", "Zod boundary validation", and OTel scenarios. ## 9. Register helper (`src/register/`) - [ ] 9.1 Implement `register.ts`: validates options, constructs codec, calls `registry.register(codec)`, returns the instance. - [ ] 9.2 Tests in `register.test.ts` covering the install scenario and the Zod rejection scenario. ## 10. OpenTelemetry instrumentation - [ ] 10.1 Wire `audio-codec-ffmpeg-wasm.decode` and `audio-codec-ffmpeg-wasm.encode` spans, with `format` and `multithread` attributes. - [ ] 10.2 Increment `audio_codec_ffmpeg_wasm_decode_total` / `audio_codec_ffmpeg_wasm_encode_total` with `format` and `outcome` labels. - [ ] 10.3 Record `audio_codec_ffmpeg_wasm_decode_duration_ms` / `audio_codec_ffmpeg_wasm_encode_duration_ms` driven by `opts.clock`. - [ ] 10.4 Verify no import-time side effects via `tests/purity.test.ts`. ## 11. Tests harness - [ ] 11.1 Every test file imports runner primitives from `@suaveplan/testing/runner` only — never `vitest`, never `bun:test` (rule 11). - [ ] 11.2 Use `happy-dom` for tests that touch `Worker`; supply a hand-rolled `Worker` shim that lets us script worker messages and `error` events. - [ ] 11.3 Use a `workerFactory` test seam to inject fakes for unit tests; reserve a real-worker smoke test for `tests/integration.test.ts` under Bun-runs-in-Node mode. - [ ] 11.4 Every `it()` / `test()` carries `{ timeout: 5000 }` (`30000` for the integration suite). No mocks of `@ffmpeg/ffmpeg` itself — only the worker boundary is faked. - [ ] 11.5 Property test: `decode(encode(pcm)) ≈ pcm` for lossless formats (`wav`, `flac`). - [ ] 11.6 `tests/purity.test.ts` greps built `dist/` for forbidden Node API references and for static imports of `@ffmpeg/core-mt`. - [ ] 11.7 `tests/exports.test.ts` asserts each subpath export resolves and that no test file imports `vitest` or `bun:test` directly. ## 12. Benchmarks - [ ] 12.1 `src/__benchmarks__/decode.bench.ts` — decode a 5-minute WAV and a 5-minute MP3 with `mitata`. - [ ] 12.2 `src/__benchmarks__/encode.bench.ts` — encode 5-minute PCM to MP3, OPUS, and FLAC. - [ ] 12.3 Document the multi-thread vs single-thread delta in the benchmark output comments. ## 13. Documentation - [ ] 13.1 Every implementation `.ts` has a sibling `.md` (Purpose ≥2 sentences, Features, Usage: Basic, Usage: Advanced, API Reference, Implementation Notes; ≥200 words excl. code; both simple AND advanced examples). - [ ] 13.2 Excluded from the `.md` rule: `.test.ts`, `.types.ts` (inside a feature dir), barrel `index.ts`, `*.schemas.ts` (Zod schemas only). - [ ] 13.3 `README.md` covers: install, quick-start `register(registry)` usage, the COI requirement for `enableMultithread`, the 30 MB lazy-load disclaimer, and the buffered-streaming honesty note. - [ ] 13.4 `bunx turbo docs:check --filter=@suaveplan/audio-codec-ffmpeg-wasm` is green. ## 14. Pre-publish gates - [ ] 14.1 `bunx turbo lint typecheck test --filter=@suaveplan/audio-codec-ffmpeg-wasm` is green. - [ ] 14.2 `bunx turbo test:coverage --filter=@suaveplan/audio-codec-ffmpeg-wasm` reports 100% on all touched modules with `provider: 'istanbul'`. - [ ] 14.3 `bun scripts/lint/check-test-runner-imports.ts` passes for the package. - [ ] 14.4 No `biome-ignore` comments anywhere in `src/` or `tests/` (rule 9). - [ ] 14.5 No `export *` in any non-root barrel; root barrel uses explicit named exports or namespace re-exports (rule 10). - [ ] 14.6 `bun scripts/folder-reorg/generate-manifest.ts` re-run; resulting `MANIFEST.md` diff committed. ## 15. Fold the spec - [ ] 15.1 Fold `specs/audio-codec-ffmpeg-wasm/spec.md` into `openspec/specs/audio-codec-ffmpeg-wasm/spec.md` per §14 archival workflow. `openspec/specs/` is untouched until this step. ## 16. Archive - [ ] 16.1 Move `openspec/changes/enterprise-audio-codec-ffmpeg-wasm/` to `openspec/archive/-enterprise-audio-codec-ffmpeg-wasm/` per §14. - [ ] 16.2 Run `openspec validate` after the move to confirm the canonical spec parses.