explainer-video 0.0.0-stage → 0.4.1
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 +154 -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 +70 -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/craft-video-thumbnail/SKILL.md +168 -0
- package/skills/explainer-video-from-codebase/FLOWS.md +577 -0
- package/skills/explainer-video-from-codebase/SKILL.md +698 -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 +61 -0
- package/skills/explainer-video-from-coursework/PIPELINE.md +369 -0
- package/skills/explainer-video-from-coursework/ROADMAP.md +727 -0
- package/skills/explainer-video-from-coursework/SERIES.md +243 -0
- package/skills/explainer-video-from-coursework/SETUP.md +105 -0
- package/skills/explainer-video-from-coursework/SKILL.md +998 -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/thumbnail.html +119 -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/thumbnail.mjs +224 -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
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
# Roadmap — what exists, what's left, what's questionable
|
|
2
|
+
|
|
3
|
+
Status snapshot for the `explainer-video-from-coursework` skill. Companion to
|
|
4
|
+
[`ARCHITECTURE.md`](./ARCHITECTURE.md), which states the code-vs-agent boundary.
|
|
5
|
+
|
|
6
|
+
Last updated after the `what-is-a-harness` run — videos 1–5 shipped end-to-end
|
|
7
|
+
(83 segments, 83 scenes, 29 min of finished video) and `/publish` built. Its P1 findings
|
|
8
|
+
are §2.9; the publish contract, the per-video work-dir layout and the beat model are
|
|
9
|
+
recorded as shipped in §1. Previously: the `high-level-design` run — video 30 through the
|
|
10
|
+
scene stage (15 scenes, `pacing-check` green, stills reviewed) — and the render and
|
|
11
|
+
assemble stages written into `SKILL.md`; video 6 (SOLID) completed end-to-end.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. What exists
|
|
16
|
+
|
|
17
|
+
### The skill
|
|
18
|
+
|
|
19
|
+
`SKILL.md` — every stage (plan, enrich, script, audio, align, scene, publish, thumbnail,
|
|
20
|
+
render, assemble), each with its command, its `done when` gate, and the visual-check
|
|
21
|
+
protocol (vision gate + the `LOOK` step). Distributed as one plugin
|
|
22
|
+
(`plugin.json` + `skills/`) — see §2.3.
|
|
23
|
+
|
|
24
|
+
### The stage skills
|
|
25
|
+
|
|
26
|
+
One sibling skill per stage, at `skills/craft-video-<stage>/SKILL.md` — top-level
|
|
27
|
+
under `skills/`, so every platform that reads the plugin discovers them all. Each is a
|
|
28
|
+
complete contract for its stage — prerequisites, command, what the agent writes, hard
|
|
29
|
+
checks, `done when` — and each is separately invocable, so one stage can be re-run alone.
|
|
30
|
+
They reach the pipeline's tools as `../explainer-video-from-coursework/tools/<tool>.mjs`,
|
|
31
|
+
resolved against the stage skill's own folder.
|
|
32
|
+
|
|
33
|
+
`SKILL.md` stays **monolithic on purpose** — a full run loads in one pass — and is the
|
|
34
|
+
contract of record. Every stage file ends with a **Maintenance** note requiring the
|
|
35
|
+
matching `SKILL.md` section to be reconciled whenever the stage file changes: edit one,
|
|
36
|
+
edit both, or they silently diverge.
|
|
37
|
+
|
|
38
|
+
### The pipeline
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
source notes
|
|
42
|
+
│
|
|
43
|
+
├─ /plan tools/planner.mjs ............. decompose + assert coverage [code]
|
|
44
|
+
├─ /enrich agent writes enrichment.json
|
|
45
|
+
│ tools/apply-enrichment.mjs .... merge + validate [code]
|
|
46
|
+
├─ /script agent writes scripts/NN-*.json
|
|
47
|
+
│ tools/script-check.mjs ........ structure/speakability/budget [code]
|
|
48
|
+
├─ /audio tools/build-audio.mjs ......... Gemini TTS + MEASURED timeline [code, API]
|
|
49
|
+
├─ /scene agent writes scenes/*.js
|
|
50
|
+
│ tools/pacing-check.mjs ........ reveal rate + dead air [code]
|
|
51
|
+
├─ /publish agent writes publish.json
|
|
52
|
+
│ tools/publish.mjs ............. chapters + captions [code]
|
|
53
|
+
├─ /thumbnail agent writes thumbnail.json
|
|
54
|
+
│ tools/thumbnail.mjs ........... 1280x720 PNG + series label [code]
|
|
55
|
+
├─ /render tools/render.mjs .............. Chromium frames + ffmpeg [code]
|
|
56
|
+
└─ /assemble tools/assemble.mjs ............ per-segment render + concat [code]
|
|
57
|
+
tools/verify-audio.mjs ........ sample-level audio match [code]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Stages are named in prose (`/render`, `/assemble`) rather than numbered, so inserting a
|
|
61
|
+
stage does not silently rewrite history: publish's insertion at 6 moved render to 7 and
|
|
62
|
+
assemble to 8, and "stages 6–7" had meant render/assemble.
|
|
63
|
+
|
|
64
|
+
Per-video timing (stages 3–8) accrues in `out/<name>/timing.jsonl` via `tools/stamp.mjs`
|
|
65
|
+
(`begin`/`end` — the end is folded into each stage's *Done when*); `tools/timing-report.mjs`
|
|
66
|
+
renders `timing.csv` (long), `timing.md` (one heading per video), `timing.svg` (static), and
|
|
67
|
+
`timing.html` (interactive Plotly — the `vendor/plotly-basic.min.js` bundle and
|
|
68
|
+
`src/timing-chart.js` are inlined, so the file opens offline). The stage lives in the
|
|
69
|
+
`stage` *value*, not a column name, so stages can be added/renamed/merged without changing
|
|
70
|
+
the file's shape.
|
|
71
|
+
|
|
72
|
+
**Grouping came out of a spike, not a guess.** A single 85-row chart is unreadable, and the
|
|
73
|
+
plan already groups videos by module (first path segment of a video's first source), so the
|
|
74
|
+
report facets by module: one aggregate bar per module with a click to drill into its videos,
|
|
75
|
+
falling back to the per-video chart when the ledger spans a single module (`--flat` forces
|
|
76
|
+
it). Three layouts were prototyped — module filter list, small multiples, and
|
|
77
|
+
overview-plus-drill — and overview-plus-drill won: it is the only one that shows the whole
|
|
78
|
+
series without spending either a click per module or a wall of truncated labels. The spike
|
|
79
|
+
also surfaced that a fractional legend `y` collapses on short charts, which is why the
|
|
80
|
+
legend is placed from reserved pixels instead.
|
|
81
|
+
|
|
82
|
+
### Renderers
|
|
83
|
+
|
|
84
|
+
| File | Covers | Vocabulary |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `src/scene-svg.html` | the open lane — freeform SVG | author's own (`renderAt(t, svg, timeline)`) |
|
|
87
|
+
| `src/scene-core.js` | primitive library — `chart`, `hubSpoke`, `loopFlow`, `timeline`, `equation`, `sequence`, and the `flow` idiom | primitives take author params; `flow` keeps `client`, `server`, `hub`, `box` |
|
|
88
|
+
| `src/scene-code.js` | code walkthroughs, before/after refactors | code panels; for code excerpted from a repo, also real line numbers, elision lines, a focus window per sentence and a pause badge |
|
|
89
|
+
|
|
90
|
+
With no `work=` parameter, `src/scene-svg.html:34` resolves `?scene=<name>` to
|
|
91
|
+
`../scenes/<name>.js` — so the top-level `scenes/` is a live path for fixtures, not a
|
|
92
|
+
leftover directory. It holds the six `_pt-*` configs, one per primitive.
|
|
93
|
+
|
|
94
|
+
### Publish — chapters and captions (stage 6)
|
|
95
|
+
|
|
96
|
+
`tools/publish.mjs` exists and the stage runs; render and assemble do not depend on it. The
|
|
97
|
+
agent writes `out/<name>/<NN>/publish.json` — `chapters[]` (segment keys plus a `title`) and a
|
|
98
|
+
`blurb`. The tool resolves the timecodes from the measured windows, validates, and emits two
|
|
99
|
+
files, copied into the video's own folder under `--dest` — `<NN>-<slug>/` — alongside
|
|
100
|
+
the MP4. `assemble`'s `--dest` now lands the MP4 in the **same** folder (both derive the
|
|
101
|
+
name from the script's `videoIndex` + title via `tools/naming.mjs`), so the fixed
|
|
102
|
+
`description.md` / `captions.srt` filenames can never collide across a series and the
|
|
103
|
+
destination sorts into watch order:
|
|
104
|
+
|
|
105
|
+
| File | Contents | Delivered to |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `description.md` | the blurb, **then** the chapter block, paste-ready as **plain text** (no markdown — the field does not render it) | the video **description** box |
|
|
108
|
+
| `captions.srt` | the cue list, one cue per segment | the **caption track** upload |
|
|
109
|
+
|
|
110
|
+
Granularity is one chapter and one cue per segment, SRT only, and chapter titles are
|
|
111
|
+
**authored** rather than taken from the scene badges.
|
|
112
|
+
|
|
113
|
+
**The rationale is in [`PIPELINE.md`](./PIPELINE.md) §6** — why the stage sits before the
|
|
114
|
+
expensive ones, why authored titles beat reusing `stageNames`, and why chapters can only come
|
|
115
|
+
from the description. The calls it made are §3.8.
|
|
116
|
+
|
|
117
|
+
### Work-dir layout — one directory per video
|
|
118
|
+
|
|
119
|
+
Each video's work dir (`out/<name>/<NN>/`) holds its own `timeline.js`, `manifest.json`,
|
|
120
|
+
and `scenes/`, so nothing is global:
|
|
121
|
+
|
|
122
|
+
- `build-audio` writes `timeline.js` beside `timeline.json` in `--out` (was `src/`).
|
|
123
|
+
- `assemble` reads `manifest.json` from `--work` (was `scenes/manifest.json`).
|
|
124
|
+
- the scene hosts resolve `timeline.js` and the scene config from a `?work=` query param,
|
|
125
|
+
forwarded by `render.mjs` and `assemble.mjs`.
|
|
126
|
+
|
|
127
|
+
Two videos can now coexist and be re-rendered independently; only Chromium's `--port` is
|
|
128
|
+
shared, so renders still run one at a time. This closed the structural gap that the
|
|
129
|
+
pipeline assumes one video at a time.
|
|
130
|
+
|
|
131
|
+
The **destination** mirrors this: each finished video lands in
|
|
132
|
+
`<destination>/<NN>-<slug>/` (`.mp4` + `description.md` + `captions.srt`), one folder per
|
|
133
|
+
video — see `tools/naming.mjs`. The folder name is the plan's `videoIndex` plus a slug of
|
|
134
|
+
the title, so listing the destination sorts into consumption order.
|
|
135
|
+
|
|
136
|
+
### Beats anchor to narration lines, not to seconds
|
|
137
|
+
|
|
138
|
+
A scene used to declare `at: 6.6` — a hand-guessed second — which nothing measured.
|
|
139
|
+
On the module-07 pilot the "April" reveal fired at 6.6s while the words *In April*
|
|
140
|
+
begin at 12.6s: a six-second desync that `pacing-check` passed, because that check can
|
|
141
|
+
only compare beats against the **segment**, never against the narration inside it.
|
|
142
|
+
|
|
143
|
+
Two new stages close it:
|
|
144
|
+
|
|
145
|
+
- **`tools/align.mjs`** (stage 4b) transcribes the whole narration track with
|
|
146
|
+
`gemini-3.5-transcribe` (`wordTimestamp: true`), cuts the words into segments at the
|
|
147
|
+
measured boundaries, aligns each segment's words back onto that segment's script
|
|
148
|
+
words, and writes every narration **line's** span to `lines.json`.
|
|
149
|
+
Alignment is mandatory because the transcript is not an echo: it rewrote *forty* as
|
|
150
|
+
`40`, *fail* as `failed`, inserted commas and merged two sentences. Our punctuation
|
|
151
|
+
fixes the boundaries; the aligned words supply the times. Offsets are clamped
|
|
152
|
+
monotonic — the API occasionally returns an outlier (one plan put `A delta` at 3.7s
|
|
153
|
+
between neighbours at 12.4s and 14.2s).
|
|
154
|
+
- **`tools/resolve-beats.mjs`** resolves `{ id, line }` (or `{ lines: [from,to] }`) to
|
|
155
|
+
seconds, writes `beats.json`, and **fails** on a raw `at` with no `line`, a
|
|
156
|
+
nonexistent line, out-of-order beats, a baked `at` that disagrees with the measured
|
|
157
|
+
onset by >60 ms, or **a narration line with no beat**.
|
|
158
|
+
|
|
159
|
+
`pacing-check` now resolves from `beats.json` and flags any remaining hand-written
|
|
160
|
+
`at`. It also had a latent unit bug: `winEnd` subtracted the lead-in although `at` is
|
|
161
|
+
already window-relative, silently removing the last 0.6 s of every window. That only
|
|
162
|
+
looked harmless while beats were hand-placed nowhere near the end; line-anchored beats
|
|
163
|
+
run to the last spoken word and exposed it.
|
|
164
|
+
|
|
165
|
+
**The `/scene ↔ /audio` pad loop is demoted.** It existed because a hand-guessed beat
|
|
166
|
+
could not be trusted to fit its window, so padding was the routine repair. With dwells
|
|
167
|
+
measured from the lines a beat covers, `at + min` is inside the window by construction —
|
|
168
|
+
the module-07 pilot ran 123 beats across 30 scenes with zero shortfalls and zero pad.
|
|
169
|
+
A shortfall now means the scene *over-asked*: widen the `lines` range, redesign the
|
|
170
|
+
beat, and pad only as a last resort. `craft-video-audio` no longer presents padding as
|
|
171
|
+
the expected re-invocation.
|
|
172
|
+
|
|
173
|
+
Honest status: the three pilots' 87 scenes were migrated by a proposer (keyword +
|
|
174
|
+
monotonic DP over line text) with manual overrides on the beats it mis-read, dwells
|
|
175
|
+
recomputed from the measured line spans. Measured on the shipped pilot it moved
|
|
176
|
+
`april` from 6.60s to 13.20s. Per-scene line assignment still deserves a human pass —
|
|
177
|
+
the proposer gets roughly four in five right.
|
|
178
|
+
|
|
179
|
+
### The transcribe request budget, and why stage 4b is one request per video
|
|
180
|
+
|
|
181
|
+
`gemini-3.5-transcribe` carries its own hard cap, discovered the expensive way: the
|
|
182
|
+
first alignment implementation transcribed per segment, and a 13-minute video cost ~30
|
|
183
|
+
requests. The API's `QuotaFailure` payload names the limit exactly —
|
|
184
|
+
`GenerateRequestsPerDayPerProjectPerModel`, `quotaValue: "100"`, scoped to the model
|
|
185
|
+
and the project, with a `RetryInfo.retryDelay` around 18 h. The Batch API is not
|
|
186
|
+
supported for this model, so the cap cannot be queued around, and being per-project it
|
|
187
|
+
cannot be spread across keys.
|
|
188
|
+
|
|
189
|
+
Stage 4b therefore transcribes `narration.wav` **once** and cuts the words into
|
|
190
|
+
segments at the measured boundaries. The model allows 30 minutes per request when word
|
|
191
|
+
timestamps are enabled, so longer tracks are cut at segment boundaries into several
|
|
192
|
+
passes. `--per-segment` survives as a fallback and is chosen automatically for a small
|
|
193
|
+
number of missing segments.
|
|
194
|
+
|
|
195
|
+
Two consequences worth keeping in view: alignment is a **one-time** cost per video
|
|
196
|
+
(the `words.json` cache is keyed on WAV + script text, and should be committed), and
|
|
197
|
+
the per-video length cap now has a second justification — `--max-min 25` keeps a video
|
|
198
|
+
inside a single transcribe pass, where the old 14 had only watchability behind it.
|
|
199
|
+
|
|
200
|
+
### The beat model — the scene declares its own timing
|
|
201
|
+
|
|
202
|
+
A segment's visual is authored as a list of **beats**, each with a minimum dwell, declared
|
|
203
|
+
as `window.SCENE_BEATS` alongside the config. `pacing-check` resolves each beat against the
|
|
204
|
+
**measured** narration, reports shortfalls as prompts (exit 0), and errors only when a beat
|
|
205
|
+
cannot fit inside its segment at all. The dead-air check works in window-relative seconds
|
|
206
|
+
throughout, so it no longer mixes absolute event times with the relative window, and a
|
|
207
|
+
declared `hold` is no longer flagged as dead air.
|
|
208
|
+
|
|
209
|
+
The scene↔audio loop is wired: a shortfall is resolved by setting `pad` (hold silence) on
|
|
210
|
+
the script segment and `hold: true` on the beat, then re-running `/audio` — the TTS is
|
|
211
|
+
text-cached, so that is free and the window re-tiles wider.
|
|
212
|
+
|
|
213
|
+
**Two open questions about it** are recorded in §2.9.9.
|
|
214
|
+
|
|
215
|
+
### Proven output
|
|
216
|
+
|
|
217
|
+
Two runs went end-to-end. **Neither's work dir survives** — see §2.11.
|
|
218
|
+
|
|
219
|
+
**Video 6 (SOLID)** — 7 segments, 205.70s, Aoede narration, audio verified at residual
|
|
220
|
+
0.0155. Produced from real notes (`DEVELOPER-NOTES/.../low-level-design`) by the full
|
|
221
|
+
pipeline.
|
|
222
|
+
|
|
223
|
+
**A five-video series**, from `GenAI_Notes/Harness Engineering/modules/01-what-is-a-harness`:
|
|
224
|
+
83 segments, 83 scenes, 29.0 min finished, every video audio-verified (residuals
|
|
225
|
+
0.013–0.017). The first run to exercise the per-video work dirs at scale — five videos
|
|
226
|
+
coexisting, each re-rendered independently. Delivered to
|
|
227
|
+
`GenAI_Notes/Harness Engineering/modules/videos/`: the five MP4s plus video 02's
|
|
228
|
+
`description.md` and `captions.srt` are the surviving artifacts.
|
|
229
|
+
|
|
230
|
+
### Checks (4 of them)
|
|
231
|
+
|
|
232
|
+
| Check | Ask | Catches |
|
|
233
|
+
|---|---|---|
|
|
234
|
+
| coverage (in planner) | is every source heading assigned? | silently dropped content |
|
|
235
|
+
| `script-check` | is the narration speakable and on budget? | URLs, markdown, uncovered sections |
|
|
236
|
+
| `pacing-check` | can a human read this? is anything still happening? | unreadable reveals, dead air |
|
|
237
|
+
| `verify-audio` | does the finished audio match the narration? | wrong-slice muxing |
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 2. Remaining
|
|
242
|
+
|
|
243
|
+
**Numbering has gaps on purpose.** §2.2, §2.8 and §2.10 were resolved or built and now
|
|
244
|
+
live in §1; §2.4 (no dispatcher, no preflight) and §2.5 (Node ≥22 undeclared) were resolved
|
|
245
|
+
by making the tools fail loud and recording the agent-driven decision in §3. Numbers are
|
|
246
|
+
left vacant rather than reused — §2.9.8 is cited by number from `SKILL.md` and
|
|
247
|
+
`PIPELINE.md`, so renumbering §2 would break those. A gap means "done", not "deleted".
|
|
248
|
+
|
|
249
|
+
### 2.1 Portability — implemented, unverified off macOS
|
|
250
|
+
|
|
251
|
+
**Open — Windows Chromium has no command-free path yet.** The `win32` branch searches the
|
|
252
|
+
Playwright cache, so a Windows user must run `npx playwright install --only-shell` — a
|
|
253
|
+
command, against this skill's GUI-over-commands stance. *Candidate fix:* auto-detect the
|
|
254
|
+
preinstalled Edge/Chrome on `win32`
|
|
255
|
+
(`C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`,
|
|
256
|
+
`C:\Program Files\Google\Chrome\Application\chrome.exe`) and use it before falling back to
|
|
257
|
+
the cache. Undecided, not implemented.
|
|
258
|
+
|
|
259
|
+
**Remaining:** one real run each on Linux and Windows — that alone flips this to "resolved".
|
|
260
|
+
|
|
261
|
+
### 2.3 Packaging — restructured; publish + register remain
|
|
262
|
+
|
|
263
|
+
The repo is now an Agent Plugins plugin: root `plugin.json` + `.claude-plugin/plugin.json`
|
|
264
|
+
+ `package.json`, the main skill self-contained under
|
|
265
|
+
`skills/explainer-video-from-coursework/` (docs + `tools/` + `src/` + `scenes/` +
|
|
266
|
+
`SETUP.md`), with the 8 stage skills as siblings at `skills/craft-video-<stage>/`. Targets:
|
|
267
|
+
Claude Code, Codex, DSH via **npm**; Antigravity via **git** (Cursor parked —
|
|
268
|
+
marketplace-only, manual review).
|
|
269
|
+
|
|
270
|
+
**Done:** the restructure, the manifests, both marketplace catalogs
|
|
271
|
+
(`.claude-plugin/marketplace.json` + `.agents/plugins/marketplace.json`, `source: npm`),
|
|
272
|
+
the `source: npm` syntax pinned against primary docs, the 8 stage skills moved to the top
|
|
273
|
+
level of `skills/` so all nine resolve on every platform, **one** package serving all four
|
|
274
|
+
surfaces (`cordis.patch.yml` + `lib/index.js` at the root, so DSH reads the same package
|
|
275
|
+
Claude Code and Codex do — no second package, no duplication, and no DeepSeek dependency
|
|
276
|
+
declared), a root `README.md`, MIT licensing across the manifests, `DISTRIBUTION.md`, and a
|
|
277
|
+
tag-triggered staged release in `.github/workflows/release.yml` + `RELEASING.md`.
|
|
278
|
+
|
|
279
|
+
**Remaining (user-owned):** the one-time npm Trusted Publisher config, then the first tagged
|
|
280
|
+
release, and the four installs on real machines.
|
|
281
|
+
|
|
282
|
+
### 2.6 `planner.mjs` — remaining input gaps
|
|
283
|
+
|
|
284
|
+
Robustness against odd inputs is done: zero-content input errors instead of emitting
|
|
285
|
+
a 1-video/0-minute plan, bad paths (nonexistent, or a file instead of a directory)
|
|
286
|
+
error cleanly, and the `--index-targets` dedupe was **deleted rather than flagged**.
|
|
287
|
+
What remains:
|
|
288
|
+
|
|
289
|
+
- **Only `.md` is parsed.** `.mdx`, `.rst`, `.txt`, `.ipynb` are invisible — silent
|
|
290
|
+
omission, not an error. In one tree, 633 non-md files of which 72 matched the
|
|
291
|
+
code-extension list leave ~560 files unaccounted for.
|
|
292
|
+
- **The code-extension list is hardcoded** (`.java .py .ts .js .go .cpp .c .rs`) —
|
|
293
|
+
no `.kt .swift .rb .php .cs .scala .sh .sql`.
|
|
294
|
+
- **Coverage is structure-dependent.** The same file reported 0 orphans planned
|
|
295
|
+
alone and 2 orphans planned inside a larger tree. "0 orphans" is a property of
|
|
296
|
+
the run, not of the content.
|
|
297
|
+
|
|
298
|
+
### 2.7 `render.mjs` parts are silent by convention, not by flag
|
|
299
|
+
|
|
300
|
+
`assemble.mjs` never passes `--narration` to `render.mjs`, which defaults it to
|
|
301
|
+
`out/narration.wav`. Parts come out silent only because no such file exists — a
|
|
302
|
+
project keeps its track at `out/<name>/narration.wav` instead. The silence is
|
|
303
|
+
intentional ("muxing narration onto each one would restart the whole track at every
|
|
304
|
+
boundary"), but it is inherited from a directory convention rather than stated:
|
|
305
|
+
any run that ever leaves a track at `out/narration.wav` re-muxes the full narration
|
|
306
|
+
onto every part and silently reintroduces the double-mux bug.
|
|
307
|
+
|
|
308
|
+
Needs: `assemble.mjs` to pass an explicit `--silent` (or `--narration ''`).
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### 2.9 Findings from the `what-is-a-harness` run
|
|
313
|
+
|
|
314
|
+
Recorded while producing five videos end-to-end (83 narrated segments, 83 authored
|
|
315
|
+
scenes, 29 min of finished video) from
|
|
316
|
+
`GenAI_Notes/Harness Engineering/modules/01-what-is-a-harness`.
|
|
317
|
+
|
|
318
|
+
**Every item below was observed in that run, not theorised.** Priorities are tagged per
|
|
319
|
+
item — all P1 except 2.9.9; this file does not track the P0 queue. Two are marked
|
|
320
|
+
*(applied in-run)* — those exist only in the working tree that produced these videos:
|
|
321
|
+
`explainer-video/` is **not a git repository**, so there is no baseline to diff them
|
|
322
|
+
against and they are not preserved anywhere else.
|
|
323
|
+
|
|
324
|
+
#### 2.9.1 `draw` silently accepts the wrong shape *(P1)*
|
|
325
|
+
|
|
326
|
+
`reveal` takes a bare two-element array — `reveal: { node: ['seg@2.0', 'seg@3.0'] }` —
|
|
327
|
+
but `draw` takes a wrapper — `draw: { edge: { at: ['seg@2.0', 'seg@3.0'] } }`.
|
|
328
|
+
Passing the bare array to `draw` does not error:
|
|
329
|
+
|
|
330
|
+
- `scene-core.js:563` reads `drawSpec.at[0]`;
|
|
331
|
+
- `drawSpec.at` therefore resolves to `Array.prototype.at` — a *function* — so `[0]`
|
|
332
|
+
is `undefined`, `fadeIn(t, undefined, undefined)` returns `NaN`, and
|
|
333
|
+
`setAttribute('opacity', 'NaN')` is rejected by the DOM, leaving the attribute at
|
|
334
|
+
its default `1`.
|
|
335
|
+
|
|
336
|
+
The edge consequently renders **fully visible from frame 0**: a connector floating
|
|
337
|
+
before the nodes it joins exist. Both `script-check` and `pacing-check` stay green,
|
|
338
|
+
because neither looks at geometry — it was caught only by sampling the assembled MP4.
|
|
339
|
+
|
|
340
|
+
Observed in `v1-demo`. **Fix:** normalise both shapes at load, or validate `draw`
|
|
341
|
+
keys. *Small.*
|
|
342
|
+
|
|
343
|
+
#### 2.9.2 `size: { w }` silently disables a box's auto-height *(P1)*
|
|
344
|
+
|
|
345
|
+
`scene-core.js:158` is `if (kind === 'box' && !nd.size)`. Supplying *any* `size` —
|
|
346
|
+
even only `w` — skips the height computation entirely.
|
|
347
|
+
|
|
348
|
+
Widening a box to stop its member text overflowing therefore silently clips the last
|
|
349
|
+
member line against the bottom border. Observed in `v1-blur`: three members in a
|
|
350
|
+
`size: { w: 640 }` box rendered at the default `h: 132` instead of the required 160.
|
|
351
|
+
|
|
352
|
+
**Fix:** keep auto-height when `members` exist and `size.h` was not given explicitly.
|
|
353
|
+
*Small.*
|
|
354
|
+
|
|
355
|
+
#### 2.9.3 Flows that terminate on a wide node draw a stray ring *(P1)*
|
|
356
|
+
|
|
357
|
+
`scene-core.js:292` gives every flow target an arrival ring of fixed `r: 58`,
|
|
358
|
+
independent of node size. On a 470px `box` (or a 252px `hub`) that ring sits wholly
|
|
359
|
+
inside the node, so it reads as a random circle rather than an arrival highlight.
|
|
360
|
+
|
|
361
|
+
The skill already knows this, but only as example-scene lore:
|
|
362
|
+
`out/hld-full/scenes/lb-l4-vs-l7.js` carries the comment *"route THROUGH the boxes,
|
|
363
|
+
never INTO them"*. Nothing enforces it.
|
|
364
|
+
|
|
365
|
+
Observed in 4 scenes: `v1-demo`, `v1-loop`, `v3-agenttool`, `v3-ex-agenttool`.
|
|
366
|
+
|
|
367
|
+
**Fix:** warn at load when a target's half-width or half-height exceeds the ring
|
|
368
|
+
radius, or scale the ring to the node bounds. *Small.*
|
|
369
|
+
|
|
370
|
+
#### 2.9.4 Nothing asserts that a scene *opens* with something on screen *(P1)*
|
|
371
|
+
|
|
372
|
+
`pacing-check`'s dead-air rule subtracts the narration windows before measuring
|
|
373
|
+
silence, so "blank stage with a voice over it" is deliberately never flagged. A
|
|
374
|
+
scene can be entirely pacing-clean and still show an empty frame for seconds after a
|
|
375
|
+
hard cut.
|
|
376
|
+
|
|
377
|
+
Observed: **16 of 83** scenes had a first visual onset later than 2.5s; the worst —
|
|
378
|
+
`v1-support`, `v1-coding`, `v1-research` — sat empty for 5–6s.
|
|
379
|
+
|
|
380
|
+
`tools/onset-audit.mjs` was written during the run for exactly this: it computes each
|
|
381
|
+
scene's earliest visual event in seconds, and reported 83/83 clean once the fixes
|
|
382
|
+
landed. **Fix:** fold it into `pacing-check` as a third assertion. *Small — the
|
|
383
|
+
script already exists.*
|
|
384
|
+
|
|
385
|
+
#### 2.9.5 Nothing checks that text fits its container *(P1)*
|
|
386
|
+
|
|
387
|
+
Every checker is timing- and structure-only; overflow, clipping and overlap are
|
|
388
|
+
invisible to all four of them.
|
|
389
|
+
|
|
390
|
+
The clearest case was the stage badge in `scene-core.js`: a hardcoded 292px pill that
|
|
391
|
+
never measured its label, with the text merely centred inside it. Measured off a real
|
|
392
|
+
frame:
|
|
393
|
+
|
|
394
|
+
| Label | Text | Pill | Result |
|
|
395
|
+
|---|---|---|---|
|
|
396
|
+
| `CAPABILITY VS SYSTEM` | 273px | 292px | fits (10px margin) |
|
|
397
|
+
| `NOT AN AFTERTHOUGHT` | 284px | 292px | fits by ~4px — reads as touching |
|
|
398
|
+
| `EXAMPLE · CHAT TRANSFER` | 323px | 293px | overflows 15px per side |
|
|
399
|
+
| `EXAMPLE · AGENT AS A TOOL` | 341px | 291px | overflows 24 / 26px |
|
|
400
|
+
|
|
401
|
+
*(Applied in-run: the pill now measures with `textW()` and re-centres, right edge
|
|
402
|
+
pinned at `W − 74`, wrapped in `Math.max(292, …)` so short labels are unchanged.)*
|
|
403
|
+
|
|
404
|
+
Still unchecked and carrying the same exposure: box `members` (21px mono — roughly
|
|
405
|
+
33 characters is the limit in a 470px box), box labels, and `cards` labels.
|
|
406
|
+
|
|
407
|
+
**Fix:** a headless pass asserting measured text width against container width.
|
|
408
|
+
**Largest item in this section.**
|
|
409
|
+
|
|
410
|
+
#### 2.9.6 `progress` defaults to on *(P1)*
|
|
411
|
+
|
|
412
|
+
`scene-core.js:324` is `if (OV.progress !== false)`. The overlay draws a 6px track
|
|
413
|
+
plus an amber fill that tracks playback. A scrubber is a *player* affordance, but it
|
|
414
|
+
is drawn into the frames, so it ends up burned into the exported MP4 — where the
|
|
415
|
+
player draws its own controls. All 83 scenes had to opt out explicitly.
|
|
416
|
+
|
|
417
|
+
**Fix:** default it off, or drop the overlay. *One line.*
|
|
418
|
+
|
|
419
|
+
#### 2.9.7 Re-rendering is all-or-nothing *(P1)*
|
|
420
|
+
|
|
421
|
+
`assemble.mjs:89` filters the segment order down to `--only` and then concatenates
|
|
422
|
+
only those parts — so `--only` emits a *one-segment* video and cannot patch an
|
|
423
|
+
existing cut. `assemble.mjs:178` then deletes every part on success.
|
|
424
|
+
|
|
425
|
+
Fixing 4 segments of video 1 therefore cost a full re-render (23 segments, ~19 min),
|
|
426
|
+
because the other 19 parts no longer existed. A reuse pass keyed on `part` mtime vs
|
|
427
|
+
`scene` + `timeline` mtime would have taken about two minutes.
|
|
428
|
+
|
|
429
|
+
**Fix:** add `--reuse` (skip segments whose part is newer than their scene and the
|
|
430
|
+
timeline), and/or keep parts by default. *Medium.*
|
|
431
|
+
|
|
432
|
+
#### 2.9.8 `SKILL.md`'s work-dir convention contradicts its own "Series scope" — **RESOLVED**
|
|
433
|
+
|
|
434
|
+
- **The contradiction:** "Series scope" says each video gets its own
|
|
435
|
+
`out/<name>/<NN>/`, but every stage command in `SKILL.md` showed `--out out/<name>`
|
|
436
|
+
/ `--work out/<name>`. §1 *Work-dir layout* records the per-video layout as resolved,
|
|
437
|
+
yet the commands were never updated to match, so the agent had to reconcile the two
|
|
438
|
+
itself.
|
|
439
|
+
- **The stale sub-claim (do not re-add):** this entry used to say `--work` must be a
|
|
440
|
+
path *relative to `src/`*, citing the hosts resolving `'../' + WORK + '/timeline.js'`.
|
|
441
|
+
That was true of an older host; `render.mjs:81` now converts `--work` with
|
|
442
|
+
`pathToFileURL(resolve(WORK))`, so a cwd-relative **or absolute** path both work.
|
|
443
|
+
- **Resolved (2026-10-20).** Stage 4/5/8 commands in `SKILL.md` and `PIPELINE.md` now
|
|
444
|
+
show `out/<name>/<NN>`, the stale parenthetical in *Series scope* is gone, and the
|
|
445
|
+
`--work` rule is documented there as cwd-relative-or-absolute.
|
|
446
|
+
- **Resolved differently, and better, than the filed fix.** The filed fix was
|
|
447
|
+
documentation only — but documentation was not the failure mode: an agent that reads
|
|
448
|
+
the right path from `build-audio.mjs`'s own usage string still collides on a careless
|
|
449
|
+
re-run, because the harm is structural. `narration.wav`, `timeline.json`, `timeline.js`,
|
|
450
|
+
`lines.json`, `manifest.json` and the MP4 are named identically for every video, so a
|
|
451
|
+
second video pointed at one dir silently overwrites the first's timings and every later
|
|
452
|
+
stage reads the wrong video. New `tools/workdir.mjs` stamps a `.video-identity` marker
|
|
453
|
+
into the work dir and `build-audio`, `align` and `assemble` **refuse** a `videoIndex`
|
|
454
|
+
mismatch (exit 3) naming both videos. Permissive by construction: any layout is allowed
|
|
455
|
+
so long as one dir serves one video, and a same-index retitle (re-enrichment) updates
|
|
456
|
+
the marker instead of failing.
|
|
457
|
+
- **Related, fixed at the same time:** `build-audio --only` wrote the global
|
|
458
|
+
`narration.wav` / `timeline.json` / `timeline.js` from a single segment, replacing a
|
|
459
|
+
finished video's timeline with a one-scene one. A partial run now stops with a
|
|
460
|
+
segment-scoped `timeline.<key>.json` and touches no global artefact.
|
|
461
|
+
|
|
462
|
+
#### 2.9.9 The beat model: two things worth resolving *(P2)*
|
|
463
|
+
|
|
464
|
+
- **Nothing but the checker reads `SCENE_BEATS`.** All three scene hosts
|
|
465
|
+
(`scene-diagram.html`, `scene-svg.html`, `scene-code.html`) and `scene-core.js` reference
|
|
466
|
+
it **zero** times, so beats are a *lint* input and not a rendering input — yet `SKILL.md`
|
|
467
|
+
says each aligned beat "resolves to `max(narration_beat, visual_beat_min)`". Holds are in
|
|
468
|
+
fact realised by padding the audio, so either that sentence overstates the mechanism or a
|
|
469
|
+
renderer-side piece is missing.
|
|
470
|
+
- **It fails silently when undeclared.** `tools/pacing-check.mjs:174` yields `null`, and
|
|
471
|
+
both the beat branch (`:175`) and its fallback (`:210`) require `BEATS && BEATS.length` —
|
|
472
|
+
so a scene with no beats prints no beat line at all, rather than "none declared". You
|
|
473
|
+
cannot tell "no beats" from "beats declared and fine" without noticing the absent line.
|
|
474
|
+
|
|
475
|
+
Adoption on a real segment is also unverified: the six `_pt-*` fixtures declare
|
|
476
|
+
no beats, and the 83 harness configs no longer exist.
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
### 2.11 Repo metadata, before this is pushed anywhere *(P2)*
|
|
481
|
+
|
|
482
|
+
- No `README.md`. `SKILL.md` is written for an agent, not for a human arriving at the repo.
|
|
483
|
+
- No `LICENSE`.
|
|
484
|
+
- `package.json` is bare `{"type": "module"}` — no `name`, `description`, or `scripts`, so
|
|
485
|
+
`npm run` does nothing. There is no test suite; the five delivered videos are the golden
|
|
486
|
+
corpus.
|
|
487
|
+
- `.gitignore` carries two dead lines, `frames/` and `.explainer-video/` — neither path has
|
|
488
|
+
ever existed here. (`preview/` and `parts/` are absent right now too, but the tools
|
|
489
|
+
recreate them, so those entries stay.)
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
### 2.12 Shorts track — parked design *(P1)*
|
|
494
|
+
|
|
495
|
+
Shorts are YouTube's discovery surface; the long videos are the product, but the Shorts
|
|
496
|
+
are how anyone finds them. Parked here mid-design, with the open questions intact.
|
|
497
|
+
|
|
498
|
+
Settled so far:
|
|
499
|
+
|
|
500
|
+
- **Separate, optional track.** Presence of `shorts.json` is the switch — no flag. Absent,
|
|
501
|
+
stages 4–8 behave exactly as they do today; present, only the producing stage reads it,
|
|
502
|
+
and a stale one warns rather than blocking the main pipeline.
|
|
503
|
+
- **Authored right after the script (stage 3.5).** A Short is **re-narrated and
|
|
504
|
+
compressed**, not sliced out of the long audio. That matters: slicing would need cut
|
|
505
|
+
points that only exist after stage 4/4b, whereas a re-narrated Short gets fresh TTS and
|
|
506
|
+
therefore fresh timings. The one objection that does not apply here is the timing one.
|
|
507
|
+
- **Mining unit: the script's own segments.** Each is already a self-contained beat, so
|
|
508
|
+
mining per segment is an auditable partition rather than a vibe; 1–2 *adjacent* segments
|
|
509
|
+
are allowed when a single one is too thin. Candidates record disjointness, so three
|
|
510
|
+
Shorts cannot be mined out of the same 40 seconds.
|
|
511
|
+
- **Each candidate is scored on four axes:** hook potential (a counterintuitive claim, a
|
|
512
|
+
number, a common mistake, a before/after) · self-containment (what it references outside
|
|
513
|
+
itself) · vertical survivability (its lane — a wide `diagram` is the expensive case) ·
|
|
514
|
+
loop/payoff point (does it end somewhere that rewards a rewatch).
|
|
515
|
+
- **Compression targets 25–45 seconds**, not "under 2 minutes". 2:00 is the ceiling, not
|
|
516
|
+
the goal; retention falls off well before that.
|
|
517
|
+
- **Cost is real:** every Short is one TTS call + one alignment pass + one vertical render.
|
|
518
|
+
Mining ten per video is not free, which is an argument for choosing candidates well.
|
|
519
|
+
|
|
520
|
+
Blocked on one open question: `render.mjs` hardcodes a `1920×1080` viewport, and the scene
|
|
521
|
+
lanes are authored wide. Vertical needs the viewport parameterised **and** an answer to
|
|
522
|
+
crop-vs-re-author — cropping a wide diagram or code block to 9:16 either clips it or
|
|
523
|
+
shrinks it past phone legibility. That decision sizes the whole track.
|
|
524
|
+
|
|
525
|
+
Recorded disagreements (mine, not settled):
|
|
526
|
+
|
|
527
|
+
1. **Misleading hooks have a hard ceiling.** Retention past the hook is measured; a promise
|
|
528
|
+
the video does not keep is the one pattern that gets actively suppressed.
|
|
529
|
+
2. **The controllable lever is the number of independent attempts**, not the perfection of
|
|
530
|
+
one — which argues for breadth and cheap production over polish per Short.
|
|
531
|
+
3. **For technical coursework the realistic goal is niche reach plus funnel** to the long
|
|
532
|
+
form, not broad virality: 5k views from people who would watch a 12-minute explainer is
|
|
533
|
+
worth more than 500k from people who will not.
|
|
534
|
+
4. **Contiguous range vs ordered selection** is mostly moot while Shorts are re-narrated
|
|
535
|
+
(the compression pass may reorder freely), but if a Short ever reuses existing audio,
|
|
536
|
+
ordered selection becomes mandatory and audio splicing becomes the constraint.
|
|
537
|
+
|
|
538
|
+
## 3. Design decisions worth challenging
|
|
539
|
+
|
|
540
|
+
These are the calls I made. Some may be wrong.
|
|
541
|
+
|
|
542
|
+
### 3.0 Where scratch lives is an input, not an inference
|
|
543
|
+
|
|
544
|
+
`out/` resolved against whatever the cwd happened to be, described as "run from the
|
|
545
|
+
project" — a directory that was never defined, never checked, and not something the
|
|
546
|
+
skills could verify. One real run put 679 MB at `<repo>/out/…`, two levels above the
|
|
547
|
+
notes and outside the tree whose `.gitignore` already covered `out/`, simply because
|
|
548
|
+
that is where the shell was.
|
|
549
|
+
|
|
550
|
+
Scratch is now a Stage 0 input with a default — the directory that holds `out/`,
|
|
551
|
+
defaulting to the source's parent — and `planner` verifies the ignore rule instead of
|
|
552
|
+
assuming it: it walks up to the git work tree, asks `git check-ignore`, and appends
|
|
553
|
+
`/<rel>/` when the rule is missing, printing what it changed. Outside a repo it says
|
|
554
|
+
nothing, because there is nothing to ignore.
|
|
555
|
+
|
|
556
|
+
The same class of bug bit the other direction: a destination *inside* the source
|
|
557
|
+
(`<notesDir>/videos/`) is walked by `planner`, so a published `description.md` re-enters
|
|
558
|
+
as a note and becomes a phantom video titled after its folder. Hence `--exclude`
|
|
559
|
+
(repeatable) and a warning keyed on `description.md` — a filename only this pipeline
|
|
560
|
+
emits. Both are worth their small cost because the failure is silent and permanent
|
|
561
|
+
otherwise: you notice it when a video about nothing shows up in the series.
|
|
562
|
+
|
|
563
|
+
*Challenge if:* you would rather the tools **refuse** a scratch root inside a git work
|
|
564
|
+
tree that does not ignore it, than edit the user's `.gitignore` for them.
|
|
565
|
+
|
|
566
|
+
### 3.1 Code vs agent boundary
|
|
567
|
+
|
|
568
|
+
Code does anything that must be exact (parsing, packing, coverage, timing math,
|
|
569
|
+
rendering, verification). The agent does judgment (titles, forms, scripts, scene
|
|
570
|
+
authoring). No LLM API is used for reasoning — only for TTS.
|
|
571
|
+
|
|
572
|
+
*Challenge if:* you think some judgment step is too expensive/variable to leave to
|
|
573
|
+
an agent, or that a deterministic step should be model-assisted.
|
|
574
|
+
|
|
575
|
+
### 3.2 One config file per segment, not a single DSL
|
|
576
|
+
|
|
577
|
+
Each segment gets its own `scenes/*.js`. There is no cross-segment timeline in
|
|
578
|
+
config — `assemble` stitches windows.
|
|
579
|
+
|
|
580
|
+
*Challenge if:* you'd rather have one project-level config describing the whole
|
|
581
|
+
video, with segments as sections.
|
|
582
|
+
|
|
583
|
+
### 3.3 Per-segment render + concat (hard cuts)
|
|
584
|
+
|
|
585
|
+
Chosen so segments stay independently reviewable and re-renderable.
|
|
586
|
+
|
|
587
|
+
*Tradeoff:* hard cuts, no cross-fades. Deferred by request.
|
|
588
|
+
|
|
589
|
+
### 3.4 Timing is relative to narration, not absolute
|
|
590
|
+
|
|
591
|
+
`"srp@4.6"` resolves to `segment_start + 4.6`. This fixed a real desync where
|
|
592
|
+
hardcoded absolute times drifted when the voice changed.
|
|
593
|
+
|
|
594
|
+
*Challenge if:* scene authoring becomes harder to reason about than absolute
|
|
595
|
+
times.
|
|
596
|
+
|
|
597
|
+
### 3.5 Four separate checkers rather than one
|
|
598
|
+
|
|
599
|
+
Each is small, single-purpose, and independently runnable.
|
|
600
|
+
|
|
601
|
+
*Challenge if:* this fragments the QA story instead of giving one "is this video
|
|
602
|
+
good?" gate.
|
|
603
|
+
|
|
604
|
+
### 3.6 Text-keyed audio cache
|
|
605
|
+
|
|
606
|
+
Re-runs reuse a segment's WAV only if the spoken text is unchanged, so iterating on
|
|
607
|
+
scene timing never re-spends TTS quota.
|
|
608
|
+
|
|
609
|
+
*Challenge if:* the script changes for non-text reasons (voice, style, model) and
|
|
610
|
+
the cache silently serves stale audio — **this is a real hole**: `style`, `voice`
|
|
611
|
+
and `model` are NOT part of the cache key.
|
|
612
|
+
|
|
613
|
+
### 3.7 The planner's duration model — recalibrated
|
|
614
|
+
|
|
615
|
+
The `×1.35` visual-overhead factor was a guess and ran **37% over** (video 6
|
|
616
|
+
predicted 4.7 min, actual 3.43). Now measured and centralized in
|
|
617
|
+
`tools/model.mjs`: `wpm 153` (508 words → 199s of audio) and `expansion 1.10`
|
|
618
|
+
(205.7s picture / 199s narration = 1.03, plus a buffer). `planner`,
|
|
619
|
+
`script-check` and `pacing-check` all import it, so they can't drift. Re-verify
|
|
620
|
+
against a code-heavy video, where `codeNarration 0.15` may still under-count.
|
|
621
|
+
|
|
622
|
+
### 3.8 Chapter granularity is bounded by segment granularity
|
|
623
|
+
|
|
624
|
+
A chapter's time is its first segment's window start, so a chapter can **group** segments
|
|
625
|
+
but never **split** one. A boundary can only land where the narration already breaks.
|
|
626
|
+
|
|
627
|
+
*Challenge if:* a segment carries two ideas and deserves two chapters. Grouping is a
|
|
628
|
+
format-compatible later change; splitting needs a second timestamp source and is not.
|
|
629
|
+
|
|
630
|
+
### 3.9 No dispatcher — the agent is the orchestrator
|
|
631
|
+
|
|
632
|
+
There is no `pipeline.mjs` or `bin` entry point. `SKILL.md` sequences the stages and the
|
|
633
|
+
agent composes the `tools/*.mjs` calls, because stages 1–2 are once-per-series, 3–8 are
|
|
634
|
+
once-per-video, and judgment steps (authored JSON) interleave with code. Each stage is also
|
|
635
|
+
its own skill.
|
|
636
|
+
|
|
637
|
+
*Challenge if:* you want a non-agent entry point — a CLI or CI job that runs the whole
|
|
638
|
+
series unattended — which would need a driver that encodes the series/per-video loop and
|
|
639
|
+
the judgment steps.
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
## Open questions
|
|
644
|
+
|
|
645
|
+
Unresolved, captured for later. Do not act on these yet.
|
|
646
|
+
|
|
647
|
+
### Q1 · Artefacts attribution in `planner.mjs`
|
|
648
|
+
|
|
649
|
+
The rule at `planner.mjs:340–343` attaches a code file to a video by **path
|
|
650
|
+
prefix** of the video's source directory. It is:
|
|
651
|
+
|
|
652
|
+
- **depth-blind** — a phase-hub README (e.g. `distributed-systems-theory/README.md`)
|
|
653
|
+
inherits every demo in every sub-chapter beneath it (10 files for one video).
|
|
654
|
+
- **content-blind** — when one README is split across N videos, all N siblings get
|
|
655
|
+
the identical list, whether or not their own text mentions any demo.
|
|
656
|
+
|
|
657
|
+
It never *misses* (every referenced demo is present) — it only over-includes.
|
|
658
|
+
Counted on `hld-full`: 107 planned entries vs 60 referenced by the carrying video's
|
|
659
|
+
own text; 47 inherited, 15 surviving after curation.
|
|
660
|
+
|
|
661
|
+
Open because:
|
|
662
|
+
|
|
663
|
+
- **The field's meaning is undecided.** Three rules give different answers for the
|
|
664
|
+
same chapter (`database-types`: 9 vs 3 vs 6 entries):
|
|
665
|
+
1. *prefix* — current.
|
|
666
|
+
2. *reference-evidenced* — a demo belongs to a video iff that video's own section
|
|
667
|
+
text mentions it (supported by the source's own structure; concept videos then
|
|
668
|
+
carry zero code artefacts).
|
|
669
|
+
3. *topic-attributed* — map each demo to the video covering its topic (needs a
|
|
670
|
+
hand-built demo→topic ontology the source does not contain).
|
|
671
|
+
- **Nothing consumes `artefacts`.** The planner writes it and `plan.md` prints its
|
|
672
|
+
count; no renderer, checker, or scene reads it. Informational only today.
|
|
673
|
+
|
|
674
|
+
### Q2 · `apply-enrichment.mjs` merge drops artefacts
|
|
675
|
+
|
|
676
|
+
The `mergeInto` branch unions `sections`, `sectionSpans` and `words` — but **not**
|
|
677
|
+
`artefacts`. Merging a video silently drops the folded-in video's code files from
|
|
678
|
+
the target's list. Never fired on `hld-full` (curation used `skip`, not
|
|
679
|
+
`mergeInto`), but it is a latent drop.
|
|
680
|
+
|
|
681
|
+
Related: the agreed "let `/enrich` curate artefacts" escape hatch is **not
|
|
682
|
+
implemented** — `enrichment.json` has no `artefacts` field, `apply-enrichment`
|
|
683
|
+
passes the list through untouched, and there is no subset validation.
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
## 4. Acceptance test
|
|
688
|
+
|
|
689
|
+
Hand a fresh agent only the skill — no conversation history — point it at a notes
|
|
690
|
+
directory, and see whether it produces a video without asking questions.
|
|
691
|
+
|
|
692
|
+
Today the likely failure is no longer scene authoring — that is documented — but the
|
|
693
|
+
platform-specific bits: Chromium discovery and the key's OS secret store, both implemented
|
|
694
|
+
but only verified on macOS (§2.1).
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
## 5. Explicitly out of scope for now
|
|
699
|
+
|
|
700
|
+
- Scripts and videos 1–5 and 7
|
|
701
|
+
- Cross-fades between segments
|
|
702
|
+
- Per-member highlighting in the UML box kind
|
|
703
|
+
- Productising the load-balancer project (now legacy duplication)
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
## 6. Planned skill family
|
|
708
|
+
|
|
709
|
+
This skill is `explainer-video-from-coursework` — it assumes structured coursework
|
|
710
|
+
notes (markdown READMEs, a heading per topic, `code/` demos) and is **not known to
|
|
711
|
+
generalise** beyond that. Three siblings are planned, split by where the source
|
|
712
|
+
material comes from:
|
|
713
|
+
|
|
714
|
+
| Skill | Source | What it adds over this one |
|
|
715
|
+
|---|---|---|
|
|
716
|
+
| `explainer-video-from-coursework` *(this)* | structured coursework notes | decompose + prove coverage + narrate + animate |
|
|
717
|
+
| `explainer-video-from-source` | a general source — docs, papers, codebases, arbitrary notes | generalise the planner's assumptions (README layout, heading structure, `code/` convention) so non-coursework sources decompose cleanly |
|
|
718
|
+
| `explainer-video` | no source — a narrow-to-broad topic description | author the study material first (a pre-stage that builds the notes), then feed it through the same plan→video pipeline |
|
|
719
|
+
| `explainer-video-from-codebase` | a codebase's major code flows (Python, TS/JS, Go, C, C++, or a mix) | mine the code (tree-sitter, grouped prod/test/eval/dev, tests as evidence), propose candidate flows, the agent proposes the series with reasons, the user approves the flow list, then a coursework-shaped plan reuses stages 3–8 |
|
|
720
|
+
|
|
721
|
+
`explainer-video-from-codebase` exists: its `SKILL.md` is the contract of record for the
|
|
722
|
+
whole code-flow pipeline, and four stage skills ship beside it, `craft-codeflow-mine`
|
|
723
|
+
(stages 1 and 1t), `craft-codeflow-flows` (1b and 1b′), `craft-codeflow-plan` (1c) and
|
|
724
|
+
`craft-codeflow-scene` (5). They are held to the dual-file rule by
|
|
725
|
+
`scripts/check-skill-sync.mjs` in CI, and stages 2–4b and 6–8 are this skill's own stage
|
|
726
|
+
skills. The plugin that ships both entry skills is now named `explainer-video`; the skill names are unchanged. The other two are not started. Recorded here so the naming is reserved and
|
|
727
|
+
the "from coursework" scope of this skill stays explicit.
|