---
phase: 27-mlx-dependency-removal
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - plugins/claude-tts-companion/Package.swift
  - plugins/claude-tts-companion/Sources/CompanionCore/WordTimingAligner.swift
  - plugins/claude-tts-companion/Tests/CompanionCoreTests/WordTimingAlignerTests.swift
autonomous: true
requirements: [DEP-01, DEP-02, DEP-03, DEP-04, DEP-05]

must_haves:
  truths:
    - "Package.swift has zero references to kokoro-ios, mlx-swift, or MLXUtilsLibrary"
    - "No Swift source file in Sources/ imports KokoroSwift, MLX, or MLXUtilsLibrary"
    - "swift build -c release succeeds with zero errors"
    - "swift test passes with no MToken-related compilation errors"
  artifacts:
    - path: "plugins/claude-tts-companion/Package.swift"
      provides: "Clean dependency list without MLX packages"
      contains: "FlyingFox"
    - path: "plugins/claude-tts-companion/Sources/CompanionCore/WordTimingAligner.swift"
      provides: "Word timing alignment without MLXUtilsLibrary import"
  key_links:
    - from: "Package.swift"
      to: "CompanionCore target"
      via: "dependency list"
      pattern: "SwiftTelegramBot.*Logging.*FlyingFox"
---

<objective>
Remove kokoro-ios, mlx-swift, and MLXUtilsLibrary from Package.swift and all source imports.

Purpose: TTS is now delegated to the Python MLX server. The Swift binary no longer needs MLX dependencies, which add ~5-7MB to the binary and pull in a large dependency tree. Removing them produces a leaner binary under 20MB.

Output: Clean Package.swift, no MLX imports in Sources/ or Tests/, passing build and tests.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@plugins/claude-tts-companion/Package.swift
@plugins/claude-tts-companion/Sources/CompanionCore/WordTimingAligner.swift
@plugins/claude-tts-companion/Tests/CompanionCoreTests/WordTimingAlignerTests.swift

<interfaces>
<!-- Key types and contracts the executor needs. -->

From Package.swift (lines to REMOVE):

```swift
// Line 11: .package(url: "https://github.com/terrylica/kokoro-ios.git", exact: "1.0.16"),
// Line 12: .package(url: "https://github.com/mlalma/MLXUtilsLibrary.git", exact: "0.0.6"),
// Line 13: .package(url: "https://github.com/ml-explore/mlx-swift", exact: "0.30.2"),
// Line 29: .product(name: "KokoroSwift", package: "kokoro-ios"),
// Line 30: .product(name: "MLXUtilsLibrary", package: "MLXUtilsLibrary"),
// Line 31: .product(name: "MLX", package: "mlx-swift"),
```

From WordTimingAligner.swift:

- `import MLXUtilsLibrary` (line 4) — only file in Sources/ that imports it
- `extractTimingsFromTokens(_ tokens: [MToken]?)` — dead code, MToken comes from MLXUtilsLibrary
- `alignOnsetsToWords(native:subtitleWords:audioDuration:)` — dead code, uses NativeTimings from MToken extraction
- `alignTimingsToWords(text:audioDuration:tokenArray:)` — dead code, takes `[MToken]?` parameter
- `extractWordTimings(text:audioDuration:)` — KEEP, this is the active character-weighted path used by tests and streaming pipeline

From Tests/WordTimingAlignerTests.swift:

- `import MLXUtilsLibrary` (line 2) — imports MToken for test fixtures
- Tests for `extractTimingsFromTokens`, `alignOnsetsToWords` — dead code tests, remove
- Tests for `extractWordTimings` — KEEP, tests the active path
  </interfaces>

</context>

<tasks>

<task type="auto">
  <name>Task 1: Remove MLX packages from Package.swift and strip dead MToken code from WordTimingAligner</name>
  <files>
    plugins/claude-tts-companion/Package.swift
    plugins/claude-tts-companion/Sources/CompanionCore/WordTimingAligner.swift
  </files>
  <read_first>
    plugins/claude-tts-companion/Package.swift
    plugins/claude-tts-companion/Sources/CompanionCore/WordTimingAligner.swift
  </read_first>
  <action>
**Package.swift changes (per DEP-01, DEP-02, DEP-03):**

1. Remove these 3 lines from the `dependencies:` array:
   - `.package(url: "https://github.com/terrylica/kokoro-ios.git", exact: "1.0.16")`
   - `.package(url: "https://github.com/mlalma/MLXUtilsLibrary.git", exact: "0.0.6")`
   - `.package(url: "https://github.com/ml-explore/mlx-swift", exact: "0.30.2")`

2. Remove these 3 lines from the CompanionCore target `dependencies:` array:
   - `.product(name: "KokoroSwift", package: "kokoro-ios")`
   - `.product(name: "MLXUtilsLibrary", package: "MLXUtilsLibrary")`
   - `.product(name: "MLX", package: "mlx-swift")`

3. Keep all other dependencies (swift-telegram-sdk, swift-log, FlyingFox, swift-testing, CSherpaOnnx) and all linker settings unchanged.

**WordTimingAligner.swift changes:**

1. Remove `import MLXUtilsLibrary` (line 4).

2. Remove the `NativeTimings` struct (dead — was populated from MToken extraction).

3. Remove `extractTimingsFromTokens(_ tokens: [MToken]?) -> NativeTimings?` (dead — MToken comes from MLXUtilsLibrary which is being removed).

4. Remove `alignOnsetsToWords(native:subtitleWords:audioDuration:) -> [TimeInterval]?` (dead — consumed NativeTimings).

