Last updated: 2026-09-25 · Skill: product-engineering:planning-implementation
- Spec and technical context:
- Product and design, all agreed on 2026-09-25: product brief (R1–R26, P1–P8, N1), UX flows (F1–F25, W1–W2, U1–U7) and handoff (slices S1–S5 and the spike, E1–E12).
- Technical baseline, revised 2026-09-25: phase-3 plan, phase-4 plan, phase-2 plan (P4–P7 recorded), and
spec/context/*(data models, Netflix, YouTube, dependencies). - Work packages: S1 tasks. Later slices get their own
spec/tasks/slice-N-*.tasks.mdonce the previous slice merges.
- Agreed architecture and decisions: E2, E4, E7, E8 and E9 were settled while planning S1. See Engineering decisions below.
- Current baseline:
main@2940f27. Phases 1–2 are merged, 87 tests pass and ruff is clean.cli.pystill prints "Hello World".- Environment: FFmpeg 9.0.1 on the Mac (M1 Max, 32 GB); no Whisper model cached yet.
- Importing Whisper takes 1.0–1.9 s.
- Integration owner: the session that owns the slice branch (one per slice). Igor approves each slice PR to
main.
Status legend: proposed = planned, not dispatchable · ready = prerequisites met, awaiting go-ahead · active · implemented · verified · reviewed · merged. Each status names its evidence.
| Package | Useful outcome / requirements | Plan | Prerequisites | Ownership / permitted parallelism | Status / evidence |
|---|---|---|---|---|---|
| S1.1 Contracts and test scaffolding | Error categories with hints and exit codes, MediaSource, ComplianceFlag, SRT invariant checker, e2e marker |
S1 → S1.1 | Plan approved; planning docs committed | errors.py, models.py, pyproject.toml (pytest), Taskfile.yml, test helpers. Runs alone (wave 1) |
ready once Igor gives the go-ahead |
| S1.2 Subtitle processor (core) | R8, R9, R10 (punctuation and balance), R12, P2 | S1 → S1.2 | S1.1 | processor.py + tests. Parallel with S1.3–S1.6 (wave 2) |
proposed |
| S1.3 SRT writer and output planner | R14 (file), R15 + U1, R16 (srt), R17; F4, F11, F12, F23 | S1 → S1.3 | S1.1 | output.py, writer.py + tests. Wave 2 |
proposed |
| S1.4 Local media via FFmpeg | R1, R4, R24 (FFmpeg); F6, F9; cancellation of the FFmpeg child | S1 → S1.4 | S1.1 | media.py, tests/media_fixtures.py + tests. Wave 2 |
proposed |
| S1.5 Transcriber stages | R7 notice; F19, F20, F21; quiet Whisper | S1 → S1.5 | S1.1 | transcriber.py + tests. Wave 2 |
proposed |
| S1.6 CLI shell | R3 (file), R6, R7 (validation), R20, R22 mapping; F1–F3, F7, F8, F24, F25; fast startup | S1 → S1.6 | S1.1 | cli.py, catalog.py, sources.py + tests. Wave 2 |
proposed |
S1.7 generate() and integration |
The S1 user journey end to end: R5, R18 (plain), R19 (path, count, flags), R23, F22; the e2e suite | S1 → S1.7 | S1.2–S1.6 | pipeline.py (adds generate), cli.py (seam), integration and e2e tests, AGENTS.md. Runs alone (wave 3) |
proposed |
| S1.8 Acceptance on real episodes | A3 Studio upload, N1 baseline, E8 latency, R4 with Wi-Fi off; fixtures for S2 and S3 | S1 → S1.8 | S1.7 | Igor, helped by an agent. Wave 4, before the S1 PR merges | proposed |
| Spike E1 Transcription engine | N1: is a 15-minute episode ≤ 15 min (target ≤ 5)? Compares openai-whisper (CPU), mlx-whisper, faster-whisper and whisper.cpp on word timestamps, initial_prompt, language detection and Ctrl+C latency |
To write after S1.8 | S1 merged, S1.8 timing | Scratch scripts only, no src/ changes, so it can run alongside S2. Outcome: a recommendation, and an ADR if the engine changes |
proposed |
| S2 Readable and reviewable | R10 (article/preposition), R11, R13, P1; R18 spinner, elapsed time, model download %; R19 full compliance block | To write after S1 merges | S1 merged; transcript fixture from S1.8 | Processor (processor.py) and CLI display (cli.py). Decide E3 and E5 when planning |
proposed |
| S3 Tech vocabulary | R25 (--prompt, --glossary), W1, F5 |
To write after S2 | S2 merged (shares transcriber.py decoding options, E5); S1.8 jargon baseline |
transcriber.py, cli.py. Could run beside S4 if both are planned together; Igor chose S3 first |
proposed |
| S4 YouTube URL input | R2, R3 (URL), R14 (title name), F10, F13–F18, download %, exit 3, P4, P7; retire process_video() |
To write after S3 | S1 merged (for generate); decide E6, E11, E12 |
url_handler.py, audio_extractor.py, pipeline.py, sources.py, output.py (sanitizing) |
proposed |
| S5 VTT and finish | R16 (vtt), F4 inference, R18 (non-terminal, NO_COLOR), README usage and install (E12), --help, CHANGELOG, CI decision |
To write after S4 | S4 merged | writer.py, output.py, cli.py, README, CHANGELOG |
proposed |
flowchart LR
P[Planning docs committed] --> S11[S1.1]
S11 --> W2{{"wave 2 (parallel)<br/>S1.2 · S1.3 · S1.4 · S1.5 · S1.6"}}
W2 --> S17[S1.7]
S17 --> S18[S1.8]
S18 --> PR1[S1 PR → main]
PR1 --> S2[S2]
PR1 --> SP[Spike E1]
S2 --> S3[S3]
S3 --> S4[S4]
S4 --> S5[S5]
SP -.may change the engine behind TranscriptionResult.-> S3
- Order: S1 → S2 → S3 → S4 → S5 (agreed at milestone 3). The spike starts once S1 is merged and runs alongside S2. Swapping the engine is a separate, user-approved package that goes before or inside S3. That's because
initial_promptsupport (R25) depends on the engine. - Branches:
- One slice branch per slice (
feat/s1-local-file-srt), cut frommainafter the planning commit. - Wave-2 packages each run in their own git worktree, branched from the slice branch after S1.1 lands. The integration owner merges them back.
- Each slice goes to
mainas one PR, as with PR #3.
- One slice branch per slice (
- Commits: SubSync's global rule says no commits without Igor's say-so. Dispatching implementation therefore needs Igor to authorize commits for that slice's branches.
Settled 2026-09-25 during S1 planning. Details are in the phase-4 plan.
| ID | Decision | Who | Revisit when |
|---|---|---|---|
| E2 | Local audio: ffprobe for duration and the no-audio/unreadable checks (F9); FFmpeg extracts the default audio stream to 16 kHz mono WAV. No track selection | Planning default (routine) | A multi-track recording picks the wrong stream |
| E4 | One generate() orchestrator and a MediaSource model. SubtitleFile drops video_id. S4 retires process_video() |
Igor | — |
| E7 | Layers: unit, integration with real FFmpeg and a stand-in transcriber, the SRT invariant checker, and opt-in e2e (pytest -m e2e) on a clip made with macOS say and FFmpeg, using Whisper tiny. Manual named cases. No CI until S5 |
Igor | S5 |
| E8 | In-process transcription. Ctrl+C unwinds all stages and exits 130 within 2 s | Planning default | S1.8 measures more than 2 s → move transcription to a child process |
| E9 | UTF-8 without BOM, LF, one blank line between cues, single trailing newline. CPS counts spaces and punctuation, not line breaks | Planning default | A3 upload fails |
| — | Fast startup: no Whisper, torch or yt-dlp import in stages 0–1. Static model and language lists with a drift test | Planning finding (Whisper import takes 1–2 s) | — |
| — | Until S4, a YouTube URL input exits 2 and suggests the local-file route | Planning default | S4 |
Still open, by slice: E1 (spike); E3 and E5 (S2 planning, which also settles how repeated lines are compared for R13); E5 hint length (S3); E6, E11 and E12 (S4; E12 carries over to S5); E10 after v1.
- Shared contracts and files:
errors.py,models.py,pyproject.toml,Taskfile.ymland the test helpers belong to S1.1 only. Wave-2 packages must escalate rather than edit them.cli.pyis written by S1.6, then extended by S1.7, one after the other.tests/media_fixtures.py(S1.4) andtests/srt_invariants.py(S1.1) are shared read-only by later packages.AGENTS.mdis updated by S1.7 only.
- Integration order and combined journeys:
- Merge S1.1, then wave 2 in any order (their files don't overlap), then S1.7.
- Combined journeys:
- The success run (I1, E1).
- Fail-fast re-run (I3, E3).
- Cancel (I11, E4).
- Offline (E2).
- Missing FFmpeg (I13).
- Reviews:
- Each package is reviewed on its own branch (
product-engineering:reviewing-code-changes, quick mode) before it merges into the slice branch. - After S1.7, a combined review of the whole slice diff checks for omissions and interactions between packages.
- Any later edit invalidates the evidence it touches.
- Each package is reviewed on its own branch (
- Verification:
product-engineering:verifying-implementationdrives the CLI through every S1 acceptance criterion on the slice head.- The report goes in the S1 PR body, or in
docs/product-engineering/subsync-v1/verification/S1.mdif there is no PR.
- Required final checks:
task test,task test:e2eandtask lintpass on the slice head, on Igor's Mac (macOS, FFmpeg on PATH,sayavailable).- S1.8 evidence is recorded in the S1 row above.
- Open decisions and blockers:
- None block S1.1.
- Dispatch needs:
- Igor's approval of this plan.
- Committing the planning docs, which gives the baseline revision.
- Authorization to commit on the S1 branches.
- Next dispatchable work: S1.1. Once it merges, S1.2–S1.6 in parallel.