explainer-video 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +152 -2
  4. package/cordis.patch.yml +7 -0
  5. package/lib/index.js +35 -0
  6. package/package.json +56 -4
  7. package/plugin.json +13 -0
  8. package/skills/coursework-author/.synced-from +5 -0
  9. package/skills/coursework-author/SKILL.md +209 -0
  10. package/skills/craft-codeflow-flows/SKILL.md +116 -0
  11. package/skills/craft-codeflow-mine/SKILL.md +207 -0
  12. package/skills/craft-codeflow-plan/SKILL.md +65 -0
  13. package/skills/craft-codeflow-scene/SKILL.md +168 -0
  14. package/skills/craft-video-align/SKILL.md +109 -0
  15. package/skills/craft-video-assemble/SKILL.md +69 -0
  16. package/skills/craft-video-audio/SKILL.md +59 -0
  17. package/skills/craft-video-enrich/SKILL.md +46 -0
  18. package/skills/craft-video-plan/SKILL.md +57 -0
  19. package/skills/craft-video-publish/SKILL.md +79 -0
  20. package/skills/craft-video-render/SKILL.md +68 -0
  21. package/skills/craft-video-scene/SKILL.md +112 -0
  22. package/skills/craft-video-script/SKILL.md +80 -0
  23. package/skills/explainer-video-from-codebase/FLOWS.md +577 -0
  24. package/skills/explainer-video-from-codebase/SKILL.md +659 -0
  25. package/skills/explainer-video-from-codebase/tools/apply-flows.mjs +531 -0
  26. package/skills/explainer-video-from-codebase/tools/approve-flows.mjs +56 -0
  27. package/skills/explainer-video-from-codebase/tools/codemap-md.mjs +116 -0
  28. package/skills/explainer-video-from-codebase/tools/config/dotenv.mjs +13 -0
  29. package/skills/explainer-video-from-codebase/tools/config/json.mjs +14 -0
  30. package/skills/explainer-video-from-codebase/tools/config/redact.mjs +16 -0
  31. package/skills/explainer-video-from-codebase/tools/config/toml-lite.mjs +13 -0
  32. package/skills/explainer-video-from-codebase/tools/config/yaml-lite.mjs +18 -0
  33. package/skills/explainer-video-from-codebase/tools/excerpt-check.mjs +186 -0
  34. package/skills/explainer-video-from-codebase/tools/excerpt.mjs +116 -0
  35. package/skills/explainer-video-from-codebase/tools/flow-diagram.mjs +168 -0
  36. package/skills/explainer-video-from-codebase/tools/flow-plan.mjs +129 -0
  37. package/skills/explainer-video-from-codebase/tools/flows-md.mjs +87 -0
  38. package/skills/explainer-video-from-codebase/tools/lang/c.mjs +684 -0
  39. package/skills/explainer-video-from-codebase/tools/lang/go.mjs +673 -0
  40. package/skills/explainer-video-from-codebase/tools/lang/python-config.mjs +143 -0
  41. package/skills/explainer-video-from-codebase/tools/lang/python-deps.mjs +62 -0
  42. package/skills/explainer-video-from-codebase/tools/lang/python-stdlib.mjs +24 -0
  43. package/skills/explainer-video-from-codebase/tools/lang/python.mjs +609 -0
  44. package/skills/explainer-video-from-codebase/tools/lang/typescript.mjs +963 -0
  45. package/skills/explainer-video-from-codebase/tools/lib/decompose.mjs +102 -0
  46. package/skills/explainer-video-from-codebase/tools/lib/groups.mjs +77 -0
  47. package/skills/explainer-video-from-codebase/tools/lib/observe-coverage.mjs +68 -0
  48. package/skills/explainer-video-from-codebase/tools/lib/observe-v8.mjs +232 -0
  49. package/skills/explainer-video-from-codebase/tools/lib/observe.mjs +162 -0
  50. package/skills/explainer-video-from-codebase/tools/lib/redact.mjs +28 -0
  51. package/skills/explainer-video-from-codebase/tools/lib/sandbox.mjs +97 -0
  52. package/skills/explainer-video-from-codebase/tools/lib/test-links.mjs +28 -0
  53. package/skills/explainer-video-from-codebase/tools/lib/toml.mjs +129 -0
  54. package/skills/explainer-video-from-codebase/tools/lib/trace-c.mjs +95 -0
  55. package/skills/explainer-video-from-codebase/tools/lib/trace-go.mjs +101 -0
  56. package/skills/explainer-video-from-codebase/tools/lib/trace-js.mjs +136 -0
  57. package/skills/explainer-video-from-codebase/tools/lib/tree-sitter.mjs +19 -0
  58. package/skills/explainer-video-from-codebase/tools/lib/walk.mjs +138 -0
  59. package/skills/explainer-video-from-codebase/tools/mine.mjs +409 -0
  60. package/skills/explainer-video-from-codebase/tools/propose.mjs +127 -0
  61. package/skills/explainer-video-from-codebase/tools/rank.mjs +119 -0
  62. package/skills/explainer-video-from-codebase/tools/ranking-md.mjs +76 -0
  63. package/skills/explainer-video-from-codebase/tools/rules/c-handlers.mjs +53 -0
  64. package/skills/explainer-video-from-codebase/tools/rules/celery-taskiq.mjs +48 -0
  65. package/skills/explainer-video-from-codebase/tools/rules/click-typer.mjs +6 -0
  66. package/skills/explainer-video-from-codebase/tools/rules/express.mjs +72 -0
  67. package/skills/explainer-video-from-codebase/tools/rules/fastapi.mjs +99 -0
  68. package/skills/explainer-video-from-codebase/tools/rules/go-http.mjs +173 -0
  69. package/skills/explainer-video-from-codebase/tools/rules/go-test.mjs +108 -0
  70. package/skills/explainer-video-from-codebase/tools/rules/grpc.mjs +57 -0
  71. package/skills/explainer-video-from-codebase/tools/rules/gtest.mjs +97 -0
  72. package/skills/explainer-video-from-codebase/tools/rules/haystack.mjs +55 -0
  73. package/skills/explainer-video-from-codebase/tools/rules/jest-vitest.mjs +100 -0
  74. package/skills/explainer-video-from-codebase/tools/rules/js-http-client.mjs +111 -0
  75. package/skills/explainer-video-from-codebase/tools/rules/nextjs.mjs +53 -0
  76. package/skills/explainer-video-from-codebase/tools/rules/process-spawn.mjs +49 -0
  77. package/skills/explainer-video-from-codebase/tools/rules/py-http-client.mjs +26 -0
  78. package/skills/explainer-video-from-codebase/tools/rules/pytest.mjs +107 -0
  79. package/skills/explainer-video-from-codebase/tools/rules/react-router.mjs +17 -0
  80. package/skills/explainer-video-from-codebase/tools/rules/registry.mjs +31 -0
  81. package/skills/explainer-video-from-codebase/tools/trace/c/fmtrace-sh +14 -0
  82. package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-net.cjs +33 -0
  83. package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-srcmap.cjs +41 -0
  84. package/skills/explainer-video-from-codebase/tools/trace/python/fmtrace_plugin.py +87 -0
  85. package/skills/explainer-video-from-codebase/tools/trace/python/sitecustomize.py +330 -0
  86. package/skills/explainer-video-from-codebase/tools/trace/sandbox/linux-bwrap.sh +73 -0
  87. package/skills/explainer-video-from-codebase/tools/trace/sandbox/macos-nonet.sb +41 -0
  88. package/skills/explainer-video-from-codebase/tools/trace.mjs +577 -0
  89. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-c.txt +21 -0
  90. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-cpp.txt +21 -0
  91. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-go.txt +21 -0
  92. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-javascript.txt +21 -0
  93. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-python.txt +21 -0
  94. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-typescript.txt +21 -0
  95. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/web-tree-sitter.txt +21 -0
  96. package/skills/explainer-video-from-codebase/vendor/tree-sitter/MANIFEST.json +172 -0
  97. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-c.wasm +0 -0
  98. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-cpp.wasm +0 -0
  99. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-go.wasm +0 -0
  100. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-javascript.wasm +0 -0
  101. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-python.wasm +0 -0
  102. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-tsx.wasm +0 -0
  103. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-typescript.wasm +0 -0
  104. package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.js +4081 -0
  105. package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.wasm +0 -0
  106. package/skills/explainer-video-from-coursework/ARCHITECTURE.md +59 -0
  107. package/skills/explainer-video-from-coursework/PIPELINE.md +343 -0
  108. package/skills/explainer-video-from-coursework/ROADMAP.md +725 -0
  109. package/skills/explainer-video-from-coursework/SERIES.md +232 -0
  110. package/skills/explainer-video-from-coursework/SETUP.md +105 -0
  111. package/skills/explainer-video-from-coursework/SKILL.md +858 -0
  112. package/skills/explainer-video-from-coursework/scenes/_pt-chart.js +12 -0
  113. package/skills/explainer-video-from-coursework/scenes/_pt-equation.js +10 -0
  114. package/skills/explainer-video-from-coursework/scenes/_pt-hubspoke.js +7 -0
  115. package/skills/explainer-video-from-coursework/scenes/_pt-loopflow.js +7 -0
  116. package/skills/explainer-video-from-coursework/scenes/_pt-sequence.js +12 -0
  117. package/skills/explainer-video-from-coursework/scenes/_pt-timeline.js +11 -0
  118. package/skills/explainer-video-from-coursework/src/scene-code.html +127 -0
  119. package/skills/explainer-video-from-coursework/src/scene-code.js +379 -0
  120. package/skills/explainer-video-from-coursework/src/scene-core.js +1250 -0
  121. package/skills/explainer-video-from-coursework/src/scene-diagram.html +50 -0
  122. package/skills/explainer-video-from-coursework/src/scene-svg.html +50 -0
  123. package/skills/explainer-video-from-coursework/src/timeline.js +297 -0
  124. package/skills/explainer-video-from-coursework/src/timing-chart.js +160 -0
  125. package/skills/explainer-video-from-coursework/tools/align.mjs +419 -0
  126. package/skills/explainer-video-from-coursework/tools/apply-enrichment.mjs +206 -0
  127. package/skills/explainer-video-from-coursework/tools/assemble.mjs +223 -0
  128. package/skills/explainer-video-from-coursework/tools/build-audio.mjs +234 -0
  129. package/skills/explainer-video-from-coursework/tools/chromium.mjs +73 -0
  130. package/skills/explainer-video-from-coursework/tools/debug-page.mjs +75 -0
  131. package/skills/explainer-video-from-coursework/tools/env.mjs +30 -0
  132. package/skills/explainer-video-from-coursework/tools/frame-dedup.mjs +131 -0
  133. package/skills/explainer-video-from-coursework/tools/key-source.mjs +151 -0
  134. package/skills/explainer-video-from-coursework/tools/model.mjs +21 -0
  135. package/skills/explainer-video-from-coursework/tools/naming.mjs +38 -0
  136. package/skills/explainer-video-from-coursework/tools/onset-audit.mjs +51 -0
  137. package/skills/explainer-video-from-coursework/tools/pacing-check.mjs +350 -0
  138. package/skills/explainer-video-from-coursework/tools/planner.mjs +616 -0
  139. package/skills/explainer-video-from-coursework/tools/publish.mjs +230 -0
  140. package/skills/explainer-video-from-coursework/tools/render.mjs +205 -0
  141. package/skills/explainer-video-from-coursework/tools/resolve-beats.mjs +239 -0
  142. package/skills/explainer-video-from-coursework/tools/script-check.mjs +174 -0
  143. package/skills/explainer-video-from-coursework/tools/stamp.mjs +71 -0
  144. package/skills/explainer-video-from-coursework/tools/timing-report.mjs +299 -0
  145. package/skills/explainer-video-from-coursework/tools/transcribe-gemini.mjs +96 -0
  146. package/skills/explainer-video-from-coursework/tools/tts-gemini.mjs +394 -0
  147. package/skills/explainer-video-from-coursework/tools/verify-audio.mjs +88 -0
  148. package/skills/explainer-video-from-coursework/tools/workdir.mjs +124 -0
  149. package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js +8 -0
  150. package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js.LICENSE.txt +16 -0
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: craft-video-align
3
+ description: Measure per-word and per-line timings for one video's narration, so scene beats can be anchored to the words they illustrate. Stage 4b of explainer-video-from-coursework.
4
+ ---
5
+
6
+ # craft-video-align — word and line timings
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 4b of 8**, between
9
+ > audio (4) and scene (5).
10
+ >
11
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
12
+
13
+ > **Timing.** Bracket the stage with the stamp pair so the series accrues a per-video audit;
14
+ > `end` must run before the stage counts as done (see *Timing* in the main skill):
15
+ >
16
+ > ```bash
17
+ > node ../explainer-video-from-coursework/tools/stamp.mjs begin --out out/<name> --video <NN> --stage 4b
18
+ > node ../explainer-video-from-coursework/tools/stamp.mjs end --out out/<name> --video <NN> --stage 4b
19
+ > ```
20
+
21
+ **Prerequisites:** `out/<name>/<NN>/narration/<key>.wav` and `timeline.json` — both
22
+ final once the audio stage exits.
23
+
24
+ ```bash
25
+ node ../explainer-video-from-coursework/tools/align.mjs --script out/<name>/scripts/<NN>-*.json \
26
+ --work out/<name>/<NN>
27
+ ```
28
+
29
+ ## Why this stage exists
30
+
31
+ The audio stage measures each **segment**. Everything *inside* a segment was left to
32
+ guesswork, and a guess is where sync dies: on the module-07 pilot the "April" reveal
33
+ fired at 6.6s while the words "In April" begin at 12.6s — a 6-second error that every
34
+ check passed, because no check could see the narration's shape.
35
+
36
+ This stage measures the narration's shape. It emits the span of every **line**
37
+ (sentence) of the script, in audio time, so the scene stage can bind a beat to a line
38
+ instead of inventing a number.
39
+
40
+ ## What it does
41
+
42
+ Transcribe the **whole narration track in one request** with `gemini-3.5-transcribe`
43
+ (requesting `wordTimestamp`), cut the returned words into segments using the measured
44
+ `timeline.json` boundaries, align each segment's words back onto that segment's script
45
+ words, then compute each line's span from the aligned words.
46
+
47
+ **Why one request, not one per segment.** The model is capped at **100 requests/day
48
+ per project per model**, the Batch API is not supported for it, and the cap is per
49
+ project — so it cannot be spread across keys. Per-segment alignment spent ~30 requests
50
+ on a 13-minute video (≈3 videos/day); a whole-track pass spends **one**.
51
+
52
+ The model allows 30 minutes per request *when word timestamps are enabled*, so a track
53
+ longer than that is cut at **segment boundaries** into multiple passes (never
54
+ mid-sentence). `--per-segment` keeps the old path as a fallback, and is chosen
55
+ automatically when only a few segments are missing. Already-cached segments are never
56
+ re-requested, so a full pass only fills what is absent and cannot overwrite good
57
+ measurements with worse ones.
58
+
59
+ On HTTP 429 the run **exits 3** rather than falling back — a daily-cap failure would
60
+ fail identically for every segment, so cascading would just burn the run.
61
+
62
+ **The alignment step is not optional.** The transcriber is not a faithful echo — it
63
+ rewrites punctuation and applies inverse text normalisation. Measured on real
64
+ narration it returned "In March, it pushes…" (inserted comma), "spends **40** minutes"
65
+ (we wrote *forty*), "**failed**" (we wrote *fail*), and merged two sentences into one.
66
+ So the transcript never decides a boundary: **our punctuation decides boundaries, the
67
+ aligned words supply times.**
68
+
69
+ **Word offsets can contain outliers.** One plan returned "A delta" at 3.7s in a
70
+ sentence whose neighbours were at 12.4s and 14.2s. Offsets are forced monotonic and
71
+ non-overlapping (`clampMonotonic`), which pulls an early outlier to the end of the
72
+ previous word — within a word or two of the truth. Repairs are reported.
73
+
74
+ ## Outputs
75
+
76
+ - `narration/<key>.words.json` — per segment: every word with its offsets, the script
77
+ index it maps to, and a cache stamp (wav size+mtime, text hash). Re-runs are free
78
+ until the audio or the script text changes.
79
+ - `lines.json` — per video: for every segment, each line's `{ line, text, start, end }`
80
+ plus `lead` and the window bounds.
81
+
82
+ **Line times are relative to the narration WAV.** A scene renders in window time,
83
+ which starts `lead` seconds earlier, so the conversion is:
84
+
85
+ ```
86
+ beat time (scene) = line.start + lead
87
+ ```
88
+
89
+ `lines.json` records `lead` per segment so nothing has to assume 0.6.
90
+
91
+ ## Done when
92
+
93
+ `align` exits **0** and prints `✓ N segment(s) · M lines · written`. Any error means a
94
+ line could not be placed in the audio (usually a garbled transcript) — re-run with
95
+ `--force` for that segment, and if it persists treat the segment's narration as the
96
+ thing to fix.
97
+
98
+ Two things to watch in the report:
99
+
100
+ - **`only NN% of script words aligned`** — below `--min-match` (default 0.8) the
101
+ transcript probably diverged; the line spans may still be right, but check them.
102
+ - **`NN word offset(s) came back out of order and were clamped`** — expected
103
+ occasionally; a large number suggests the audio and the script text disagree.
104
+
105
+ ## Maintenance
106
+
107
+ > If you modify this file, or the user asks to change this stage, tell the user that the
108
+ > main `explainer-video-from-coursework` skill's **"Stage 4b — /align"** section must be
109
+ > reconciled to match, and name that section in your reply.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: craft-video-assemble
3
+ description: Assemble one video from its segments — render each window silent, concatenate, mux the narration once, and verify. Stage 8 of explainer-video-from-coursework.
4
+ ---
5
+
6
+ # craft-video-assemble — the finished video
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 8 of 8**. The
9
+ > **orchestrator** over the render stage.
10
+ >
11
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
12
+
13
+ > **Timing.** Bracket the stage with the stamp pair so the series accrues a per-video audit;
14
+ > `end` must run before the stage counts as done (see *Timing* in the main skill):
15
+ >
16
+ > ```bash
17
+ > node ../explainer-video-from-coursework/tools/stamp.mjs begin --out out/<name> --video <NN> --stage 8
18
+ > node ../explainer-video-from-coursework/tools/stamp.mjs end --out out/<name> --video <NN> --stage 8
19
+ > ```
20
+
21
+ **Prerequisites:** the script, `out/<name>/<NN>/timeline.json`, `manifest.json`, and
22
+ `narration.wav`; on the machine, `ffmpeg` + headless Chromium (see `SETUP.md`).
23
+
24
+ > A `[setup]` / `Playwright cache not found` failure means ffmpeg or Chromium is absent —
25
+ > relay the matching `SETUP.md` step to the user and re-run.
26
+
27
+ ```bash
28
+ node ../explainer-video-from-coursework/tools/assemble.mjs --script out/<name>/scripts/<NN>-*.json \
29
+ --work out/<name>/<NN> --name <output> \
30
+ --dest <destination> [--port 9333]
31
+ ```
32
+
33
+ `--port` (default 9333) is forwarded to every per-segment `render.mjs`, so **two videos can
34
+ assemble concurrently** when each is given its own port — one port per concurrent agent.
35
+ The run aborts before segment one if the port is already listening, because a shared CDP
36
+ port does not fail cleanly: the second assembly attaches to the first browser's page and
37
+ encodes the *other* video's scene under its own filename, exit 0. Verified — byte-identical
38
+ frames from the wrong video. This is what lets a per-video agent own stage 8 end to end.
39
+
40
+ Per segment: render its own window through its own host + config, **silent** — parts
41
+ are silent on purpose, because muxing narration onto each would restart the whole track
42
+ at every boundary. Then concatenate (`-c copy`), mux the single measured narration
43
+ track **once**, and verify. `--only <key>` renders a single segment, for a smoke test.
44
+
45
+ Asserts: every segment has a timeline window **and** a `manifest.json` entry · the
46
+ windows tile the timeline with no gaps · the finished audio matches the narration at
47
+ **sample level**.
48
+
49
+ **Done when:** the run exits **0** and prints `[done] …<output>.mp4` with `audio ok`.
50
+ The MP4 is copied into the video's own folder, `--dest/<NN>-<slug>/`, alongside the
51
+ `description.md` and `captions.srt` that publish wrote.
52
+
53
+ <!-- sync:codeflow-publish-render -->
54
+ **Code-flow videos only.** A code-flow video's plan, `plan.json` or `plan.enriched.json`, carries
55
+ `"kind": "codebase"`, and its `forms` are `overview`, `flow` and `core`. A coursework plan has no `kind`; nothing
56
+ in this block applies to its videos. A code-flow video adds these rules at stages 6–8:
57
+
58
+ - **Render.** A code segment renders through `src/scene-code.html?scene=<key>`. Pass `--work out/<name>/<NN>` so
59
+ the host reads that video's `timeline.js`; `assemble` passes it for every segment.
60
+ - **Chapters.** Write each chapter title from that chapter's narration, as for any video.
61
+ - **Upload title.** The `youtubeTitle` names the flow in the viewer's terms, such as what a request does or what
62
+ a command runs. Never use a module path.
63
+ <!-- /sync:codeflow-publish-render -->
64
+
65
+ ## Maintenance
66
+
67
+ > If you modify this file, or the user asks to change this stage, tell the user that the
68
+ > main `explainer-video-from-coursework` skill's **"Stage 8 — /assemble"** section must be
69
+ > reconciled to match, and name that section in your reply.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: craft-video-audio
3
+ description: Synthesize narration and build the measured timeline for one video. Stage 4 of explainer-video-from-coursework — the one-shot TTS primitive.
4
+ ---
5
+
6
+ # craft-video-audio — TTS and the measured timeline
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 4 of 8**. This is the
9
+ > **primitive**: it builds audio once and stops. This skill itself never loops.
10
+ >
11
+ > The scene stage may re-invoke it, but **padding is not the expected path any more**.
12
+ > Beat dwells are measured from the narration (stage 4b), so a shortfall now means the
13
+ > scene over-asked — the fix is to widen the beat's line range, not to insert silence.
14
+ > Only if a visual genuinely needs more screen time than its words take is a `pad`
15
+ > justified; see stage 5's over-ask ladder.
16
+ >
17
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
18
+
19
+ > **Timing.** Bracket the stage with the stamp pair so the series accrues a per-video audit;
20
+ > `end` must run before the stage counts as done (see *Timing* in the main skill):
21
+ >
22
+ > ```bash
23
+ > node ../explainer-video-from-coursework/tools/stamp.mjs begin --out out/<name> --video <NN> --stage 4
24
+ > node ../explainer-video-from-coursework/tools/stamp.mjs end --out out/<name> --video <NN> --stage 4
25
+ > ```
26
+
27
+ **Prerequisites:** `out/<name>/scripts/<NN>-*.json` from the script stage; on the machine, the Gemini key in the OS secret store (see `SETUP.md`).
28
+
29
+ > A `No API key found` failure means the key isn't in the OS secret store — relay the
30
+ > matching `SETUP.md` step to the user and re-run.
31
+
32
+ ```bash
33
+ node ../explainer-video-from-coursework/tools/build-audio.mjs --script out/<name>/scripts/<NN>-*.json \
34
+ --out out/<name>/<NN> --voice Aoede
35
+ ```
36
+
37
+ Writes `narration/<seg>.wav`, `narration.wav`, `timeline.json`, and `timeline.js` into
38
+ the per-video work dir. This is **the only network stage** — it spends the Gemini TTS
39
+ key.
40
+
41
+ Next: **stage 4b (`/align`)** measures the words *inside* each of those windows —
42
+ `node ../explainer-video-from-coursework/tools/align.mjs --script out/<name>/scripts/<NN>-*.json --work out/<name>/<NN>`.
43
+ Without it, scene beats have nothing measured to anchor to.
44
+
45
+ **Done when:** the run exits **0** and prints one window per segment, ending in
46
+ `[out] …/timeline.json`.
47
+
48
+ - **Durations are measured, not estimated** — each WAV is measured with `ffprobe`; the
49
+ plan's `Min` was a guardrail, this is the real number later stages bind to.
50
+ - **The cache is text-keyed** — `narration/<key>.src` stores the exact spoken text, so a
51
+ re-run after a wording tweak costs 0 calls. It does **not** cover `--voice`, `style`,
52
+ or `--model`: change one and the old audio is silently reused. Pass `--force` to
53
+ re-synthesize.
54
+
55
+ ## Maintenance
56
+
57
+ > If you modify this file, or the user asks to change this stage, tell the user that the
58
+ > main `explainer-video-from-coursework` skill's **"Stage 4 — /audio"** section must be
59
+ > reconciled to match, and name that section in your reply.
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: craft-video-enrich
3
+ description: Retitle and curate the videos in an explainer-video plan — write enrichment.json and merge it. Stage 2 of explainer-video-from-coursework; run it alone to retitle an existing plan.
4
+ ---
5
+
6
+ # craft-video-enrich — retitle and curate
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 2 of 8**, the second
9
+ > once-per-series stage. Self-contained: it operates on an existing `plan.json`. For a
10
+ > full run, use the main skill instead.
11
+ >
12
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
13
+
14
+ **Prerequisites:** `out/<name>/plan.json` from the plan stage.
15
+
16
+ The agent writes `out/<name>/enrichment.json` — one entry per video — then validates
17
+ and merges:
18
+
19
+ ```bash
20
+ node ../explainer-video-from-coursework/tools/apply-enrichment.mjs --plan out/<name>/plan.json \
21
+ --enrichment out/<name>/enrichment.json
22
+ ```
23
+
24
+ Writes `plan.enriched.json` and `plan.enriched.md`.
25
+
26
+ **Done when:** the run exits **0** (no validation problems), `plan.enriched.md` shows
27
+ the final video list, **and that full list has been shown to the user** — every
28
+ surviving video plus the removals with their reasons. Never summarise it into a sample:
29
+ `apply-enrichment` prints the whole table, and the whole table is what gets reviewed.
30
+
31
+ **Title contract.** The planner's titles are placeholders. Replace each with a real
32
+ title: descriptive, **unique**, written for a human scrolling YouTube — never a
33
+ directory slug. Title from the content, not the name: read the source at the video's
34
+ `sectionSpans` before naming it. The tool rejects empty titles, `Part N` placeholders,
35
+ ellipses, slugs, and duplicates.
36
+
37
+ **Curation.** The planner cannot tell an overview page from real content, so it plans
38
+ both — the agent decides. An entry may carry `skip: true` or `mergeInto: <index>`, both
39
+ requiring a `why`. Every skip/merge lands in `plan.removed` with its reason and
40
+ sections, so coverage stays auditable.
41
+
42
+ ## Maintenance
43
+
44
+ > If you modify this file, or the user asks to change this stage, tell the user that the
45
+ > main `explainer-video-from-coursework` skill's **"Stage 2 — /enrich"** section must be
46
+ > reconciled to match, and name that section in your reply.
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: craft-video-plan
3
+ description: Decompose a coursework notes directory into an explainer-video plan and prove no content-bearing section is dropped. Stage 1 of explainer-video-from-coursework; run it alone to (re)plan a directory.
4
+ ---
5
+
6
+ # craft-video-plan — decompose and prove coverage
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 1 of 8**, the first of
9
+ > the two once-per-series stages. This skill is self-contained: it plans a directory
10
+ > from scratch. For a full run, use the main skill instead.
11
+ >
12
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, defaulting beside your notes — so `out/` scratch lands where Stage 0 said it would and inside a repo's ignore rules.
13
+
14
+ **Prerequisites:** a directory of markdown notes (`<notesDir>`).
15
+
16
+ ```bash
17
+ node ../explainer-video-from-coursework/tools/planner.mjs <notesDir> --out out/<name> --max-min 25 \
18
+ [--exclude <notesDir>/videos]
19
+ ```
20
+
21
+ Writes `out/<name>/plan.json` and `out/<name>/plan.md`. No network, no key, safe to
22
+ re-run. Read `plan.md`, not `plan.json`.
23
+
24
+ > **Exclude a destination that lives inside the source.** `<notesDir>/videos/` is the
25
+ > usual destination, and the planner walks all of `<notesDir>`: a published
26
+ > `description.md` comes back in as a note and turns into a phantom video. Pass
27
+ > `--exclude` for it (repeatable, one per old output folder). The planner warns when
28
+ > it finds `description.md` inputs, because that filename is this pipeline's own
29
+ > output — but the flag is the fix.
30
+
31
+ > **`--max-min` is part of the plan.** Video indices are keyed by the split, so
32
+ > re-running with a different cap reshuffles them and invalidates any enrichment
33
+ > already written. The planner refuses (exit 4) to overwrite an enriched plan whose
34
+ > model differs; pass `--force` only when you intend to re-enrich from scratch.
35
+
36
+ **Done when:** the run exits **0**, `plan.md`'s coverage line reads
37
+ `N assigned · M excluded as navigation · 0 orphaned`, **and every planned video has
38
+ been shown to the user** (index, title, length — all of them, no sample and no
39
+ "…and N more"). A non-zero exit, or any orphan, means the plan is incomplete — stop and
40
+ fix it. Never enrich an incomplete plan.
41
+
42
+ Two facts that shape how the plan is read:
43
+
44
+ - **The coverage proof is the point.** The planner proves every content-bearing section
45
+ is assigned to a video or is a navigation heading. `plan.json` also carries
46
+ `sectionSpans` (each section's `src`, `title`, and 1-based `line`/`endLine`) — the raw
47
+ material for the next stage.
48
+ - **The `Min` column is a guardrail, not a duration** — a planning estimate
49
+ (`wpm 153 × expansion 1.10`), not measured audio. Use it to judge boundaries.
50
+
51
+ Only `.md` files are parsed; non-markdown content is invisible to the planner.
52
+
53
+ ## Maintenance
54
+
55
+ > If you modify this file, or the user asks to change this stage, tell the user that the
56
+ > main `explainer-video-from-coursework` skill's **"Stage 1 — /plan"** section must be
57
+ > reconciled to match, and name that section in your reply.
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: craft-video-publish
3
+ description: Generate the upload title, YouTube chapters and English captions for one finished video. Stage 6 of explainer-video-from-coursework.
4
+ ---
5
+
6
+ # craft-video-publish — title, chapters and captions
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 6 of 8**.
9
+ >
10
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
11
+
12
+ > **Timing.** Bracket the stage with the stamp pair so the series accrues a per-video audit;
13
+ > `end` must run before the stage counts as done (see *Timing* in the main skill):
14
+ >
15
+ > ```bash
16
+ > node ../explainer-video-from-coursework/tools/stamp.mjs begin --out out/<name> --video <NN> --stage 6
17
+ > node ../explainer-video-from-coursework/tools/stamp.mjs end --out out/<name> --video <NN> --stage 6
18
+ > ```
19
+
20
+ **Prerequisites:** `out/<name>/<NN>/timeline.json` and `narration/<key>.src` — both
21
+ final once the scene stage exits.
22
+
23
+ The agent writes `out/<name>/<NN>/publish.json` — a `youtubeTitle`, a chapter list
24
+ (`segments` keys + `title`) and a `blurb`. The tool resolves timecodes, validates, and
25
+ emits **three files** into the video's own folder, `--dest/<NN>-<slug>/`: `title.txt`
26
+ (one line, what YouTube shows), `description.md` (the blurb, then the chapter block,
27
+ paste-ready plain text) and `captions.srt`. The folder name is derived from the script's
28
+ `videoIndex` + title — do not pick it by hand.
29
+
30
+ - **Title** — `title.txt` is the upload title, and it is also the file `upload_yt_video`
31
+ reads out of that folder, so a missing one is an incomplete upload. It is *not* the
32
+ same thing as the video's `title`: that one is structural (set at stage 2, it names the
33
+ folder and must not move); this one is written here because only now does the video
34
+ exist to be described honestly. Keep it a real search phrase — the subject someone
35
+ would type — and never promise what the video does not deliver: retention past the
36
+ first seconds is measured, and a hook the video does not pay off is the one thing that
37
+ gets actively suppressed.
38
+
39
+ - **Chapters** — `M:SS Title`, start-time only, one per segment by default; grouping is
40
+ two keys in one list, and a chapter's time is the first segment's window start.
41
+ Titles are authored from the narration — never the scene `stageNames` (uppercase,
42
+ one missing, near-duplicate pairs).
43
+ - **Captions** — one cue per segment, text from `narration/<key>.src` (the exact text
44
+ the WAV was built from — the audio is the truth, not `script.json`).
45
+ - **Hard checks** — first timecode `0:00` · ≥3 chapters · each ≥10s · ascending · every
46
+ segment key resolves · titles ≤40 chars, not duplicated/near-duplicated · cues
47
+ ascend and don't overlap · `youtubeTitle` ≤100 chars, one line.
48
+ - **Soft checks** (warnings, not failures) — title past ~70 chars (mobile truncation) ·
49
+ all-uppercase · stacked `!!`/`??` · a near-duplicate of a sibling video's title, which
50
+ buries a series' own parts. The tool can police the shape of a title, never its
51
+ appeal; that judgement stays yours.
52
+
53
+ **Chapters need channel eligibility.** They are a YouTube advanced feature: a channel that
54
+ has not qualified for advanced-feature eligibility renders **no** chapters, however valid
55
+ the list — its timestamps still work as jump-to-section links, but the segmented progress
56
+ bar and Google key moments do not appear. Nothing in `description.md` affects this, so
57
+ absent chapters are not a formatting fault: `0:00` (one digit), a `-` after the timecode, a
58
+ blurb above the block and a 2:39 runtime were each verified live on eligible channels, and
59
+ the hard checks above already mirror YouTube's rules.
60
+
61
+ **Done when:** `publish` exits 0 and both files land in `--dest/<NN>-<slug>/` beside the MP4.
62
+
63
+ <!-- sync:codeflow-publish-render -->
64
+ **Code-flow videos only.** A code-flow video's plan, `plan.json` or `plan.enriched.json`, carries
65
+ `"kind": "codebase"`, and its `forms` are `overview`, `flow` and `core`. A coursework plan has no `kind`; nothing
66
+ in this block applies to its videos. A code-flow video adds these rules at stages 6–8:
67
+
68
+ - **Render.** A code segment renders through `src/scene-code.html?scene=<key>`. Pass `--work out/<name>/<NN>` so
69
+ the host reads that video's `timeline.js`; `assemble` passes it for every segment.
70
+ - **Chapters.** Write each chapter title from that chapter's narration, as for any video.
71
+ - **Upload title.** The `youtubeTitle` names the flow in the viewer's terms, such as what a request does or what
72
+ a command runs. Never use a module path.
73
+ <!-- /sync:codeflow-publish-render -->
74
+
75
+ ## Maintenance
76
+
77
+ > If you modify this file, or the user asks to change this stage, tell the user that the
78
+ > main `explainer-video-from-coursework` skill's **"Stage 6 — /publish"** section must be
79
+ > reconciled to match, and name that section in your reply.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: craft-video-render
3
+ description: Render one scene config over one time window to an MP4 (or a handful of QA stills). Stage 7 of explainer-video-from-coursework — the render primitive.
4
+ ---
5
+
6
+ # craft-video-render — frames to MP4
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 7 of 8**. The render
9
+ > **primitive**; the assemble stage drives it per segment. It can also run alone for QA
10
+ > stills during scene authoring.
11
+ >
12
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
13
+
14
+ > **Timing.** Bracket the stage with the stamp pair so the series accrues a per-video audit;
15
+ > `end` must run before the stage counts as done (see *Timing* in the main skill):
16
+ >
17
+ > ```bash
18
+ > node ../explainer-video-from-coursework/tools/stamp.mjs begin --out out/<name> --video <NN> --stage 7
19
+ > node ../explainer-video-from-coursework/tools/stamp.mjs end --out out/<name> --video <NN> --stage 7
20
+ > ```
21
+
22
+ **Prerequisites:** a scene config and, for windowed renders, `out/<name>/<NN>/timeline.json`; on the machine, `ffmpeg` + headless Chromium (see `SETUP.md`).
23
+
24
+ > A `[node]` / `[setup]` / `Playwright cache not found` failure means a prerequisite is
25
+ > absent — relay the matching `SETUP.md` step to the user and re-run.
26
+
27
+ ```bash
28
+ node ../explainer-video-from-coursework/tools/render.mjs --scene "src/scene-diagram.html?scene=<name>" \
29
+ --range <t0>,<t1> --outdir out/<name>/<NN>/parts --name part-<key>
30
+ ```
31
+
32
+ Renders one scene config over one window — headless Chromium via CDP → PNG sequence →
33
+ ffmpeg H.264. It is not a whole-video renderer: `--scene` takes a single
34
+ `host?scene=<config>`, so a multi-scene video is rendered window by window. A window
35
+ rendered with no `--narration` comes out silent, which is what assembly wants.
36
+
37
+ **Each distinct frame is captured once** (no flag; it is the only capture path). Because
38
+ `renderAt(t)` is pure, consecutive frames are often byte-identical — 82–99% measured — and
39
+ a screenshot costs ~63 ms, so the loop hashes the serialized `#stage` SVG after each
40
+ `renderAt(t)` and hardlinks the previous PNG when the markup is unchanged. Guards: scenes
41
+ declaring CSS/web animations take the plain capture path from frame 0, and every 30 frames
42
+ a real screenshot is diffed against the frame written (on disagreement it re-captures,
43
+ rewinds since the last audit, and falls back to plain capture). Verified at SSIM 1.000000
44
+ against every-frame renders on two full videos, 3.3×–4.3× faster. Read the closing
45
+ `[dedup]` line — screenshots vs total, reuse %, audit mismatches.
46
+
47
+ `--shots 6,20,34` renders QA stills instead of a full sequence (stills bypass the dedup
48
+ loop — each is a different instant); `--keep-frames` retains the PNG sequence.
49
+
50
+ **Done when:** the run exits **0** and prints `[done] …<name>.mp4`.
51
+
52
+ <!-- sync:codeflow-publish-render -->
53
+ **Code-flow videos only.** A code-flow video's plan, `plan.json` or `plan.enriched.json`, carries
54
+ `"kind": "codebase"`, and its `forms` are `overview`, `flow` and `core`. A coursework plan has no `kind`; nothing
55
+ in this block applies to its videos. A code-flow video adds these rules at stages 6–8:
56
+
57
+ - **Render.** A code segment renders through `src/scene-code.html?scene=<key>`. Pass `--work out/<name>/<NN>` so
58
+ the host reads that video's `timeline.js`; `assemble` passes it for every segment.
59
+ - **Chapters.** Write each chapter title from that chapter's narration, as for any video.
60
+ - **Upload title.** The `youtubeTitle` names the flow in the viewer's terms, such as what a request does or what
61
+ a command runs. Never use a module path.
62
+ <!-- /sync:codeflow-publish-render -->
63
+
64
+ ## Maintenance
65
+
66
+ > If you modify this file, or the user asks to change this stage, tell the user that the
67
+ > main `explainer-video-from-coursework` skill's **"Stage 7 — /render"** section must be
68
+ > reconciled to match, and name that section in your reply.
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: craft-video-scene
3
+ description: Author and iterate the animated visuals for one video's segments. Stage 5 of explainer-video-from-coursework; beats anchor to measured narration lines, not hand-written seconds.
4
+ ---
5
+
6
+ # craft-video-scene — author the visuals
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 5 of 8**. This is the
9
+ > **orchestrator** over the audio stage: it loops, and it re-invokes `build-audio` when a
10
+ > beat cannot fit. The audio stage itself is one-shot and never loops back.
11
+ >
12
+ > **A shortfall is no longer routine.** Beat dwells default to the measured span of
13
+ > the lines they cover, so `at + min` is inside the window by construction and a
14
+ > line-anchored scene should report none. If `pacing-check` does report one, the scene
15
+ > **over-asked**: widen the beat's `lines` range first, redesign the beat second, and
16
+ > `pad` the segment only as a last resort.
17
+ >
18
+ > If you do pad, line times survive it (a pad widens the window, never the segment
19
+ > WAV) — but *rewriting the narration* invalidates every line time for that segment, so
20
+ > re-run `/audio` → `/align` → `/resolve-beats`. `resolve-beats --script …` enforces
21
+ > that by comparing the text hash `align` recorded.
22
+ >
23
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
24
+
25
+ > **Timing.** Bracket the stage with the stamp pair so the series accrues a per-video audit;
26
+ > `end` must run before the stage counts as done (see *Timing* in the main skill):
27
+ >
28
+ > ```bash
29
+ > node ../explainer-video-from-coursework/tools/stamp.mjs begin --out out/<name> --video <NN> --stage 5
30
+ > node ../explainer-video-from-coursework/tools/stamp.mjs end --out out/<name> --video <NN> --stage 5
31
+ > ```
32
+
33
+ **Prerequisites:** `out/<name>/<NN>/timeline.json` (from the audio stage) and the
34
+ script for the video; headless Chromium for the `--shots` look (see `SETUP.md`).
35
+
36
+ > A `Playwright cache not found` failure on the `--shots` render means Chromium is absent —
37
+ > relay the `SETUP.md` step to the user and re-run.
38
+
39
+ Each segment is a **beat**: narration plus the visual that illustrates it. Author the
40
+ visual as **visual beats** — steps, each with a minimum dwell and an alignment to the
41
+ narration — never as raw seconds.
42
+
43
+ **Hosts, per segment** (`out/<name>/<NN>/manifest.json`, `key` → `{host, scene}`):
44
+
45
+ - **`svg`** (`src/scene-svg.html`) — the open lane; expose `renderAt(t, svg, timeline)`
46
+ plus `SCENE_BEATS`.
47
+ - **`code`** (`src/scene-code.html`) — code walkthroughs.
48
+ - **`diagram`** (`src/scene-diagram.html`) — declarative `nodes`/`edges`/`reveal`/
49
+ `banners` configs built on `scene-core.js`.
50
+
51
+ **The beat anchors to a line, never to a second** — declare per segment:
52
+
53
+ ```js
54
+ window.SCENE_BEATS = { beats: [
55
+ { id: 'march', line: 1 }, // index into lines.json for this segment
56
+ { id: 'caption', lines: [3, 4], hold: true }, // holds across those lines
57
+ ]};
58
+ ```
59
+
60
+ `line` indexes that segment's lines in `lines.json` (stage 4b). `lines: [from, to]`
61
+ claims a range, and `hold: true` lets a final beat run to the cut.
62
+
63
+ **Do not write `at`.** A hand-written second cannot be validated and will not be
64
+ caught: the module-07 pilot shipped `at: 6.6` for a phrase that begins at 12.6s and
65
+ passed every check. `resolve-beats` rejects a beat with `at` but no `line`, and
66
+ rejects a baked `at` that disagrees with the measured onset by more than 60 ms.
67
+
68
+ ```bash
69
+ node ../explainer-video-from-coursework/tools/resolve-beats.mjs --work out/<name>/<NN>
70
+ ```
71
+
72
+ Run this before `pacing-check`: it resolves each `line` to seconds, writes `beats.json`,
73
+ and fails when a line does not exist, beats run out of order, or a narration line has
74
+ no beat (narration playing with nothing changing on screen).
75
+
76
+ A beat's dwell should be **how long its lines are spoken**. Only add `min` when the
77
+ visual genuinely needs longer than its line — and then reach for a wider `lines` range
78
+ before reaching for `pad` (stage 5's over-ask ladder).
79
+
80
+ ### The loop, expressed as tool invocations
81
+
82
+ ```
83
+ write / edit a scene
84
+ → node ../explainer-video-from-coursework/tools/pacing-check.mjs --scene out/<name>/<NN>/scenes/<name>.js \
85
+ --timeline out/<name>/<NN>/timeline.json --segment <key>
86
+ (a shortfall means the scene over-asked — widen the beat's `lines` range,
87
+ or redesign the beat. Only then consider a pad.)
88
+ → last resort, if the visual really needs more screen time than its words take:
89
+ set `pad` on the script segment and `hold: true` on the beat, then:
90
+ node ../explainer-video-from-coursework/tools/build-audio.mjs --script out/<name>/scripts/<NN>-*.json \
91
+ --out out/<name>/<NN> --voice Aoede
92
+ (free — TTS is text-cached — and it re-tiles the timeline wider)
93
+ line times stay valid: a pad widens the window, never the segment WAV
94
+ → if you REWROTE the narration instead (extending it):
95
+ re-run build-audio, then align.mjs, then resolve-beats.mjs — a new WAV
96
+ invalidates every line time. resolve-beats --script … catches the omission.
97
+ → node ../explainer-video-from-coursework/tools/render.mjs --shots 6,20,34 --work out/<name>/<NN> \
98
+ --scene "src/scene-diagram.html?scene=<name>"
99
+ → LOOK at the stills ← the step nothing automates
100
+ → fix → repeat
101
+ ```
102
+
103
+ The checkers catch timing and structure, never a visual fault — a missing chip, an
104
+ orphaned connector, a wrong colour. Only looking at a rendered frame catches those.
105
+
106
+ **Done when:** `pacing-check` exits 0 **and** every still has been eyeballed.
107
+
108
+ ## Maintenance
109
+
110
+ > If you modify this file, or the user asks to change this stage, tell the user that the
111
+ > main `explainer-video-from-coursework` skill's **"Stage 5 — /scene"** section must be
112
+ > reconciled to match, and name that section in your reply.