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.
- package/.claude-plugin/plugin.json +12 -0
- package/LICENSE +21 -0
- package/README.md +152 -2
- package/cordis.patch.yml +7 -0
- package/lib/index.js +35 -0
- package/package.json +56 -4
- package/plugin.json +13 -0
- package/skills/coursework-author/.synced-from +5 -0
- package/skills/coursework-author/SKILL.md +209 -0
- package/skills/craft-codeflow-flows/SKILL.md +116 -0
- package/skills/craft-codeflow-mine/SKILL.md +207 -0
- package/skills/craft-codeflow-plan/SKILL.md +65 -0
- package/skills/craft-codeflow-scene/SKILL.md +168 -0
- package/skills/craft-video-align/SKILL.md +109 -0
- package/skills/craft-video-assemble/SKILL.md +69 -0
- package/skills/craft-video-audio/SKILL.md +59 -0
- package/skills/craft-video-enrich/SKILL.md +46 -0
- package/skills/craft-video-plan/SKILL.md +57 -0
- package/skills/craft-video-publish/SKILL.md +79 -0
- package/skills/craft-video-render/SKILL.md +68 -0
- package/skills/craft-video-scene/SKILL.md +112 -0
- package/skills/craft-video-script/SKILL.md +80 -0
- package/skills/explainer-video-from-codebase/FLOWS.md +577 -0
- package/skills/explainer-video-from-codebase/SKILL.md +659 -0
- package/skills/explainer-video-from-codebase/tools/apply-flows.mjs +531 -0
- package/skills/explainer-video-from-codebase/tools/approve-flows.mjs +56 -0
- package/skills/explainer-video-from-codebase/tools/codemap-md.mjs +116 -0
- package/skills/explainer-video-from-codebase/tools/config/dotenv.mjs +13 -0
- package/skills/explainer-video-from-codebase/tools/config/json.mjs +14 -0
- package/skills/explainer-video-from-codebase/tools/config/redact.mjs +16 -0
- package/skills/explainer-video-from-codebase/tools/config/toml-lite.mjs +13 -0
- package/skills/explainer-video-from-codebase/tools/config/yaml-lite.mjs +18 -0
- package/skills/explainer-video-from-codebase/tools/excerpt-check.mjs +186 -0
- package/skills/explainer-video-from-codebase/tools/excerpt.mjs +116 -0
- package/skills/explainer-video-from-codebase/tools/flow-diagram.mjs +168 -0
- package/skills/explainer-video-from-codebase/tools/flow-plan.mjs +129 -0
- package/skills/explainer-video-from-codebase/tools/flows-md.mjs +87 -0
- package/skills/explainer-video-from-codebase/tools/lang/c.mjs +684 -0
- package/skills/explainer-video-from-codebase/tools/lang/go.mjs +673 -0
- package/skills/explainer-video-from-codebase/tools/lang/python-config.mjs +143 -0
- package/skills/explainer-video-from-codebase/tools/lang/python-deps.mjs +62 -0
- package/skills/explainer-video-from-codebase/tools/lang/python-stdlib.mjs +24 -0
- package/skills/explainer-video-from-codebase/tools/lang/python.mjs +609 -0
- package/skills/explainer-video-from-codebase/tools/lang/typescript.mjs +963 -0
- package/skills/explainer-video-from-codebase/tools/lib/decompose.mjs +102 -0
- package/skills/explainer-video-from-codebase/tools/lib/groups.mjs +77 -0
- package/skills/explainer-video-from-codebase/tools/lib/observe-coverage.mjs +68 -0
- package/skills/explainer-video-from-codebase/tools/lib/observe-v8.mjs +232 -0
- package/skills/explainer-video-from-codebase/tools/lib/observe.mjs +162 -0
- package/skills/explainer-video-from-codebase/tools/lib/redact.mjs +28 -0
- package/skills/explainer-video-from-codebase/tools/lib/sandbox.mjs +97 -0
- package/skills/explainer-video-from-codebase/tools/lib/test-links.mjs +28 -0
- package/skills/explainer-video-from-codebase/tools/lib/toml.mjs +129 -0
- package/skills/explainer-video-from-codebase/tools/lib/trace-c.mjs +95 -0
- package/skills/explainer-video-from-codebase/tools/lib/trace-go.mjs +101 -0
- package/skills/explainer-video-from-codebase/tools/lib/trace-js.mjs +136 -0
- package/skills/explainer-video-from-codebase/tools/lib/tree-sitter.mjs +19 -0
- package/skills/explainer-video-from-codebase/tools/lib/walk.mjs +138 -0
- package/skills/explainer-video-from-codebase/tools/mine.mjs +409 -0
- package/skills/explainer-video-from-codebase/tools/propose.mjs +127 -0
- package/skills/explainer-video-from-codebase/tools/rank.mjs +119 -0
- package/skills/explainer-video-from-codebase/tools/ranking-md.mjs +76 -0
- package/skills/explainer-video-from-codebase/tools/rules/c-handlers.mjs +53 -0
- package/skills/explainer-video-from-codebase/tools/rules/celery-taskiq.mjs +48 -0
- package/skills/explainer-video-from-codebase/tools/rules/click-typer.mjs +6 -0
- package/skills/explainer-video-from-codebase/tools/rules/express.mjs +72 -0
- package/skills/explainer-video-from-codebase/tools/rules/fastapi.mjs +99 -0
- package/skills/explainer-video-from-codebase/tools/rules/go-http.mjs +173 -0
- package/skills/explainer-video-from-codebase/tools/rules/go-test.mjs +108 -0
- package/skills/explainer-video-from-codebase/tools/rules/grpc.mjs +57 -0
- package/skills/explainer-video-from-codebase/tools/rules/gtest.mjs +97 -0
- package/skills/explainer-video-from-codebase/tools/rules/haystack.mjs +55 -0
- package/skills/explainer-video-from-codebase/tools/rules/jest-vitest.mjs +100 -0
- package/skills/explainer-video-from-codebase/tools/rules/js-http-client.mjs +111 -0
- package/skills/explainer-video-from-codebase/tools/rules/nextjs.mjs +53 -0
- package/skills/explainer-video-from-codebase/tools/rules/process-spawn.mjs +49 -0
- package/skills/explainer-video-from-codebase/tools/rules/py-http-client.mjs +26 -0
- package/skills/explainer-video-from-codebase/tools/rules/pytest.mjs +107 -0
- package/skills/explainer-video-from-codebase/tools/rules/react-router.mjs +17 -0
- package/skills/explainer-video-from-codebase/tools/rules/registry.mjs +31 -0
- package/skills/explainer-video-from-codebase/tools/trace/c/fmtrace-sh +14 -0
- package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-net.cjs +33 -0
- package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-srcmap.cjs +41 -0
- package/skills/explainer-video-from-codebase/tools/trace/python/fmtrace_plugin.py +87 -0
- package/skills/explainer-video-from-codebase/tools/trace/python/sitecustomize.py +330 -0
- package/skills/explainer-video-from-codebase/tools/trace/sandbox/linux-bwrap.sh +73 -0
- package/skills/explainer-video-from-codebase/tools/trace/sandbox/macos-nonet.sb +41 -0
- package/skills/explainer-video-from-codebase/tools/trace.mjs +577 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-c.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-cpp.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-go.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-javascript.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-python.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-typescript.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/web-tree-sitter.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/MANIFEST.json +172 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-c.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-cpp.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-go.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-javascript.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-python.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-tsx.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-typescript.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.js +4081 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.wasm +0 -0
- package/skills/explainer-video-from-coursework/ARCHITECTURE.md +59 -0
- package/skills/explainer-video-from-coursework/PIPELINE.md +343 -0
- package/skills/explainer-video-from-coursework/ROADMAP.md +725 -0
- package/skills/explainer-video-from-coursework/SERIES.md +232 -0
- package/skills/explainer-video-from-coursework/SETUP.md +105 -0
- package/skills/explainer-video-from-coursework/SKILL.md +858 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-chart.js +12 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-equation.js +10 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-hubspoke.js +7 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-loopflow.js +7 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-sequence.js +12 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-timeline.js +11 -0
- package/skills/explainer-video-from-coursework/src/scene-code.html +127 -0
- package/skills/explainer-video-from-coursework/src/scene-code.js +379 -0
- package/skills/explainer-video-from-coursework/src/scene-core.js +1250 -0
- package/skills/explainer-video-from-coursework/src/scene-diagram.html +50 -0
- package/skills/explainer-video-from-coursework/src/scene-svg.html +50 -0
- package/skills/explainer-video-from-coursework/src/timeline.js +297 -0
- package/skills/explainer-video-from-coursework/src/timing-chart.js +160 -0
- package/skills/explainer-video-from-coursework/tools/align.mjs +419 -0
- package/skills/explainer-video-from-coursework/tools/apply-enrichment.mjs +206 -0
- package/skills/explainer-video-from-coursework/tools/assemble.mjs +223 -0
- package/skills/explainer-video-from-coursework/tools/build-audio.mjs +234 -0
- package/skills/explainer-video-from-coursework/tools/chromium.mjs +73 -0
- package/skills/explainer-video-from-coursework/tools/debug-page.mjs +75 -0
- package/skills/explainer-video-from-coursework/tools/env.mjs +30 -0
- package/skills/explainer-video-from-coursework/tools/frame-dedup.mjs +131 -0
- package/skills/explainer-video-from-coursework/tools/key-source.mjs +151 -0
- package/skills/explainer-video-from-coursework/tools/model.mjs +21 -0
- package/skills/explainer-video-from-coursework/tools/naming.mjs +38 -0
- package/skills/explainer-video-from-coursework/tools/onset-audit.mjs +51 -0
- package/skills/explainer-video-from-coursework/tools/pacing-check.mjs +350 -0
- package/skills/explainer-video-from-coursework/tools/planner.mjs +616 -0
- package/skills/explainer-video-from-coursework/tools/publish.mjs +230 -0
- package/skills/explainer-video-from-coursework/tools/render.mjs +205 -0
- package/skills/explainer-video-from-coursework/tools/resolve-beats.mjs +239 -0
- package/skills/explainer-video-from-coursework/tools/script-check.mjs +174 -0
- package/skills/explainer-video-from-coursework/tools/stamp.mjs +71 -0
- package/skills/explainer-video-from-coursework/tools/timing-report.mjs +299 -0
- package/skills/explainer-video-from-coursework/tools/transcribe-gemini.mjs +96 -0
- package/skills/explainer-video-from-coursework/tools/tts-gemini.mjs +394 -0
- package/skills/explainer-video-from-coursework/tools/verify-audio.mjs +88 -0
- package/skills/explainer-video-from-coursework/tools/workdir.mjs +124 -0
- package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js +8 -0
- package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js.LICENSE.txt +16 -0
|
Binary file
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Architecture — who does what
|
|
2
|
+
|
|
3
|
+
The single rule this project is built on:
|
|
4
|
+
|
|
5
|
+
> **Code does what must be exact. The host agent does what requires judgment.
|
|
6
|
+
> Gemini is used for one thing only: text-to-speech.**
|
|
7
|
+
|
|
8
|
+
## Why
|
|
9
|
+
|
|
10
|
+
An LLM is already in the loop — it is the agent running this skill. Delegating
|
|
11
|
+
judgment to a *second* LLM via an API call is strictly worse:
|
|
12
|
+
|
|
13
|
+
- **Less context.** A fresh API call sees only the prompt we construct. The agent
|
|
14
|
+
has the plan, the sources, the conversation, and the user's stated preferences.
|
|
15
|
+
- **More failure modes.** Rate limits, 503s, quota exhaustion, a second vendor, a
|
|
16
|
+
key on disk. During development, an optional text adapter cost ~10 wasted API
|
|
17
|
+
calls and three model fallbacks to do something the agent could do natively.
|
|
18
|
+
- **No reproducibility gain.** Judgment is not the part that needs to be
|
|
19
|
+
deterministic — the *merge and validation* around it is, and that stays in code.
|
|
20
|
+
|
|
21
|
+
## Division of labour
|
|
22
|
+
|
|
23
|
+
| Stage | Owner | Network? |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `planner.mjs` — parse, count, pack, assert coverage | **code** | none |
|
|
26
|
+
| enrichment — titles, forms, curation (skip / merge) | **agent** | none |
|
|
27
|
+
| `apply-enrichment.mjs` — merge + validate | **code** | none |
|
|
28
|
+
| script writing — narration, styles, beats | **agent** | none |
|
|
29
|
+
| audio — TTS | **code** | **Gemini TTS API** ← the only API call in the pipeline |
|
|
30
|
+
| publish config — chapter grouping, titles, blurb | **agent** | none |
|
|
31
|
+
| `publish.mjs` — resolve timecodes, emit chapters/captions, validate | **code** | none |
|
|
32
|
+
| `render.mjs` — Chromium frames, ffmpeg | **code** | none |
|
|
33
|
+
|
|
34
|
+
Everything except audio runs offline. The planner and the merge/validate step
|
|
35
|
+
require **no API key at all** — verified by running them with no key set.
|
|
36
|
+
|
|
37
|
+
## Why the determinism lives in code
|
|
38
|
+
|
|
39
|
+
The agent's output is *checked*, not trusted. `apply-enrichment.mjs` rejects
|
|
40
|
+
empty titles, `Part N` placeholders, ellipses, directory-slug titles
|
|
41
|
+
(`load-balancing`), duplicate titles, forms outside the enum, references to videos
|
|
42
|
+
that don't exist, and `skip`/`mergeInto` entries without a stated reason. That is
|
|
43
|
+
the same discipline as
|
|
44
|
+
`compare-shots.mjs` for the scene port and the coverage assertion in the planner:
|
|
45
|
+
**assert, don't assume.**
|
|
46
|
+
|
|
47
|
+
## The trap this rule avoids
|
|
48
|
+
|
|
49
|
+
A pipeline that reaches for an API whenever it needs "an LLM step" ends up with
|
|
50
|
+
two models, two quotas, two failure modes, and a reasoning step that is worse
|
|
51
|
+
than the agent it already had. If a stage needs judgment, the skill's `SKILL.md`
|
|
52
|
+
should say *what* to decide and let the host agent decide it — not shell out.
|
|
53
|
+
|
|
54
|
+
## If you ever genuinely need unattended batch runs
|
|
55
|
+
|
|
56
|
+
Running 40 topics overnight with no agent present is a real use case, and it is
|
|
57
|
+
the *only* justification for an API-backed enrichment step. If that need appears,
|
|
58
|
+
add it back deliberately — as a clearly separate adapter, not as the default path
|
|
59
|
+
— and accept the quota and context costs knowingly.
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Pipeline — the stage sequence
|
|
2
|
+
|
|
3
|
+
How a directory of notes becomes a finished video. Eight stages.
|
|
4
|
+
|
|
5
|
+
**Bold = a stage where the agent supplies judgment.** Everything else is code.
|
|
6
|
+
|
|
7
|
+
> **The `/plan` … `/assemble` names are stage labels, not executable commands — there
|
|
8
|
+
> is still no dispatcher, and `package.json` declares no `bin`.** Every stage is a
|
|
9
|
+
> `node tools/<tool>.mjs` invocation, which is what each section shows.
|
|
10
|
+
>
|
|
11
|
+
> [`SKILL.md`](./SKILL.md) is the **agent-facing contract** — what to do, and each
|
|
12
|
+
> stage's `done when` gate. This file is the **flow and its rationale**: what each
|
|
13
|
+
> stage consumes and produces, and why it sits where it does. Where the two could
|
|
14
|
+
> drift, `SKILL.md` wins.
|
|
15
|
+
|
|
16
|
+
> **Path note.** The stage commands below still show the older single-work-dir form
|
|
17
|
+
> (`out/<name>`) in places; the per-video layout is `out/<name>/<NN>/`. Tracked as
|
|
18
|
+
> ROADMAP §2.9.8.
|
|
19
|
+
|
|
20
|
+
Companion docs: [`ARCHITECTURE.md`](./ARCHITECTURE.md) (who does what, and why no
|
|
21
|
+
LLM API is used for reasoning) · [`ROADMAP.md`](./ROADMAP.md) (what's missing).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
| | |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Node ≥ 22 | `render.mjs` uses the global `WebSocket` for CDP |
|
|
30
|
+
| `ffmpeg`, `ffprobe` | encoding, muxing, measurement |
|
|
31
|
+
| A Chromium build | headless frame capture |
|
|
32
|
+
| A Gemini API key | TTS only — stages 1–3 and 5–8 need no network; stored in the OS secret store, never in env or a file |
|
|
33
|
+
|
|
34
|
+
| Tool | Role |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `tools/key-source.mjs` | resolves the key from the OS secret store — macOS Keychain · Linux `secret-tool` · Windows Credential Manager — never env or a plaintext file |
|
|
37
|
+
| `tools/tts-gemini.mjs` | Gemini TTS client — **the only file that calls an API** |
|
|
38
|
+
|
|
39
|
+
The full, cited, per-OS setup for all four prerequisites lives in
|
|
40
|
+
[`SETUP.md`](./SETUP.md) — the single source, kept in the skill folder so the skill is
|
|
41
|
+
self-contained. The per-stage files point at it too, and the tools print the relevant
|
|
42
|
+
command when a prerequisite is missing.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## The stages
|
|
47
|
+
|
|
48
|
+
### 1 · `/plan` — decompose the source
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
node tools/planner.mjs <notesDir> --out out/<name> --max-min 25
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
There is **no dedupe flag and no name-based inference**. The planner excludes
|
|
55
|
+
exactly one category — navigation headings ("Table of Contents", "Contents",
|
|
56
|
+
"TOC", "Index") — because that is unambiguous in any tree. Everything else is
|
|
57
|
+
planned.
|
|
58
|
+
|
|
59
|
+
An earlier version inferred "index pointers" by matching section titles against
|
|
60
|
+
directory names. It was deleted: it silently dropped real content on unfamiliar
|
|
61
|
+
trees, gating it behind a flag only moved that burden onto the user, and on the
|
|
62
|
+
reference tree it changed no video count, no orphan count, and no longest video —
|
|
63
|
+
only the series total, by 3.2 min out of 62.5.
|
|
64
|
+
|
|
65
|
+
Errors (exit 1) on: a path that doesn't exist, a path that isn't a directory, or a
|
|
66
|
+
tree with no markdown at all.
|
|
67
|
+
|
|
68
|
+
| | |
|
|
69
|
+
|---|---|
|
|
70
|
+
| Tool | `tools/planner.mjs` |
|
|
71
|
+
| In | a directory of notes (markdown + code artefacts) |
|
|
72
|
+
| Out | `out/<name>/plan.json`, `out/<name>/plan.md` |
|
|
73
|
+
| Asserts | **coverage** — every content-bearing heading is assigned to a video, or is a navigation heading. Orphans exit non-zero. |
|
|
74
|
+
| Network | none |
|
|
75
|
+
|
|
76
|
+
Also emits a parser self-check: raw heading lines vs parsed headings, so a
|
|
77
|
+
swallowed section can't pass coverage vacuously.
|
|
78
|
+
|
|
79
|
+
**The duration model is a guardrail, not a prediction.** Both constants are now
|
|
80
|
+
**measured, not guessed**, and live in one place (`tools/model.mjs`): `wpm 153`
|
|
81
|
+
(Gemini Aoede, 508 words → 199s of audio) and `expansion 1.10` (205.7s picture /
|
|
82
|
+
199s narration = 1.03, plus a small buffer). The planner, `script-check`, and
|
|
83
|
+
`pacing-check` all import from there, so the stages cannot disagree about the
|
|
84
|
+
budget. Use the plan to partition the source and keep each video under the cap;
|
|
85
|
+
take real durations from stage 4, which *measures* them.
|
|
86
|
+
|
|
87
|
+
### 2 · `/enrich` — titles, forms, and curation
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
node tools/apply-enrichment.mjs --plan out/<name>/plan.json \
|
|
91
|
+
--enrichment out/<name>/enrichment.json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| | |
|
|
95
|
+
|---|---|
|
|
96
|
+
| **Agent writes** | `out/<name>/enrichment.json` — one entry per video |
|
|
97
|
+
| Tool | `tools/apply-enrichment.mjs` |
|
|
98
|
+
| Out | `plan.enriched.json`, `plan.enriched.md` |
|
|
99
|
+
| Network | none |
|
|
100
|
+
|
|
101
|
+
**Title contract.** The planner's titles are placeholders; the agent replaces them
|
|
102
|
+
with real ones — descriptive, **unique**, written for a human scrolling YouTube,
|
|
103
|
+
not a directory name. The content is given precisely: `plan.json` carries
|
|
104
|
+
`sectionSpans` (each section's `src`, `title`, and 1-based `line`/`endLine`), so the
|
|
105
|
+
agent titles from the substance it reads at those spans, not from a slug.
|
|
106
|
+
`apply-enrichment` enforces the floor: it rejects empty titles, `Part N`
|
|
107
|
+
placeholders, ellipses, **directory-slug titles** (`load-balancing`), **duplicates**,
|
|
108
|
+
forms outside the enum, and references to non-existent videos.
|
|
109
|
+
|
|
110
|
+
**Curation.** A deterministic planner cannot tell an overview page from real
|
|
111
|
+
content, so it plans both — the agent decides. Each entry may carry `skip: true` or
|
|
112
|
+
`mergeInto: <index>`, both requiring a stated `why`. `skip` drops the video and
|
|
113
|
+
records its sections; `mergeInto` folds the video's sections and words into the
|
|
114
|
+
target. Both land in `plan.removed` with the reason, so nothing is dropped silently
|
|
115
|
+
and coverage stays auditable.
|
|
116
|
+
|
|
117
|
+
### 3 · `/script` — narration
|
|
118
|
+
|
|
119
|
+
| | |
|
|
120
|
+
|---|---|
|
|
121
|
+
| **Agent writes** | `out/<name>/scripts/<NN>-<slug>.json` — per segment: `key`, `text`, `style`, `visual`, `covers` |
|
|
122
|
+
| Tool | `tools/script-check.mjs` |
|
|
123
|
+
| Asserts | unique keys · speakability (no URLs, markdown, code) · duration budget vs the plan · **every source section the plan assigned is actually narrated** |
|
|
124
|
+
| Network | none |
|
|
125
|
+
|
|
126
|
+
`style` is sent to TTS as `speech_metadata` (delivery direction); `text` is sent
|
|
127
|
+
**verbatim**.
|
|
128
|
+
|
|
129
|
+
**Script contract.** The agent composes the spoken narration; `script-check` gates
|
|
130
|
+
it. The rules:
|
|
131
|
+
|
|
132
|
+
- **Register** — a calm, methodical teacher who explains through story: each
|
|
133
|
+
concept opens as a concrete scenario ("a request arrives…", "one server falls
|
|
134
|
+
over…"), then unpacks step by step. No edge, no hot takes; steady and warm;
|
|
135
|
+
analogies are welcome as teaching devices.
|
|
136
|
+
- **Granularity** — one segment per visual beat (one idea, one reveal). Roughly
|
|
137
|
+
40–80 words; **≤130 words** hard ("one breath"). Split rather than bloat.
|
|
138
|
+
- **Per segment** — `key` (unique, short), `text` (spoken **verbatim**; no URLs,
|
|
139
|
+
markdown, or code), `style` (delivery direction), `visual` (a hint for the scene
|
|
140
|
+
stage), `covers` (the plan sections this segment narrates).
|
|
141
|
+
- **`covers`** — copy the exact section identity from `plan.enriched.json`
|
|
142
|
+
(`"src :: title @L<line>"`). Every assigned section must appear in some segment's
|
|
143
|
+
`covers`; `script-check` errors on any uncovered section.
|
|
144
|
+
- **Budget** — target `planTargetMinutes` from the plan; stay within its 55%–125%
|
|
145
|
+
band (`script-check` errors below, warns above).
|
|
146
|
+
|
|
147
|
+
### 4 · `/audio` — TTS and the measured timeline
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
node tools/build-audio.mjs --script out/<name>/scripts/NN-*.json --out out/<name>/<NN> --voice Aoede
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| | |
|
|
154
|
+
|---|---|
|
|
155
|
+
| Tool | `tools/build-audio.mjs` → `tools/tts-gemini.mjs` |
|
|
156
|
+
| Out | `narration/<seg>.wav`, `narration.wav`, `timeline.json`, `timeline.js` |
|
|
157
|
+
| Network | **yes — the only API-spending stage** |
|
|
158
|
+
| Notes | durations are **measured** with ffprobe, never estimated. Emits per-segment **windows** that tile the timeline. |
|
|
159
|
+
|
|
160
|
+
The cache is keyed on the **spoken text only** — `narration/<key>.src` stores the
|
|
161
|
+
exact text the `.wav` was built from — so re-running after a wording tweak costs 0
|
|
162
|
+
calls. It does **not** cover `--voice`, `style`, or `--model`: change one of those
|
|
163
|
+
and the previous audio is silently reused. Pass `--force` when you mean to
|
|
164
|
+
re-synthesize.
|
|
165
|
+
|
|
166
|
+
### 5 · `/scene` — author the visuals
|
|
167
|
+
|
|
168
|
+
| | |
|
|
169
|
+
|---|---|
|
|
170
|
+
| **Agent writes** | `scenes/<name>.js` — one config per segment |
|
|
171
|
+
| Binding | `scenes/manifest.json` maps segment → `{host, scene}` |
|
|
172
|
+
| Tool | `tools/pacing-check.mjs` |
|
|
173
|
+
| Asserts | reveal rate 0.45–1.6 lines/sec · scene fits the **measured** narration · **dead air** (no visual change *and* no narration) ≤ 3.5s |
|
|
174
|
+
| Network | none |
|
|
175
|
+
|
|
176
|
+
Timings are written **relative to narration** — `"srp@4.6"` resolves to
|
|
177
|
+
`segment_start + 4.6`, so regenerating audio re-locks the choreography.
|
|
178
|
+
|
|
179
|
+
### 6 · `/publish` — chapters and captions
|
|
180
|
+
|
|
181
|
+
| | |
|
|
182
|
+
|---|---|
|
|
183
|
+
| **Agent writes** | `out/<name>/<NN>/publish.json` — `chapters[]` (segment keys + `title`) and a `blurb` |
|
|
184
|
+
| Tool | `tools/publish.mjs` |
|
|
185
|
+
| Out | `description.md` (blurb + chapter block), `captions.srt` — copied to `--dest` |
|
|
186
|
+
| Asserts | first timecode `0:00` · ≥3 chapters · each ≥10s · ascending · every segment key resolves · titles present, ≤40 chars, not duplicated or near-duplicated · cues ascend, don't overlap, one per segment |
|
|
187
|
+
| Network | none |
|
|
188
|
+
|
|
189
|
+
Placed **before** the expensive stages on purpose. Both artifacts are pure functions of
|
|
190
|
+
data already frozen when stage 5 exits — the measured windows in `timeline.json`, and
|
|
191
|
+
the verbatim narration — so the metadata can be reviewed while a fix is still cheap.
|
|
192
|
+
No TTS, no Chromium, so re-running it is free.
|
|
193
|
+
|
|
194
|
+
Chapter **titles are authored** (read the chapter's narration, then name it); the
|
|
195
|
+
**times are resolved** from the windows, so a re-render regenerates correct timecodes
|
|
196
|
+
for free. Reusing the scene `stageNames` badges would cost nothing but is wrong: they
|
|
197
|
+
are presentation labels — all uppercase, one missing (the title card disables its
|
|
198
|
+
badge), near-duplicate pairs — and they resist mechanical title-casing, because a video
|
|
199
|
+
contains `IT · TOOLS`, which would become `It · Tools`.
|
|
200
|
+
|
|
201
|
+
Captions are one cue per segment, text taken from `narration/<key>.src` (the exact text
|
|
202
|
+
the WAV was built from — the audio is the truth, not `script.json`), emitted as **SRT
|
|
203
|
+
only**. YouTube accepts both SRT and VTT, so one file covers the upload; VTT matters
|
|
204
|
+
only for an HTML5 `<track>` in a web embed, and re-running this stage is free, so it is
|
|
205
|
+
deferred rather than pre-built. Finer cue splitting is possible only by estimation — the TTS
|
|
206
|
+
returns audio with **no word timings** — so it is deliberately not attempted.
|
|
207
|
+
|
|
208
|
+
**Chapters come from the description, nothing else.** YouTube builds them only from
|
|
209
|
+
timestamp lines in the **description**, start-time only. A caption file (SRT/VTT) does
|
|
210
|
+
**not** create chapters, and neither does embedded MP4 chapter metadata. The Data API
|
|
211
|
+
confirms the asymmetry: captions get a whole resource with an `insert` verb
|
|
212
|
+
([`captions`](https://developers.google.com/youtube/v3/docs/captions)), while the
|
|
213
|
+
[`videos`](https://developers.google.com/youtube/v3/docs/videos) resource has no
|
|
214
|
+
chapters field anywhere — `snippet.description` is a plain string, and
|
|
215
|
+
`contentDetails.caption` is only a yes/no flag. Chapters therefore have nowhere to live
|
|
216
|
+
except the description text, which is why the block is emitted **inside**
|
|
217
|
+
`description.md` rather than as its own file or a caption by-product.
|
|
218
|
+
|
|
219
|
+
**Chapters also need channel eligibility.** They are a YouTube advanced feature: a channel
|
|
220
|
+
that has not qualified for advanced-feature eligibility renders **no** chapters at all,
|
|
221
|
+
however valid the list. Its description timestamps still work as jump-to-section links —
|
|
222
|
+
what is missing is the segmented progress bar and Google key moments. Nothing in
|
|
223
|
+
`description.md` can change this, so absent chapters are not a formatting fault.
|
|
224
|
+
|
|
225
|
+
### 7 · `/render` — frames to MP4
|
|
226
|
+
|
|
227
|
+
| | |
|
|
228
|
+
|---|---|
|
|
229
|
+
| Tool | `tools/render.mjs` (+ `tools/frame-dedup.mjs`) |
|
|
230
|
+
| Does | headless Chromium via CDP → PNG sequence → ffmpeg. Each **distinct** frame is captured once; repeats are hardlinked from their predecessor (measured 82% on a diagram scene, 90% on an svg scene, 99.7% on a held card). Guards: scenes declaring CSS/web animations take the plain capture path, and an audit every 30 frames diffs a real screenshot against the frame written. No flag — it is the only capture path. |
|
|
231
|
+
| Flags | `--scene`, `--shots` (QA stills), `--range t0,t1`, `--outdir`, `--name`, `--narration` · also `--fps`, `--crf`, `--shot-dir`, `--keep-frames` |
|
|
232
|
+
| Diagnose | `tools/debug-page.mjs` — prints boot-time console errors; `render.mjs` only reports "never exposed VIDEO_META" |
|
|
233
|
+
|
|
234
|
+
### 8 · `/assemble` — the finished video
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
node tools/assemble.mjs --script out/<name>/scripts/NN-*.json \
|
|
238
|
+
--work out/<name>/<NN> --name <output>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
| | |
|
|
242
|
+
|---|---|
|
|
243
|
+
| Tool | `tools/assemble.mjs` + `tools/verify-audio.mjs` |
|
|
244
|
+
| Does | per-segment time-sliced render (**silent**) → concat → mux narration **once** → verify |
|
|
245
|
+
| Asserts | windows tile the timeline with no gaps · finished audio matches the narration at **sample level** (residual energy ≤ 0.25) |
|
|
246
|
+
| Network | none |
|
|
247
|
+
|
|
248
|
+
Parts are rendered silent deliberately. Muxing narration per part restarts the
|
|
249
|
+
whole track at every segment boundary — a bug this stage used to have.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Renderers
|
|
254
|
+
|
|
255
|
+
| File | Role |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `src/scene-core.js` | diagram renderer — node kinds `client`/`server`/`hub`/`box` (UML), edges, flows, pings, rules, banners, overlays |
|
|
258
|
+
| `src/scene-diagram.html` | host; selects the config via `?scene=<name>` |
|
|
259
|
+
| `src/scene-code.js` | code + refactor renderer — code panels, before/after |
|
|
260
|
+
| `src/scene-code.html` | host; selects the config via `?scene=<name>` |
|
|
261
|
+
| `<work>/timeline.js` | **generated** by stage 4 into the per-video work dir; browser-loadable measured timeline |
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## End-to-end (video 6, as run)
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# 1 + 2 — plan, then enrich
|
|
269
|
+
node tools/planner.mjs <lld-notes> --out out/lld --max-min 25
|
|
270
|
+
# → agent writes out/lld/enrichment.json
|
|
271
|
+
node tools/apply-enrichment.mjs --plan out/lld/plan.json --enrichment out/lld/enrichment.json
|
|
272
|
+
|
|
273
|
+
# 3 — script (agent writes scripts/06-solid-principles-violated-and-fixed.json)
|
|
274
|
+
node tools/script-check.mjs --script out/lld/scripts/06-*.json --plan out/lld/plan.enriched.json
|
|
275
|
+
|
|
276
|
+
# 4 — audio
|
|
277
|
+
node tools/build-audio.mjs --script out/lld/scripts/06-*.json --out out/lld/06 --voice Aoede
|
|
278
|
+
|
|
279
|
+
# 5 — scenes (agent writes 7 configs in scenes/, registered in scenes/manifest.json)
|
|
280
|
+
node tools/pacing-check.mjs --scene out/lld/06/scenes/<name>.js --timeline out/lld/06/timeline.json --segment <key>
|
|
281
|
+
|
|
282
|
+
# 6 — publish (agent writes publish.json: chapters + blurb)
|
|
283
|
+
node tools/publish.mjs --work out/lld/06 --script out/lld/scripts/06-*.json --dest <destination>
|
|
284
|
+
|
|
285
|
+
# 7 + 8 — render and assemble
|
|
286
|
+
node tools/assemble.mjs --script out/lld/scripts/06-*.json --work out/lld/06 --name video6-solid
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Result: `out/lld/06/video6-solid.mp4` — 7 segments, 205.70s of picture against
|
|
290
|
+
199.00s of measured narration (the final segment holds past its narration).
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## The authoring loop (where the real time goes)
|
|
295
|
+
|
|
296
|
+
Stage 5 is not a single command. The inner loop is:
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
write / edit a scene config
|
|
300
|
+
→ node tools/pacing-check.mjs … (timing, readability, dead air)
|
|
301
|
+
→ node tools/render.mjs --shots 6,20,34 … (a handful of stills)
|
|
302
|
+
→ LOOK at the stills ← the step nothing automates
|
|
303
|
+
→ fix → repeat
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
That loop ran roughly **eight times** for video 6.
|
|
307
|
+
|
|
308
|
+
**The checkers caught timing and structure. They never once caught a visual fault.**
|
|
309
|
+
Every visual bug — missing chips, orphaned connectors, green clients, an absent
|
|
310
|
+
fail marker, a box drawn from an empty API field — was found by looking at a
|
|
311
|
+
rendered frame.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Bug log
|
|
316
|
+
|
|
317
|
+
What went wrong, and what actually surfaced it.
|
|
318
|
+
|
|
319
|
+
| Bug | Surfaced by |
|
|
320
|
+
|---|---|
|
|
321
|
+
| Diagram renderer read the SDK's convenience field instead of the REST path | dumping the real response |
|
|
322
|
+
| Retry logic fought a *daily* quota for 8 minutes | reading the error text |
|
|
323
|
+
| A `<script>` fence behind a list marker swallowed 3 headings | parser self-check added afterwards |
|
|
324
|
+
| Coverage passing *vacuously* (a subtree over-claim masked real orphans) | fixing the over-claim |
|
|
325
|
+
| Reveals 2.5× too fast to read | **the user's eye** |
|
|
326
|
+
| 7.3s of dead air at the end | **the user's eye** |
|
|
327
|
+
| Audio restarting at every segment boundary | **the user's ear** |
|
|
328
|
+
| A patch that silently matched nothing (`+ 30` had become `+ 32`) | the rendered frame still showing no marker |
|
|
329
|
+
|
|
330
|
+
The last four are the lesson: **cheap checks passed while the real question went
|
|
331
|
+
unasked.** Declaring "verified" off a per-stage check is not verification of the
|
|
332
|
+
artefact a human consumes.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Current limitations
|
|
337
|
+
|
|
338
|
+
See [`ROADMAP.md`](./ROADMAP.md) §2. In short: renders run **one at a time** (the
|
|
339
|
+
shared Chromium `--port` — per-video work dirs mean the videos themselves are
|
|
340
|
+
independent), the Chromium and key locations are per-platform but have only been
|
|
341
|
+
verified on macOS (§2.1), and `SKILL.md` has not yet passed the **acceptance test** in §4:
|
|
342
|
+
hand a fresh agent the skill with no conversation history and see whether it ships a
|
|
343
|
+
video unaided.
|