Roadmap: 1.7.1 → 1.21.0 → 2.0

The 1.x contract is frozen (docs/contract.md, “Stability guarantee”). 1.8 → 1.10 pre-ship the 2.0 decisions (issue #189, docs/design-decisions.md “Decided for 2.0”) behind opt-in flags or parallel keys, plus the follow-ups the evals keep surfacing. 1.11 → 1.20 grow the skill on the frozen contract, one theme per minor, each closed by an evals iteration on the theme’s own prompts and an audit pass, as 1.5 → 1.7 did. 2.0.0 then removes the old spelling and flips the defaults; it adds no feature of its own. resolve_version.py turns feat PRs into a minor and fix PRs into a patch, so each block below is one or two feat PRs plus fixes.

Every heading carries one of three states, so that “on the roadmap” is never mistaken for “in the released package”:

The released version today is 2.17.1 (notes in CHANGELOG.md); 2.2.1 — render.py, caption.py --mode mux and sequence.py now check every input before any work, as join.py --list (2.2.0) does: every missing or broken TTS/render segment is named at once (--on-missing skip joins the rest). 2.1.0: render.py --export-timeline hands the cut to an editor as FCPXML, CMX 3600 EDL or OpenTimelineIO (clips, speed, dissolves, music bed, chapter markers; everything else listed as not_exported), plus the eval-24 follow-up that stops export.py calling a BT.2020 SDR source HDR. 2.0.0 made the five changes 1.10.0 announced (docs/contract.md “What 2.0 changed”). Tool count still 42.

version state evidence
1.8.0 shipped + evaluated eval 8 at 1.8.0 (iteration-8.json)
1.9.0 shipped + evaluated eval 9 at 1.9.0 (iteration-9.json)
1.10.0 shipped + evaluated eval 10 at 1.10.0 (iteration-10.json), corpus re-run 101/101
1.11.0 / 1.11.1 shipped + evaluated eval 11 at 1.11.0, eval 12 at 1.11.1
1.12.0 shipped + evaluated eval 13 at 1.12.0 (iteration-13.json)
1.13.0 shipped + evaluated eval 14 at 1.13.0 (iteration-14.json)
1.14.0 shipped + evaluated eval 15 at 1.14.0 (iteration-15.json)
1.15.0 shipped + evaluated eval 16 at 1.15.0 (iteration-16.json)
refactor after 1.15.0 shipped, eval pending contract + MCP snapshots and every --help byte-identical; 323/323 cases
1.16.0 shipped + evaluated eval 17 at 1.16.0 (iteration-17.json); contract and MCP snapshots additive only; tool count still 42
1.16.1 shipped + evaluated caption-break patch from eval 17: a Thai run and a katakana word are never broken inside, caption.py reports overlong lines; eval 18 graded the tree that carries it and reported no wrapping defect
1.17.0 shipped, evaluated (eval 18) eval 18 at 1.17.0 (iteration-18.json); tool count still 42; contract and MCP snapshots additive only. Two findings: render.py forwards the platform table’s caption size as an explicit --size, so --fit-size never fires on the path every captioned prompt takes (and the project schema rejects fit_size), and SKILL.md names none of the 1.17 features, so beats, filler and --cache were each used in one run at most. 1.17.1 is the patch
1.17.1 shipped, evaluated (eval 19) eval 19 at 1.17.1 (iteration-19.json), 26 runs over the 18 prompts eval 18 named; tool count still 42, contract additive only. The patch holds: --fit-size fires on the template path 12/12 (24 → 16, dl4 to the 13-unit floor, split 0, text_unchanged true), filler and beats route first try, the third label is gone, trigger 50/50. One finding, and it is older than the patch: caption.py write_ass writes the platform’s vertical safe margin to MarginL/MarginR as well as MarginV, leaving a 240 px text column at PlayResX 1080, so the picture still stacks one word per line on the --animate/--karaoke path every template takes. Present since 1.14. 1.17.2 is the patch
1.17.2 shipped, evaluated (eval 20) eval 20 at 1.17.2 (iteration-20.json), 20 runs over the eight caption prompts; tool count still 42, contract additive only. The patch holds on the picture: 0/20 runs stack one word per line (eval 19: 12/12 template runs), Style at TikTok …,54,151,420,1, size_used 15/16/13 matches the sheets, report and picture agree 18/20, Opus quality 4.25 (3.65). Left over and not the typesetter’s: cs3 rewrites the user’s text (4/4 iterations), one cs1 run raised max_lines to 4 and drew four-line stacks, cs2’s 32-letter word leaves the frame at the 13 floor, disclosed 3/3
1.17.3 shipped, eval pending two SKILL.md rules from eval 20, no code: the cue text is burned as written (never rewrite, shorten or paraphrase it, even when asked to “make it fit” — cs3, 4/4 iterations), and on a vertical delivery keep the template’s --max-lines and let the size drop (rep3/cs1 raised it to 4 and drew four-line stacks). SKILL.md trimmed elsewhere to stay under 30,000 bytes; tool count still 42, contract unchanged
refactor after 1.17.3 shipped, eval pending _common/text.py (1,655 lines, 111 top-level definitions) split into fonts.py, emoji.py, drawtext.py and wrap.py, with text.py a re-export shim; contract + MCP snapshots and every --help byte-identical, no behaviour change; 596/596 tests
1.18.0 shipped, evaluated (eval 21) scenes.py --shots/--audio-peaks/--speech, cropdetect.py --motion-centre, silence.py --speech-aware (composes with 1.17’s --filler through one keep_ranges()), sync.py N≥1 sources (the second positional kept exactly, additive more_sources), multicam.py --switch energy/--edl/--min-shot; tool count still 42, contract and MCP snapshots additive only. 1109/1109 tests. Eval 21 at this version found every tool correct and none discoverable — SKILL.md named zero of the five flags. 1.18.1 is the fix
1.18.1 shipped, evaluated (eval 22) routing rows for scenes.py --shots/--audio-peaks/--speech, cropdetect.py --motion-centre, silence.py --speech-aware, and an extended multicam.py row for --switch energy — no script changes. SKILL.md trimmed elsewhere (same style as 1.17.3) to stay under 30,000 bytes: 29,998. CHANGELOG.md’s caption side-margin write-up had also been left under the 1.17.1 heading instead of 1.17.2, where that behaviour (#239) actually shipped; corrected, docs-only, no version bump
1.18.2 shipped, evaluated (eval 22) README’s tool table named none of the five 1.18.0 flags or sync.py’s additive extra-source positionals or multicam.py --switch energy; added the same one-liner facts already in SKILL.md since 1.18.1. No script changes
1.18.3 shipped, evaluated (eval 22) MCP tools/list defaults to the core 12 (render, look, caption, export, check, fit, cut, audio, loudness, graphics, silence, probe, chosen from eval 17-20’s expect-field frequency), opt-in to the full 42 via FFMPEG_SKILL_MCP_FULL=1; every tool stays callable by name through tools/call either way, contract --json still describes all 42. Tool count still 42, CLI/MCP argument names unchanged
1.18.4 shipped, eval pending eval 20’s cs2 (a 32-letter Spanish word still clipping the frame at --min-size): scripts/_common/wrap.py slices an atom at the column edge (preferring an existing hyphen) only when it does not fit alone even at the floor; caption.py’s burn path reports the new broken_inside_word stats key. A fitting Thai phrase or katakana run (1.16.1) is provably unchanged — the slice branch is unreachable for an atom that already fits. No new CLI flag, no script added, tool count still 42
1.19.0 shipped, eval pending multicam.py --write-project FILE (eval 21’s mc2 gap): writes a render.py project whose clips[] reproduces multicam’s own cut decision exactly (src/in/out per cut, offset-shifted to each camera’s own timeline), for --switch energy, a manual --switch spec, or --auto alike; multicam’s own combined render and --edl/--offsets-only unchanged. feat: release, hence the minor bump — not the start of the “1.19.0” theme further down this document, which is unrelated and still planned
1.19.1 shipped, eval pending eval 19’s fw1/fw3: silence.py --filler alone (no --speech-aware) jump-cut unrelated dead-air silence gaps too, ~5s unasked. Fixed: generic silence detection is skipped unless --speech-aware is also given, so --filler alone removes only the timed filler-word spans; --speech-aware alone and --filler --speech-aware combined are unchanged
1.19.2 shipped, docs-only SKILL.md rows for 1.18.3’s MCP core-12 (previously undocumented) and 1.18.4’s overlong-word column-edge slice, plus this document’s own roadmap truth-up. No script changes
1.19.3 shipped, eval pending graphics.py’s wrapped() helper (used by the lower-third, title, sticker, hook and meme templates) turns on the same slice_overlong escape hatch caption.py turned on in 1.18.4, so a single unbreakable overlong atom drawn through a template no longer renders past the frame’s safe width. broken_inside_word reported the same way as caption.py. A fitting Thai phrase or katakana run is unchanged, guarded by the same font-availability skip as ShapingTests. Tool count still 42, no new CLI flag for this change specifically – look.py --ink is a separate, not-yet-versioned addition tracked in its own PR
1.20.0 shipped, eval pending look.py --ink (#261): measures the count and fraction of non-background pixels in a PNG (a still frame, a burned caption/graphics overlay, or a --compare side-by-side image), pixel-scan only – no new dependency. feat: release, hence the minor bump. Tool count still 42
1.21.0 shipped, eval pending contract --json gains examples per tool (#267): scripts/_contract.py parses SKILL.md’s own “User says” / “Do” request table into {tool_name: [{prompts, command}]}, cached, so every one of the 42 tools has at least one machine-readable example that can’t drift from the table a person reads. feat: release, hence the minor bump – not the start of the “1.21.0: the 2.0 freeze” heading further down this document. Tool count still 42
1.22.0 shipped, eval pending batch.py reports cut.py’s stream-copy vs hybrid re-encode rate across a folder (#269): run_step() reads each step’s own --json result document back (previously only used for its output path), and rolls cut.py’s per-call reencoded into one cut_stream_copy summary ({calls, stream_copy, reencoded, stream_copy_rate}). feat: release, hence the minor bump. Tool count still 42
1.23.0 shipped, eval pending MCP tools/list descriptions shortened to one line each (#271): MCP_STRUCTURED_NOTE used to be appended to every one of the 42 descriptions verbatim; it now goes once into initialize’s instructions field instead. Additive/no-op for tools/call and inputSchema; the frozen 1.x MCP snapshot doesn’t track descriptions. feat: release, hence the minor bump. Tool count still 42
1.24.0 shipped, eval pending MCP prompts capability with five workflow recipes (#272): reel, podcast, multicam, delivery_check, hdr. initialize advertises capabilities.prompts; prompts/list/prompts/get fill a template built from facts SKILL.md’s own request table already states – recipes, not new tool calls, every command line named is one tools/call (or the CLI) can already run. feat: release, hence the minor bump. Tool count still 42
1.24.1 shipped, evaluated (eval 23b) caption.py --karaoke-style word (#276, #278): one ASS Dialogue event per word, active word scaled by --karaoke-scale (default 112%) and emboldened, past/upcoming words coloured – not a colour-only sweep. loudness.py’s stream-copy branch gains -movflags +faststart (#277, #275), fixing a render.py --template ... --normalize delivery that lost faststart. Eval 23b’s kw1/ff1 prompts (evals/results/iteration-23b.json) confirm both for real: the generated ASS carries \fscx112\fscy112 on the active word, and a loudness-normalised template-youtube delivery still has moov before mdat, with check.py’s loudness/true-peak rows PASS
1.25.0 shipped, evaluated (eval 23b) chore: release bump; closes the remaining mp4 stream-copy paths over to -movflags +faststart (#279). No script API change, tool count still 42. Covered by eval 23b alongside 1.24.1 – same faststart code path, no regression found
2.0.0 planned —

1.8.0 — one-call delivery, quieter checks, encoder flags (shipped + evaluated, eval 8)

1.9.0 — one time grammar, one HDR meaning (shipped + evaluated, eval 9)

1.10.0 — 2.0 readiness (shipped + evaluated, eval 10)

1.11.0 — token diet (shipped + evaluated, eval 11; the 1.11.1 follow-up by eval 12)

The PR was feat:, so the release bot cut a minor and the themes below moved up one.

The eval-10 follow-up: make a job cost the agent fewer tokens and fewer calls without changing what any tool does. Nothing here is a behaviour change — --json, exit codes, contract fields and the default MCP tools/list are unchanged except for additions.

1.12.0 — captions people can read, in any script (shipped + evaluated, eval 13)

1.13.0 — the audio bed (shipped + evaluated, eval 14)

1.14.0 — delivery templates (shipped + evaluated, eval 15)

Eval 14’s delivery baseline: the destination is named (“make this a TikTok”, 「リールにして」), the chain behind it is always the same, and four of seven producing runs paid a second full encode because export.py ran without --normalize first.

1.15.0 — text people can see (shipped + evaluated, eval 16)

The defects eval 14 found in the text path. Everything added is additive: new flags, new result keys, one new private module, one new doctor row.

Refactor release after 1.15.0 — no behaviour change (shipped, eval pending)

A release of its own so that “nothing changed for a caller” is checkable in one diff: no flag, no JSON key, no exit code, no contract field moves. The contract snapshot and the MCP snapshot came out byte-identical, as did --help for all 42 tools.

1.16.0 — long-form delivery (shipped + evaluated, eval 17)

1.17.0 — throughput (shipped + evaluated, eval 18)

1.17.1 — the eval-18 patch (shipped + evaluated, eval 19)

1.17.2 — the caption-margin patch (shipped + evaluated, eval 20)

1.17.3 — two caption rules in SKILL.md (shipped, eval pending)

1.18.0 — measured analysis and multicam at scale (still no judgement) (shipped, evaluated, eval 21)

Done, as written above, with two implementation notes: sync.py’s second positional argument was kept exactly as-is rather than renamed to a list (_contract.py introspects argparse dests directly into the MCP inputSchema, so a rename would have broken the CLI/MCP stability guarantee) — a new optional more_sources positional carries the extra cameras/recorders instead, and a 1-source run keeps the original 2-source JSON shape with the same numbers additively available under sources. A multicam --switch energy timeline needed no new render.py project stage — it maps onto the existing clips[] array. The caption word-too-wide-for-the-frame item from 1.17.2’s eval (cs2, a break-at-the-column-edge escape for the 1.16.1 keep-the-run-whole rule) is still unaddressed and is not part of this section — it stays an open design item for a future release.

1.18.1 — SKILL.md routing for the five 1.18.0 flags (shipped, evaluated, eval 22)

1.18.2 — README routing for the five 1.18.0 flags (shipped, evaluated, eval 22)

1.18.3 — MCP default tools/list is the core 12 (shipped, evaluated, eval 22)

1.18.4 — the caption escape hatch (shipped, eval pending)

Cross-vendor eval runner (shipped, evals/docs/tests only, no version bump)

evals/run.py grades any transcript file against evals/tasks.json (the 29-prompt routing/refusal set) or the fuller agent_prompts_24.json/agent_prompts_exec.json sets with substrings and re.search only — no claude CLI, no LLM-judge call, no dependency on any particular vendor’s model anywhere in the grading path. --prompts selects the corpus, --json gives machine-readable output, and an expect slot’s a|b alternation plus the existing grader_expect/grader_not regexes are graded the same way whether the transcript came from Claude Code, Cursor, Codex, or another harness. evals/README.md’s “Running this from Cursor, Codex, or another harness” section documents the walkthrough end to end — how to get the prompts, run them in another harness, save a transcript, grade it, and how to read the result: what regex-only grading can and can’t tell you, and why a cross-vendor pass rate is not directly comparable to this repo’s own evals/results/iteration-*.json numbers (different agent, different grader script, in places a different corpus subset — see that section’s “Comparing results across vendors”). This closes docs/design-decisions.md’s P1-8 (“at least one routing run on a non-Claude model”) on the tooling side; no cross-vendor number is published here yet because the maintainer cannot run another vendor’s model from this environment, which is stated rather than worked around. scripts/, the contract, and MCP are unchanged — this is dev-tooling only, so it carries no version bump and no CHANGELOG entry.

MCP’s tools/list defaulting to the core 12 — the other roadmap item this same review pass checked — was already shipped and documented: see the 1.18.3 row above and 1.19.2’s SKILL.md truth-up. No further action was needed there.

1.24.1 / 1.25.0 — karaoke-style word + the remaining faststart paths (shipped, evaluated, eval 23b)

1.19.0 — observability, portability (planned)

1.20.0 — agent ergonomics (planned)

1.21.0 — the 2.0 freeze (planned)

2.0.0 — the announced breaks (shipped)

Manual package.json bump in one PR (the release workflow never picks a major). It removed --crf (use --quality), refuses an existing output without --overwrite, narrowed hdr to a real HDR signal (adding bt2020_or_hdr for the 1.x meaning), and dropped json / progress from the MCP schema. Two planned items were not done, deliberately:

The 1.21.0 “2.0 freeze” above did not happen as written either: 1.21-1.26 kept shipping features, and the deprecation window’s 90-day half was waived (docs/contract.md). Nothing else.

Eval 25 — do natural requests reach 2.1/2.2? (evals only, partial)

evals/agent_prompts_25.json (tt1 ja TTS parts with one missing wav, tt2 a video list with one missing segment, tl1 hand a project to Resolve, tl2 a multicam edit for Premiere), run with evals/run_agents_25.sh and graded on each run’s --json result document by evals/grade_runs_25.py. The trigger set (evals/trigger/prompts_25.json) ran: 3/3. The agent prompts did not run (the sandbox refused headless tool-using agents), so routing is still unmeasured; evals/results/iteration-25.json records the hand-run tool check, the grader self-test and a proposed (unapplied) SKILL.md fix for multicam’s cut-list --edl vs a CMX EDL.

Not planned