5. Remove `normalizeForAlignment(_:)` helper (dead — only used by alignOnsetsToWords).

6. Remove `alignTimingsToWords(text:audioDuration:tokenArray:) -> ResolvedTimings` (dead — takes `[MToken]?` parameter from removed library).

7. KEEP `extractWordTimings(text:audioDuration:) -> [TimeInterval]` — this is the active character-weighted path used by StreamingPipelineTests and the TTS pipeline.

8. KEEP the `ResolvedTimings` struct ONLY if `extractWordTimings` returns it. If `extractWordTimings` returns `[TimeInterval]` directly, ResolvedTimings can be removed too.

9. Update doc comments — remove references to "MToken", "kokoro-ios", "NativeTimings". The file now provides character-weighted word timing only.
   </action>

  <verify>
    <automated>cd plugins/claude-tts-companion && grep -r 'import MLX\|import KokoroSwift\|import MLXUtilsLibrary' Sources/ ; echo "EXIT:$?"</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c 'kokoro-ios\|mlx-swift\|MLXUtilsLibrary' plugins/claude-tts-companion/Package.swift` returns 0
    - `grep -rc 'import MLX\|import KokoroSwift\|import MLXUtilsLibrary' plugins/claude-tts-companion/Sources/` returns 0
    - `grep -c 'MToken' plugins/claude-tts-companion/Sources/CompanionCore/WordTimingAligner.swift` returns 0
    - `extractWordTimings` function still exists in WordTimingAligner.swift
  </acceptance_criteria>
  <done>Package.swift has zero MLX dependencies; WordTimingAligner has no MLXUtilsLibrary import and no MToken references; extractWordTimings character-weighted path preserved</done>
</task>

<task type="auto">
  <name>Task 2: Update tests, resolve Package.resolved, verify build and binary size</name>
  <files>
    plugins/claude-tts-companion/Tests/CompanionCoreTests/WordTimingAlignerTests.swift
  </files>
  <read_first>
    plugins/claude-tts-companion/Tests/CompanionCoreTests/WordTimingAlignerTests.swift
    plugins/claude-tts-companion/Tests/CompanionCoreTests/StreamingPipelineTests.swift
  </read_first>
  <action>
**Test file changes:**

1. In `WordTimingAlignerTests.swift`:
   - Remove `import MLXUtilsLibrary` (line 2)
   - Remove the `makeMToken` helper function (creates MToken test fixtures)
   - Remove ALL test functions that call `extractTimingsFromTokens` or `alignOnsetsToWords` — these test the dead MToken path
   - KEEP all tests that call `extractWordTimings` — these test the active character-weighted path
   - If the file becomes empty (no tests left), delete it entirely

2. In `StreamingPipelineTests.swift` — should need NO changes (it already uses `extractWordTimings`, not MToken-based functions). Verify by reading the file.

3. Remove any other test files that import MLXUtilsLibrary: `grep -r 'import MLXUtilsLibrary' Tests/`

**Build verification (per DEP-04):**

1. Run `cd plugins/claude-tts-companion && swift package resolve` to regenerate Package.resolved without MLX dependencies.

2. Run `swift build -c release` — must succeed with zero errors and zero warnings about MLX symbols.

3. Run `swift test` — must pass. All remaining tests use the character-weighted path.

**Binary size check (per DEP-05):**

1. After release build, run: `strip .build/release/claude-tts-companion && ls -la .build/release/claude-tts-companion` — binary must be under 20MB.

2. Verify no MLX symbols: `nm .build/release/claude-tts-companion 2>/dev/null | grep -i 'mlx\|kokoro' | head -5` — should return empty.
   </action>

  <verify>
    <automated>cd plugins/claude-tts-companion && swift build -c release 2>&1 | tail -5 && swift test 2>&1 | tail -10</automated>
  </verify>
  <acceptance_criteria>
    - `swift build -c release` exits 0 with no MLX-related errors
    - `swift test` exits 0 with all tests passing
    - `grep -rc 'import MLXUtilsLibrary' plugins/claude-tts-companion/Tests/` returns 0
    - Stripped release binary under 20MB (20971520 bytes)
    - `nm .build/release/claude-tts-companion 2>/dev/null | grep -ic 'mlx'` returns 0
  </acceptance_criteria>
  <done>Tests pass without MLX dependencies; release binary builds clean under 20MB; no MLX symbols in binary</done>
</task>

</tasks>

<verification>
1. `grep -r 'kokoro-ios\|mlx-swift\|MLXUtilsLibrary\|import MLX\|import KokoroSwift' plugins/claude-tts-companion/Package.swift plugins/claude-tts-companion/Sources/ plugins/claude-tts-companion/Tests/` returns zero matches
2. `cd plugins/claude-tts-companion && swift build -c release` succeeds
3. `cd plugins/claude-tts-companion && swift test` passes
4. Stripped binary under 20MB
</verification>

<success_criteria>

- DEP-01: kokoro-ios removed from Package.swift, no KokoroSwift import in Sources/
- DEP-02: mlx-swift removed from Package.swift, no MLX import in Sources/
- DEP-03: MLXUtilsLibrary removed from Package.swift, no MLXUtilsLibrary import anywhere
- DEP-04: `swift build -c release` succeeds with zero MLX symbols
- DEP-05: Stripped release binary under 20MB
  </success_criteria>

<output>
After completion, create `.planning/phases/27-mlx-dependency-removal/27-01-SUMMARY.md`
</output>
