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,160 @@
# Design — audio-shipped-file-size-cleanup
This change is a pure mechanical refactor across three packages. The design is the **split strategy** per file: every public symbol stays exported from its current module path; the splits happen *below* the public surface.
---
## 1. `@suaveplan/audio-timeline` — `engine/engine.ts` (548 → ≤300)
`TimelineEngine` is the single class in this file and currently owns:
- Construction + adapter wiring (`setAdapter`, `dispose`)
- Track CRUD (`addTrack`, `removeTrack`, `updateTrack`, `setTracks`)
- Clip CRUD (`addClip`, `removeClip`, `updateClip`, `splitClip`, `joinClip`)
- Mixer mutations (`setMasterVolume`, `setTrackVolume`, `setTrackMute`, `setTrackSolo`, `setTrackPan`)
- Transport (`play`, `pause`, `stop`, `seek`, `setLoop`, `setTempo`)
- Snapshot / restore + undo/redo dispatch
- Event emission
### Proposed split
Keep `TimelineEngine` in `engine.ts` as the public class, but extract helper modules called from its methods:
```text
src/engine/
engine.ts ← class shell + delegating methods (≤300)
engine-tracks.ts ← addTrack/removeTrack/updateTrack/setTracks helpers
engine-tracks.md
engine-clips.ts ← addClip/removeClip/updateClip/splitClip/joinClip helpers
engine-clips.md
engine-mixer.ts ← setMasterVolume/setTrackVolume/Mute/Solo/Pan helpers
engine-mixer.md
engine-transport.ts ← play/pause/stop/seek/setLoop/setTempo helpers
engine-transport.md
engine.test.ts ← class-level invariants only (≤500)
engine-tracks.test.ts ← extracted track tests
engine-clips.test.ts ← extracted clip tests
engine-mixer.test.ts ← extracted mixer tests
engine-transport.test.ts ← extracted transport tests
```
Each helper module exports pure functions of `(state, args) => newState` (where `state` is `TimelineEngine`'s internal record) plus any side-effect-free derived values. `TimelineEngine` methods become 1–3 liners that call the helper and forward the result. This pattern matches how `audio-core`'s `process/` directory is structured.
The original `engine.test.ts` splits along the same four axes; the residual `engine.test.ts` keeps construction, adapter wiring, snapshot/restore, undo/redo dispatch, and event emission tests — the cross-cutting concerns no single helper owns.
---
## 2. `@suaveplan/audio-transport` — `transport/transport.ts` (419 → ≤300)
`Transport` is the single class and owns:
- Construction + clock injection
- BPM + time-signature + PPQ state
- Tick advance + lookahead scheduling loop
- Schedule / cancel API
- Loop region + swing
- Tempo automation
### Proposed split
```text
src/transport/
transport.ts ← class shell (≤300)
transport-schedule.ts ← schedule/cancel/lookahead helpers
transport-schedule.md
transport-tempo.ts ← BPM + tempo automation + tick conversion helpers
transport-tempo.md
transport.test.ts ← stays as-is (already under cap)
```
No test splitting needed — `transport.test.ts` is already within the 500-line cap.
---
## 3. `@suaveplan/audio-metadata` — `parsers/id3v2/id3v2.ts` (536 → ≤300)
`id3v2.ts` is the most over-budget src file. It owns:
- Magic-byte detection
- Header + extended-header parsing
- Frame iteration
- Per-frame-type decoders (TXXX, T..., COMM, USLT, APIC, UFID, WCOM, WCOP, …)
- Text-encoding decoders (ISO-8859-1, UTF-16, UTF-16BE, UTF-8)
- Unsynchronisation reversal
### Proposed split
```text
src/parsers/id3v2/
id3v2.ts ← public parseId3v2() + frame dispatch (≤300)
id3v2-header.ts ← header + extended-header parsing
id3v2-header.md
id3v2-frames-text.ts ← T... / TXXX text frame decoders
id3v2-frames-text.md
id3v2-frames-binary.ts ← APIC / UFID / GEOB binary frame decoders
id3v2-frames-binary.md
id3v2-frames-url.ts ← W... URL frame decoders
id3v2-frames-url.md
id3v2-text-encoding.ts ← ISO-8859-1 / UTF-16 / UTF-16BE / UTF-8 decoders
id3v2-text-encoding.md
id3v2-unsync.ts ← unsynchronisation reversal
id3v2-unsync.md
id3v2.test.ts ← integration parses on real fixture files (≤500)
id3v2-header.test.ts ← header-specific
id3v2-frames-text.test.ts ← text-frame-specific
id3v2-frames-binary.test.ts ← binary-frame-specific
id3v2-frames-url.test.ts ← url-frame-specific
id3v2-text-encoding.test.ts ← encoding-specific
id3v2-unsync.test.ts ← unsync-specific
```
The split mirrors the spec sections of the ID3v2.4 standard, so the file boundaries match how future maintainers will reason about the format.
### `id3v2.test.ts` (1340 → 7 files of ≤500 each)
The current `id3v2.test.ts` mixes header tests, every frame type, encoding round-trips, and integration parses. Splitting by frame family (per the source split above) gives ~150–200 lines per file. The residual `id3v2.test.ts` keeps the end-to-end "parse a real fixture file" cases.
---
## 4. `@suaveplan/audio-metadata` — `parsers/mp4-atom/mp4-atom.ts` (351 → ≤300)
Lightest split. The file owns atom-walking + per-atom decoders (`ftyp`, `moov`, `mvhd`, `mdat`, `ilst` and the iTunes metadata sub-atoms). Extract the iTunes-specific decoders:
```text
src/parsers/mp4-atom/
mp4-atom.ts ← public parseMp4Atom() + container walker (≤300)
mp4-atom-itunes.ts ← iTunes/ilst metadata decoders
mp4-atom-itunes.md
mp4-atom.test.ts ← container + non-iTunes tests (≤500)
mp4-atom-itunes.test.ts ← iTunes-specific
```
---
## 5. `@suaveplan/audio-metadata` — `parsers/ogg-vorbis/ogg-vorbis.test.ts` (803 → ≤500)
Source file is already within cap. Only the test file needs splitting:
```text
src/parsers/ogg-vorbis/
ogg-vorbis.ts ← unchanged
ogg-vorbis.test.ts ← page parsing + comment header (≤500)
ogg-vorbis-frames.test.ts ← extracted frame-level tests
```
---
## 6. Cross-cutting rules for the split
- **Public surface is invariant.** Every existing `export` from `<pkg>/src/index.ts` must continue to resolve to the same symbol with the same signature. The split is *internal*.
- **Subpath exports unchanged.** The `package.json` `exports` map for each package keeps every existing entry. New internal helper modules are NOT added to `exports` — they are implementation details.
- **Barrels use explicit named re-exports** (§6a). No `export * from` in new feature barrels.
- **Sibling `.md` files** added for every new `.ts` per §4 (200-word minimum, six required sections).
- **Coverage stays 100/100/100/100** per `vitest.config.ts` thresholds.
- **No new behavior.** A diff of `dist/*.d.ts` must show only path changes — no type-shape changes.
## 7. Risk
Low. The packages have no published consumers; the refactor is mechanical; the tests are the contract and they all must continue to pass. The largest file (`id3v2.test.ts` at 1340 lines) is the highest-leverage win: shrinking it makes the format-spec correspondence explicit and gives future maintainers per-frame-family focus.
The one non-mechanical risk is in `audio-timeline/engine.ts` — extracting helper modules implies extracting a `TimelineEngineState` shape, which is currently inlined as private class fields. The helpers operate on that shape; if the extraction inadvertently widens the visibility of private state, the public surface widens. Mitigation: keep `TimelineEngineState` as a non-exported type in `engine-state.ts` (or inline in `engine.ts`) and pass it explicitly to helpers; do not export it.

View File

@@ -0,0 +1,77 @@
# OpenSpec Change: audio-shipped-file-size-cleanup
- **Change ID**: audio-shipped-file-size-cleanup
- **Packages**: `@suaveplan/audio-timeline`, `@suaveplan/audio-transport`, `@suaveplan/audio-metadata`
- **Category**: maintenance (refactor — no behavior change)
- **Status**: PROPOSED
- **Date**: 2026-05-15
- **Design**: `./design.md`
- **Spec deltas**: `./specs/audio-timeline/spec.md`, `./specs/audio-transport/spec.md`, `./specs/audio-metadata/spec.md`
---
## Why
A post-ship audit of the three universal audio packages flagged source and test files exceeding `CLAUDE.md` Rule §3 / `openspec/AGENTS.md` §3 (300-line hard cap on source, 500-line cap on tests):
| Package | File | Lines | Cap | Over |
|---|---|---|---|---|
| audio-timeline | `src/engine/engine.ts` | 548 | 300 | +248 |
| audio-timeline | `src/engine/engine.test.ts` | 819 | 500 | +319 |
| audio-transport | `src/transport/transport.ts` | 419 | 300 | +119 |
| audio-metadata | `src/parsers/id3v2/id3v2.ts` | 536 | 300 | +236 |
| audio-metadata | `src/parsers/id3v2/id3v2.test.ts` | 1340 | 500 | +840 |
| audio-metadata | `src/parsers/mp4-atom/mp4-atom.ts` | 351 | 300 | +51 |
| audio-metadata | `src/parsers/mp4-atom/mp4-atom.test.ts` | 670 | 500 | +170 |
| audio-metadata | `src/parsers/ogg-vorbis/ogg-vorbis.test.ts` | 803 | 500 | +303 |
The 1340-line `id3v2.test.ts` (more than 2.6× the cap) is the most egregious. All eight files passed their own gates because the size budget is currently enforced by manual audit rather than a CI check.
In parallel, the audit found a §7 / Rule 11 violation that was already migrated as a separate change (audio-timeline test files importing directly from `vitest` — the `bench` import was wrong-framework too and is now migrated to mitata). That work is out of scope for this proposal.
## What changes
- REFACTOR: Split each oversized source file at natural responsibility boundaries while preserving the **exact public API surface** (every existing `export` keeps its name, signature, and semantics).
- REFACTOR: Split each oversized test file into co-located sibling `*.test.ts` modules, one per cohesive behavior cluster (e.g. `id3v2/frames.test.ts`, `id3v2/text-encoding.test.ts`, `id3v2/extended-header.test.ts`).
- ADDED: Per-package barrel updates if new sub-modules are created — explicit named re-exports only (§6a).
- ADDED: Sibling `.md` docs for any new implementation files; the original file's `.md` stays as the surface-level overview.
- NO BEHAVIOR CHANGE. Test counts must not decrease. Coverage must remain 100/100/100/100.
## Out of scope
- API redesigns or new features.
- The runner-import migration (already done in audio-timeline).
- File-size cleanups in other packages (handled separately if needed).
- Introducing a new CI gate for the 300/500 caps — recommended as a follow-up.
## Source mapping
This is a pure-genesis maintenance change; no upstream reference. The size caps come from `CLAUDE.md` (§Quality) and `openspec/AGENTS.md` §3.
## Impact
**Breaking changes**: none. Public API surface, package exports, and `package.json` `exports` entries are preserved verbatim. Internal sub-module paths may shift, but they were never part of the public contract.
### Affected dependents
The three packages are universal-tier and currently have **no published consumers** on genesis (`bunx turbo ls --filter=...@suaveplan/audio-timeline` / `audio-transport` / `audio-metadata` returns empty). Future consumers (`audio-engine-browser`, `audio-engine-server`, react-audio*) consume the public surface, which this change does not touch.
- No dependents (verified by `bunx turbo ls --filter=...@suaveplan/audio-timeline`).
- No dependents (verified by `bunx turbo ls --filter=...@suaveplan/audio-transport`).
- No dependents (verified by `bunx turbo ls --filter=...@suaveplan/audio-metadata`).
## Verification approach
For each touched package:
1. `bunx turbo lint typecheck test --filter=@suaveplan/<pkg>` — green.
2. `bunx turbo test:coverage --filter=@suaveplan/<pkg>` — 100/100/100/100.
3. `bunx turbo build --filter=@suaveplan/<pkg>` — green.
4. `find packages/audio/<pkg>/src -name "*.ts" ! -name "*.test.ts" | xargs awk 'END { if (NR > 300) print FILENAME, NR " lines" }'` — empty.
5. `find packages/audio/<pkg>/src packages/audio/<pkg>/tests -name "*.test.ts" | xargs awk 'END { if (NR > 500) print FILENAME, NR " lines" }'` — empty.
6. Diff the public `exports` map in `package.json` before/after — identical.
7. Diff `dist/` `.d.ts` files before/after — only path / source-map changes; no type-shape changes.
## Follow-up
Once this change archives, add a `scripts/lint/check-file-sizes.ts` CI gate so the caps are enforced automatically. That belongs in its own change — out of scope here.

View File

@@ -0,0 +1,70 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: audio-metadata (audio-shipped-file-size-cleanup)
**Package**: `@suaveplan/audio-metadata`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
## MODIFIED Requirements
### Requirement: id3v2 parser source modules respect the 300-line cap
The ID3v2 parser implementation MUST be split such that no single `.ts` file under `src/parsers/id3v2/` exceeds the 300-line hard cap. The public function export `parseId3v2` SHALL remain available from `@suaveplan/audio-metadata` with its existing signature and observable behavior across every supported frame type (T... text frames, TXXX user-defined text, W... URL frames, APIC attached picture, UFID file identifier, COMM comments, USLT lyrics, GEOB general encapsulated object) and every supported text encoding (ISO-8859-1, UTF-16, UTF-16BE, UTF-8) with unsynchronisation reversal applied where the header flag is set.
#### Scenario: every id3v2 source file is at or under the 300-line cap
- **WHEN** `find packages/audio/audio-metadata/src/parsers/id3v2 -name "*.ts" ! -name "*.test.ts" | xargs awk 'END { if (NR > 300) print FILENAME, NR }'` is run
- **THEN** the output is empty
#### Scenario: id3v2 fixture parses are unchanged
- **WHEN** `parseId3v2` is invoked on the existing fixture set used by `id3v2.test.ts`
- **THEN** every parse result is deep-equal to the pre-split result
**Verified by:** `src/parsers/id3v2/id3v2.test.ts::"every supported v2.4 fixture parses to the documented shape"` AND `tests/file-size.test.ts::"every id3v2 src file is under 300 lines"`
---
### Requirement: id3v2 test modules respect the 500-line cap
The ID3v2 test suite MUST be split such that no single `*.test.ts` file under `src/parsers/id3v2/` exceeds the 500-line hard cap. The aggregate test count across the split files SHALL be greater than or equal to the pre-split count. The residual `id3v2.test.ts` SHALL hold end-to-end fixture-parse cases; per-frame-family and per-encoding cases SHALL move to sibling files named after the family they cover.
#### Scenario: every id3v2 test file is at or under the 500-line cap
- **WHEN** `find packages/audio/audio-metadata/src/parsers/id3v2 -name "*.test.ts" | xargs awk 'END { if (NR > 500) print FILENAME, NR }'` is run
- **THEN** the output is empty
**Verified by:** `tests/file-size.test.ts::"every id3v2 test file is under 500 lines"` AND `tests/exports.test.ts::"aggregate id3v2 test count is greater than or equal to pre-split"`
---
### Requirement: mp4-atom parser modules respect the size caps
The MP4 atom parser implementation MUST be split such that no `.ts` source file under `src/parsers/mp4-atom/` exceeds the 300-line cap and no `.test.ts` file exceeds the 500-line cap. The public function export `parseMp4Atom` SHALL remain available from `@suaveplan/audio-metadata` with its existing signature and observable behavior across container atoms (`ftyp`, `moov`, `mvhd`, `mdat`, `ilst`) and iTunes metadata sub-atoms.
#### Scenario: every mp4-atom source file is at or under the 300-line cap
- **WHEN** `find packages/audio/audio-metadata/src/parsers/mp4-atom -name "*.ts" ! -name "*.test.ts" | xargs awk 'END { if (NR > 300) print FILENAME, NR }'` is run
- **THEN** the output is empty
#### Scenario: every mp4-atom test file is at or under the 500-line cap
- **WHEN** `find packages/audio/audio-metadata/src/parsers/mp4-atom -name "*.test.ts" | xargs awk 'END { if (NR > 500) print FILENAME, NR }'` is run
- **THEN** the output is empty
**Verified by:** `tests/file-size.test.ts::"every mp4-atom src file is under 300 lines"` AND `tests/file-size.test.ts::"every mp4-atom test file is under 500 lines"`
---
### Requirement: ogg-vorbis test modules respect the 500-line cap
The Ogg-Vorbis test suite MUST be split such that no `*.test.ts` file under `src/parsers/ogg-vorbis/` exceeds the 500-line hard cap. The aggregate test count across the split files SHALL be greater than or equal to the pre-split count. The source file is already within cap and is unchanged.
#### Scenario: every ogg-vorbis test file is at or under the 500-line cap
- **WHEN** `find packages/audio/audio-metadata/src/parsers/ogg-vorbis -name "*.test.ts" | xargs awk 'END { if (NR > 500) print FILENAME, NR }'` is run
- **THEN** the output is empty
**Verified by:** `tests/file-size.test.ts::"every ogg-vorbis test file is under 500 lines"`

View File

@@ -0,0 +1,44 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: audio-timeline (audio-shipped-file-size-cleanup)
**Package**: `@suaveplan/audio-timeline`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
## MODIFIED Requirements
### Requirement: TimelineEngine source modules respect the 300-line cap
The `TimelineEngine` implementation MUST be split across source modules such that no single `.ts` file under `src/engine/` exceeds the 300-line hard cap defined in `CLAUDE.md` Rule §3 / `openspec/AGENTS.md` §3. The public class export from `@suaveplan/audio-timeline` SHALL remain `TimelineEngine` with its existing constructor signature, public methods, and observable behavior (track CRUD, clip CRUD, mixer mutations, transport controls, snapshot/restore, undo/redo, event emission). Internal helper modules MAY be added under `src/engine/` but SHALL NOT appear in the `package.json` `exports` map; they are implementation detail.
#### Scenario: every engine source file is at or under the 300-line cap
- **WHEN** `find packages/audio/audio-timeline/src/engine -name "*.ts" ! -name "*.test.ts" | xargs awk 'END { if (NR > 300) print FILENAME, NR }'` is run
- **THEN** the output is empty
#### Scenario: public TimelineEngine surface is unchanged
- **WHEN** a consumer imports `TimelineEngine` from `@suaveplan/audio-timeline` and exercises every public method documented in `src/engine/engine.md`
- **THEN** every method exists with its existing signature and returns the existing shape
**Verified by:** `tests/exports.test.ts::"TimelineEngine surface is byte-identical to pre-split"` AND `tests/file-size.test.ts::"every engine src file is under 300 lines"`
---
### Requirement: TimelineEngine test modules respect the 500-line cap
The `TimelineEngine` test suite MUST be split such that no single `*.test.ts` file under `src/engine/` exceeds the 500-line hard cap. The aggregate test count across all split files SHALL be greater than or equal to the pre-split count — no test SHALL be deleted or weakened as part of the split. Test names SHALL describe behavior, not implementation (per `openspec/AGENTS.md` §7 "Test structure").
#### Scenario: every engine test file is at or under the 500-line cap
- **WHEN** `find packages/audio/audio-timeline/src/engine packages/audio/audio-timeline/tests -name "*.test.ts" | xargs awk 'END { if (NR > 500) print FILENAME, NR }'` is run
- **THEN** the output is empty
#### Scenario: aggregate test count is preserved
- **WHEN** `bunx turbo test --filter=@suaveplan/audio-timeline` is run after the split
- **THEN** the reported total test count is greater than or equal to the pre-split count captured in task 1.1
**Verified by:** `tests/file-size.test.ts::"every engine test file is under 500 lines"` AND `tests/exports.test.ts::"aggregate engine test count is greater than or equal to pre-split"`

View File

@@ -0,0 +1,26 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: audio-transport (audio-shipped-file-size-cleanup)
**Package**: `@suaveplan/audio-transport`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
## MODIFIED Requirements
### Requirement: Transport source modules respect the 300-line cap
The `Transport` implementation MUST be split such that no single `.ts` file under `src/transport/` exceeds the 300-line hard cap. The public class export from `@suaveplan/audio-transport` SHALL remain `Transport` with its existing constructor signature, public methods, and observable behavior (clock injection, BPM + time-signature + PPQ state, tick advance, lookahead scheduling, schedule/cancel, loop region, swing, tempo automation). Internal helper modules MAY be added under `src/transport/` but SHALL NOT appear in the `package.json` `exports` map.
#### Scenario: every transport source file is at or under the 300-line cap
- **WHEN** `find packages/audio/audio-transport/src/transport -name "*.ts" ! -name "*.test.ts" | xargs awk 'END { if (NR > 300) print FILENAME, NR }'` is run
- **THEN** the output is empty
#### Scenario: public Transport surface is unchanged
- **WHEN** a consumer imports `Transport` from `@suaveplan/audio-transport` and exercises every public method documented in `src/transport/transport.md`
- **THEN** every method exists with its existing signature and returns the existing shape
**Verified by:** `tests/exports.test.ts::"Transport surface is byte-identical to pre-split"` AND `tests/file-size.test.ts::"every transport src file is under 300 lines"`

View File

@@ -0,0 +1,82 @@
# Tasks — audio-shipped-file-size-cleanup
> Every checkbox MUST reflect work actually done in this change. No behavior change is permitted.
## 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
- [ ] 1.1 Capture baseline: `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/audio-timeline --filter=@suaveplan/audio-transport --filter=@suaveplan/audio-metadata` — record green output.
- [ ] 1.2 Snapshot `dist/**/*.d.ts` for the three packages to a temp dir for post-split comparison.
- [ ] 1.3 Capture each package's `package.json` `exports` map for post-split comparison.
## 2. `@suaveplan/audio-timeline` — `engine.ts` split
- [ ] 2.1 Extract `src/engine/engine-tracks.ts` + `.md` + `.test.ts` from `engine.ts` (track CRUD helpers).
- [ ] 2.2 Extract `src/engine/engine-clips.ts` + `.md` + `.test.ts` (clip CRUD helpers).
- [ ] 2.3 Extract `src/engine/engine-mixer.ts` + `.md` + `.test.ts` (mixer mutation helpers).
- [ ] 2.4 Extract `src/engine/engine-transport.ts` + `.md` + `.test.ts` (transport helpers).
- [ ] 2.5 Shrink `engine.ts` to delegating shell (≤300 lines); keep public class export at `src/engine/index.ts` unchanged.
- [ ] 2.6 Shrink `engine.test.ts` to construction / adapter wiring / snapshot / undo-redo / event cases only (≤500 lines).
- [ ] 2.7 Verify: `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/audio-timeline` green; test count ≥ pre-split count.
- [ ] 2.8 Diff `dist/**/*.d.ts` vs baseline — only path / source-map changes, no type-shape changes.
## 3. `@suaveplan/audio-transport` — `transport.ts` split
- [ ] 3.1 Extract `src/transport/transport-schedule.ts` + `.md` (schedule/cancel/lookahead helpers).
- [ ] 3.2 Extract `src/transport/transport-tempo.ts` + `.md` (BPM + tempo automation helpers).
- [ ] 3.3 Shrink `transport.ts` to delegating shell (≤300 lines).
- [ ] 3.4 Verify: `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/audio-transport` green; test count unchanged.
- [ ] 3.5 Diff `dist/**/*.d.ts` vs baseline — no type-shape changes.
## 4. `@suaveplan/audio-metadata` — `id3v2.ts` split (largest)
- [ ] 4.1 Extract `src/parsers/id3v2/id3v2-header.ts` + `.md` + `.test.ts` (header / extended header parsing).
- [ ] 4.2 Extract `src/parsers/id3v2/id3v2-text-encoding.ts` + `.md` + `.test.ts` (ISO-8859-1 / UTF-16 / UTF-16BE / UTF-8 decoders).
- [ ] 4.3 Extract `src/parsers/id3v2/id3v2-unsync.ts` + `.md` + `.test.ts` (unsynchronisation reversal).
- [ ] 4.4 Extract `src/parsers/id3v2/id3v2-frames-text.ts` + `.md` + `.test.ts` (T... / TXXX text frame decoders).
- [ ] 4.5 Extract `src/parsers/id3v2/id3v2-frames-binary.ts` + `.md` + `.test.ts` (APIC / UFID / GEOB binary frame decoders).
- [ ] 4.6 Extract `src/parsers/id3v2/id3v2-frames-url.ts` + `.md` + `.test.ts` (W... URL frame decoders).
- [ ] 4.7 Shrink `id3v2.ts` to public `parseId3v2()` + frame-dispatch only (≤300 lines).
- [ ] 4.8 Shrink `id3v2.test.ts` to end-to-end fixture parses only (≤500 lines).
- [ ] 4.9 Verify: per-file split tests cover the same code paths; aggregate test count ≥ pre-split (1340-line file's tests redistributed, none deleted).
## 5. `@suaveplan/audio-metadata` — `mp4-atom.ts` + tests split
- [ ] 5.1 Extract `src/parsers/mp4-atom/mp4-atom-itunes.ts` + `.md` + `.test.ts` (iTunes/ilst decoders).
- [ ] 5.2 Shrink `mp4-atom.ts` to public `parseMp4Atom()` + container walker (≤300 lines).
- [ ] 5.3 Shrink `mp4-atom.test.ts` to container + non-iTunes tests (≤500 lines).
## 6. `@suaveplan/audio-metadata` — `ogg-vorbis.test.ts` split
- [ ] 6.1 Extract `src/parsers/ogg-vorbis/ogg-vorbis-frames.test.ts` (frame-level tests).
- [ ] 6.2 Shrink `ogg-vorbis.test.ts` to page parsing + comment header tests (≤500 lines).
## 7. Per-package verification (post-split)
- [ ] 7.1 `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/audio-timeline` — 100/100/100/100.
- [ ] 7.2 `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/audio-transport` — 100/100/100/100.
- [ ] 7.3 `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/audio-metadata` — 100/100/100/100.
- [ ] 7.4 File-size sweep: `find packages/audio/audio-timeline/src packages/audio/audio-transport/src packages/audio/audio-metadata/src -name "*.ts" ! -name "*.test.ts" | xargs awk 'END { if (NR > 300) print FILENAME, NR " lines" }'` — empty.
- [ ] 7.5 Test-size sweep: same `find ... -name "*.test.ts" ...` with cap 500 — empty.
- [ ] 7.6 Diff `package.json` `exports` maps vs baseline — identical for all three packages.
- [ ] 7.7 Diff `dist/**/*.d.ts` vs baseline — only path/source-map changes.
## 8. Per-package docs refresh
- [ ] 8.1 Update `MANIFEST.md` via `bun scripts/folder-reorg/generate-manifest.ts` (new sub-modules surfaced).
- [ ] 8.2 No `STATUS.md` change — packages remain `✅` Completed.
- [ ] 8.3 `bunx markdownlint-cli2 --fix` over touched files; re-stage; verify exit 0 without `--fix`.
## 9. Archive
- [ ] 9.1 Fold `specs/audio-timeline/spec.md`, `specs/audio-transport/spec.md`, and `specs/audio-metadata/spec.md` into the canonical `openspec/specs/<capability>/spec.md` files.
- [ ] 9.2 Move `openspec/changes/audio-shipped-file-size-cleanup/` → `openspec/archive/YYYY-MM-DD-audio-shipped-file-size-cleanup/`.
## 10. Follow-up (separate change)
- [ ] 10.1 Open a `tooling-add-file-size-ci-gate` change that adds `scripts/lint/check-file-sizes.ts` (run from `bunx turbo lint`) so the 300/500 caps are enforced automatically. Out of scope here.