@lalalic/markcut 2.2.8 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/package.json +2 -1
  2. package/skills/markcut/SKILL.md +11 -9
  3. package/skills/markcut/docs/markdown-descriptive.md +1 -1
  4. package/src/config.mjs +60 -63
  5. package/src/descriptive/resolve.ts +3 -2
  6. package/src/player/pipeline.mjs +64 -21
  7. package/src/render/cli.mjs +42 -69
  8. package/src/vision/cli.mjs +21 -45
  9. package/skills/markcut/docs/edit-mode.md +0 -220
  10. package/skills/markcut/docs/label-mode.md +0 -112
  11. package/skills/markcut/docs/template.md +0 -212
  12. package/skills/markcut/docs/templates/courseware/TEMPLATE.md +0 -526
  13. package/skills/markcut/docs/templates/courseware/agents/reviewer.md +0 -84
  14. package/skills/markcut/docs/templates/courseware/prompts/fix.md +0 -30
  15. package/skills/markcut/docs/templates/courseware/prompts/outline.md +0 -58
  16. package/skills/markcut/docs/templates/courseware/prompts/scene.md +0 -70
  17. package/skills/markcut/docs/templates/deep-dive/TEMPLATE.md +0 -354
  18. package/skills/markcut/docs/templates/deep-dive/agents/researcher.md +0 -69
  19. package/skills/markcut/docs/templates/deep-dive/agents/reviewer.md +0 -108
  20. package/skills/markcut/docs/templates/deep-dive/prompts/fix.md +0 -28
  21. package/skills/markcut/docs/templates/deep-dive/prompts/outline.md +0 -102
  22. package/skills/markcut/docs/templates/deep-dive/prompts/script.md +0 -64
  23. package/skills/markcut/docs/templates/illustrated-book/TEMPLATE.md +0 -356
  24. package/skills/markcut/docs/templates/illustrated-book/agents/reviewer.md +0 -115
  25. package/skills/markcut/docs/templates/illustrated-book/prompts/fix.md +0 -29
  26. package/skills/markcut/docs/templates/illustrated-book/prompts/illustration.md +0 -35
  27. package/skills/markcut/docs/templates/illustrated-book/prompts/story.md +0 -69
  28. package/skills/markcut/docs/templates/short-film/TEMPLATE.md +0 -385
  29. package/skills/markcut/docs/templates/short-film/agents/reviewer.md +0 -115
  30. package/skills/markcut/docs/templates/short-film/prompts/fix.md +0 -29
  31. package/skills/markcut/docs/templates/short-film/prompts/screenplay.md +0 -101
  32. package/skills/markcut/docs/templates/short-film/prompts/storyboard.md +0 -54
  33. package/skills/markcut/docs/templates/short-video/TEMPLATE.md +0 -301
  34. package/skills/markcut/docs/templates/short-video/agents/reviewer.md +0 -94
  35. package/skills/markcut/docs/templates/short-video/prompts/fix.md +0 -27
  36. package/skills/markcut/docs/templates/short-video/prompts/script.md +0 -65
  37. package/skills/markcut/docs/templates/vlog/TEMPLATE.md +0 -326
  38. package/skills/markcut/docs/templates/vlog/agents/reviewer.md +0 -89
  39. package/skills/markcut/docs/templates/vlog/agents/story-writer.md +0 -100
  40. package/skills/markcut/docs/templates/vlog/prompts/bgm-select.md +0 -38
  41. package/skills/markcut/docs/templates/vlog/prompts/group-clips.md +0 -93
  42. package/skills/markcut/docs/templates/vlog/prompts/local-context.md +0 -59
  43. package/skills/markcut/docs/templates/vlog/prompts/outline.md +0 -56
  44. package/skills/markcut/docs/templates/vlog/prompts/review-fix.md +0 -27
  45. package/skills/markcut/docs/templates/vlog/prompts/storyboard.md +0 -41
  46. package/src/render/cli.test.ts +0 -13
@@ -1,526 +0,0 @@
1
- ---
2
- name: courseware
3
- description: Turn a topic or teaching material into a professional slide-based course video with TTS narration, bullet-reveal slides, and a reviewer quality gate.
4
- when-to-use: lessons, lectures, training, tutorials, explainers — 1-5 minute educational videos built from slides + narration
5
- test:
6
- ---
7
-
8
- # Courseware Template
9
-
10
- Follow this file top to bottom. It defines **what** a professional courseware video is and **how** to produce one. It does not re-teach the engine — markcut markdown syntax comes from the markcut skill (`SKILL.md` → `docs/markdown-descriptive.md`). Read those first if you have not.
11
-
12
- Layout of this template package:
13
-
14
- | Path | Runs in | Purpose |
15
- |---|---|---|
16
- | `TEMPLATE.md` | your context | everything: inputs, structure, rules, workflow, quality gate |
17
- | `prompts/*.md` | **your own (orchestrator) context** | fill-in prompt templates you execute yourself |
18
- | `agents/*.md` | **separate agent session** | subagent definitions (system prompt + task template) |
19
-
20
- ## 0. Prerequisites
21
-
22
- - markcut skill loaded (engine contract: `SKILL.md` + `docs/markdown-descriptive.md`)
23
- - `npx @lalalic/markcut` runnable
24
- - For the reviewer gate: `ffmpeg`/`ffprobe`, an STT CLI (e.g. whisper), and image-understanding capability
25
-
26
- ## 1. Inputs — collect before starting
27
-
28
- | Input | Required | Default | Notes |
29
- |---|---|---|---|
30
- | Topic / source material | **yes** | — | plain topic, outline, doc, or article |
31
- | Audience level | no | beginner | beginner / intermediate / expert |
32
- | Language(s) | no | en | extra languages become variants (see §3) |
33
- | Target duration | no | 2 min | 1–5 min |
34
- | Accent color | no | `#61dafb` | theme knob, see §4 |
35
-
36
- **Rule:** if the topic/material is missing or ambiguous, ask the user. Never invent course content beyond the given material plus well-established knowledge of the topic. Never invent facts, numbers, or citations.
37
-
38
- ## 2. Scene grammar — mandatory structure
39
-
40
- ```
41
- # video ← root: width:1920 height:1080 fps:30 layout:series
42
- │ subtitle:{fontSize:"20px"}
43
- ├── ## Hook ← 3–5s attention grabber, fixed duration
44
- ├── ## Slides ← layout:transitionSeries transition:fade(0.5)
45
- │ ├── ### TitleSlide ← transitionSeries, bullet-reveal (1 beat)
46
- │ ├── ### <Concept> × 3–6 ← transitionSeries, bullet-reveal (1 beat per bullet)
47
- │ └── ### Summary ← transitionSeries, bullet-reveal (1 beat per takeaway)
48
- └── ## Thanks ← fixed duration:6, no script
49
- ```
50
-
51
- Scene rules:
52
-
53
- - **Hook**: `- image duration:3 prompt:"<vivid visual, cinematic>"` (or `video`). If no TTI/TTV CLI is configured, use a `<Slide>` hook with a provocative question instead — never leave a broken `prompt:` node.
54
- - **Every scene inside Slides (TitleSlide, concepts, Summary)** uses the **bullet-reveal pattern** — this is mandatory, not optional:
55
-
56
- ```
57
- ### <SceneName>
58
- layout:transitionSeries transition:fade(0.5)
59
- - component id:<shortId> isBackground:true jsx:"<Slide current={current}>{source}</Slide>"
60
- ~~~md source
61
- ## Title
62
- - 🎯 **Key point 1** — elaboration
63
- - 🎯 **Key point 2** — elaboration
64
- - 🎯 **Key point 3** — elaboration
65
- ~~~
66
- - script "Paragraph expanding bullet 1..." on:(start, <shortId>.current=1)
67
- - script "Paragraph expanding bullet 2..." on:(start, <shortId>.current=2)
68
- - script "Paragraph expanding bullet 3..." on:(start, <shortId>.current=3)
69
- ```
70
-
71
- - `id` must be unique per scene (e.g. `t`, `c1`, `c2`, `s`). The `id` connects the component to the script events.
72
- - `current={current}` on the Slide component reads the event-driven bullet index and highlights the matching `<li>`.
73
- - Each `- script "..."` node is one beat in the `transitionSeries`, so it gets a fade transition. The TTS duration of that script determines the segment length.
74
- - The component has `isBackground:true` so its `<Slide>` spans all beats of the scene.
75
- - **Beat count = bullet count**: TitleSlide gets 1 beat (just the welcome line), concept scenes get 1 beat per bullet, Summary gets 1 beat per row in the comparison table or per major takeaway.
76
-
77
- - **Thanks**: fixed-visual, `duration:6`, no script.
78
-
79
- - **Mermaid diagrams inside slides**: Fenced mermaid code blocks (` ```mermaid `) inside `~~~md source` are rendered by the `<Slide>` component (see §4). Use for flowcharts, architecture diagrams, sequence diagrams, timeline visualizations. The diagram renders as inline SVG — place it after bullet points or on its own.
80
-
81
- - **Duration is TTS-driven**: never set explicit `duration` on scenes that have a script. Only fixed-visual scenes (Hook, Thanks) get `duration:`.
82
-
83
- ### Production polish — optional enhancements
84
-
85
- Once the basic structure works, add these to make the video more professional.
86
-
87
- #### A. Bullet-level media (image/video/component per bullet)
88
-
89
- Each bullet can have its own visual accompaniment that appears only during that beat. Use `#### Beat` sub-scenes inside the concept scene to group the media and script into one transition segment:
90
-
91
- ```markdown
92
- ### Commits
93
- layout:transitionSeries transition:fade(0.5)
94
- - component id:c isBackground:true jsx:"<Slide current={current}>{source}</Slide>"
95
- ~~~md source
96
- ## Commits
97
- - 📸 **Snapshot** — Every commit records changes
98
- - 🏷️ **Unique hash** — Each has an ID like a1b2c3d
99
- - 🔗 **Real example** — GitHub commit history for a PR
100
- ~~~
101
-
102
- #### B1-Snapshot
103
- layout:parallel
104
- - image src:snapshot.png style:position:absolute;right:60px;top:50%;width:35%;border-radius:12px;transform:translateY(-50%)
105
- - script "A commit is like a photograph of your project at a moment in time..." start:0.5 on:(start, c.current=1)
106
-
107
- #### B2-Hash
108
- layout:parallel
109
- - image src:unique-id.png style:position:absolute;right:60px;top:50%;width:35%;border-radius:12px;transform:translateY(-50%)
110
- - script "Every commit gets a unique hash, like a1b2c3d, so you can always find it later..." start:0.5 on:(start, c.current=2)
111
-
112
- #### B3-Example
113
- layout:parallel
114
- - image src:pr-history.png style:position:absolute;right:60px;top:50%;width:35%;border-radius:12px;transform:translateY(-50%)
115
- - script "For example, when reviewing a pull request on GitHub, each commit tells a story..." start:0.5 on:(start, c.current=3)
116
- ```
117
-
118
- Rules for bullet-level media:
119
- - Position the media in the **right third** of the slide (`right:60px;width:35%`) so it doesn't overlap the left-aligned text.
120
- - Use `border-radius:12px` for images, `border-radius:50%` for circular narrator avatars.
121
- - The media node and script node are siblings in a `layout:parallel` sub-scene so they play together as one segment.
122
- - Each beat node name should briefly describe the visual (e.g. `#### B1-Snapshot`).
123
- - For **video** clips per bullet: use `- video src:...` instead of `- image src:...`. Same positioning.
124
- - For **component** per bullet: use `- component jsx:"<MyBulletVisual />"` with its own styling.
125
-
126
- #### B. Transition-audio timing
127
-
128
- In a `transitionSeries`, adjacent segments overlap by the transition duration (default 0.5s). Without adjustment, the next paragraph's TTS audio starts playing during the fade — creating cacophony.
129
-
130
- **Fix: add `start:<T>` to every script node in a transitionSeries, where T = your transition time.**
131
-
132
- ```markdown
133
- - script "Paragraph..." start:0.5 on:(start, c.current=1)
134
- ```
135
-
136
- This delays both the TTS audio AND the bullet-highlight event until after the transition completes. The transition time value must match the `transition:fade(T)` parameter.
137
-
138
- > **If using multiple transitionSeries (nested)**: segment overlap compounds. Each script still gets `start:T` relative to its own segment. The engine handles the compound timing.
139
-
140
- #### C. Persistent logo overlay
141
-
142
- Add a logo that appears throughout the entire video by placing an `- image isBackground:true` at the root level:
143
-
144
- ```markdown
145
- # video
146
- width:1920 height:1080 fps:30 layout:series
147
- - image isBackground:true src:logo.png style:position:absolute;bottom:24px;right:24px;width:60px;opacity:0.7
148
-
149
- ## Hook ...
150
- ## Slides ...
151
- ## Thanks ...
152
- ```
153
-
154
- The `isBackground:true` flag wraps the image in `<Loop>` so it plays for the full video duration. Position it in a corner so it doesn't cover slide content.
155
-
156
- Variations:
157
- - **Bottom-left**: `bottom:24px;left:24px`
158
- - **Transparent watermark**: `opacity:0.3;width:160px`
159
- - **Animated logo**: use `- video loop isBackground:true src:logo-animated.mp4`
160
- - **Component logo**: use `- component isBackground:true jsx:"<Logo />"` and define `<Logo>` in the imports block for custom styling.
161
-
162
- #### D. Narrator overlay (picture-in-picture)
163
-
164
- Add a narrator's face or avatar as a persistent overlay in a corner. Two approaches:
165
-
166
- **Static image (circular crop):**
167
- ```markdown
168
- - image isBackground:true src:narrator.png style:position:absolute;bottom:40px;left:40px;width:100px;height:100px;border-radius:50px;border:3px solid #61dafb;object-fit:cover
169
- ```
170
-
171
- **Video loop (animated narrator):**
172
- ```markdown
173
- - video loop isBackground:true src:narrator.mp4 style:position:absolute;bottom:40px;right:40px;width:140px;height:140px;border-radius:70px;object-fit:cover;border:3px solid rgba(255,255,255,.3)
174
- ```
175
-
176
- **With name label (using component):**
177
- ```markdown
178
- - component isBackground:true jsx:"<NarratorBox src='narrator.png' name='Dr. Smith' />"
179
- ```
180
-
181
- The `NarratorBox` component is available in the imports block (see §4) and adds a name label + glow effect.
182
-
183
- **Placement rules:**
184
- - Narrator overlay goes in the **opposite corner** from the logo. If logo is bottom-right, narrator goes bottom-left.
185
- - The overlay should be small enough (≤140px diameter) to not distract from slide content.
186
- - For video narrators, keep the B-roll simple and well-lit (talking head style).
187
-
188
- #### E. Effects (entrance animations)
189
-
190
- Apply CSS keyframe animations to images and components for entrance effects:
191
- ```markdown
192
- - image src:chart.png effect:zoomIn duration:1
193
- - component jsx:"<Counter />" effect:fadeIn duration:2
194
- ```
195
-
196
- See markcut docs for available effect types: `fadeIn`, `zoomIn`, `bounceIn`, `slideInLeft`, `slideInRight`, `flipIn`, or custom keyframes.
197
-
198
- #### F. TTS expression control
199
-
200
- Guide TTS pronunciation for more natural speech using SSML-style cues in the script text:
201
- - **Pause**: use `—` (em dash) or `...` for natural breath pauses
202
- - **Rate**: set `tts.rate:+10%` on the root config line for faster pacing, or `tts:{"--rate +10%"}` per scene
203
- - **Phonetic spelling**: for jargon ("Snyk" = "snik", "Grafana" = "gruh-fah-nuh"), add parenthetical pronunciation on first use: `Snyk (pronounced "snik")`
204
- - **Language mix**: for bilingual content, switch tts voice per language using the `# <lang>` variant block
205
-
206
- ## 3. Authoring rules — the professional bar
207
-
208
- Slide content:
209
-
210
- - One idea per slide. ≤ 6 bullets. Bullet shape: `🔹 **bold key phrase** — short elaboration`.
211
- - Emoji bullet prefixes for scannability (🤖 📊 🔄 🏷️ 💡 ⚡ …), consistent within the course.
212
- - Use a markdown **table** whenever content is comparative (applications, methods, milestones).
213
- - Use a `>` quote block for the one memorable takeaway line.
214
- - Code blocks only when actually teaching code; keep ≤ 10 lines per slide.
215
-
216
- Narration script:
217
-
218
- - **One paragraph per bullet.** Never merge bullets into a single monolithic script. Each bullet gets its own `- script "..."` node with `on:(start, id.current=N)`.
219
- - Paragraph length ≈ **15–30 words** per beat (≈6–12 seconds at 2.5 w/s). Total words = target minutes × 150.
220
- - Each paragraph **expands** that bullet — never reads it verbatim. The paragraph plus all sibling paragraphs must touch every bullet on the slide.
221
- - The concrete example gets its own bullet and its own script paragraph. Never bury the example in a generic paragraph.
222
- - Bullet ordering: concepts first (what/why), example last (concrete reinforcement). The example is the final beat before advancing to the next scene.
223
- - Summary: each row in the comparison table gets one beat. The final beat closes the course ("Thank you for watching…").
224
- - **Transition-audio timing**: in a `transitionSeries`, add `start:<transitionTime>` to each script node to prevent TTS audio from playing during fade transitions (see §2.b).
225
-
226
- Mermaid diagrams:
227
-
228
- - Use ` ```mermaid ` fenced code blocks inside `~~~md source` for charts, flowcharts, architecture diagrams, sequence diagrams, and timelines.
229
- - Place mermaid blocks after the bullet list, separated by a blank line.
230
- - The Slide component (see §4) renders mermaid code blocks as inline SVG. Ensure the diagram fits within the slide (keep node labels short, max 1–2 sentences per node).
231
- - For complex diagrams that take time to render, pre-render them as SVG files via `mmdc` (mermaid-cli) or your TTI CLI, and reference as `- image src:...` instead — this is more reliable for final render.
232
-
233
- Multi-language (variants):
234
-
235
- - For each extra language: a `<lang>:"..."` twin on every script node, a `~~~md <lang>-source` fence beside every `~~~md source`, and a `# <lang>` root block at the end of the file carrying that language's `tts:` voice.
236
- - See the worked example in §7 for the exact shape (`zh` variant).
237
-
238
- ## 4. Components & styles — canonical, copy verbatim
239
-
240
- Copy both blocks into the generated `course.md` unchanged (except the theme knobs below).
241
-
242
- ~~~~text
243
- ~~~js imports
244
- import ReactMarkdown from 'react-markdown';
245
- import remarkGfm from 'remark-gfm'
246
- import mermaid from 'mermaid'
247
- import React from 'react'
248
- import { delayRender, continueRender } from 'remotion'
249
-
250
- // Initialize mermaid once. Theme matches the slide deck's dark scheme.
251
- mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'loose' })
252
-
253
- /**
254
- * Renders a mermaid diagram as inline SVG.
255
- * Uses Remotion's delayRender/continueRender so the diagram is guaranteed
256
- * to be ready before the frame is captured.
257
- */
258
- export function Mermaid({ source }) {
259
- const ref = React.useRef(null)
260
-
261
- React.useEffect(() => {
262
- if (!source || !ref.current) return
263
- const handle = delayRender('Mermaid rendering')
264
- mermaid.render('mmd-' + Math.random().toString(36).slice(2), source)
265
- .then((result) => {
266
- if (ref.current) ref.current.innerHTML = result.svg
267
- continueRender(handle)
268
- })
269
- .catch((err) => {
270
- console.error('Mermaid error:', err)
271
- continueRender(handle)
272
- })
273
- }, [source])
274
-
275
- return <div ref={ref} style={{ width: '100%', maxWidth: 960, margin: '20px auto' }} />
276
- }
277
-
278
- /**
279
- * Courseware slide component.
280
- * - Renders markdown via react-markdown
281
- * - Highlights the Nth bullet when `current=N`
282
- * - Renders mermaid code blocks ( ```mermaid ...``` ) as inline diagrams
283
- */
284
- /**
285
- * Picture-in-picture narrator overlay with name label.
286
- * Place as isBackground:true at root level.
287
- */
288
- export function NarratorBox({ src, name = '', size = 100 }) {
289
- return (
290
- <div style={{
291
- position: 'absolute',
292
- bottom: 40,
293
- left: 40,
294
- display: 'flex',
295
- flexDirection: 'column',
296
- alignItems: 'center',
297
- gap: 6,
298
- }}>
299
- <div style={{
300
- width: size,
301
- height: size,
302
- borderRadius: '50%',
303
- overflow: 'hidden',
304
- border: '3px solid #61dafb',
305
- boxShadow: '0 0 20px rgba(97, 218, 251, 0.3)',
306
- }}>
307
- <img
308
- src={src}
309
- style={{ width: '100%', height: '100%', objectFit: 'cover' }}
310
- />
311
- </div>
312
- {name && (
313
- <span style={{
314
- fontSize: 14,
315
- color: '#a8dadc',
316
- textShadow: '0 2px 8px rgba(0,0,0,.6)',
317
- whiteSpace: 'nowrap',
318
- }}>{name}</span>
319
- )}
320
- </div>
321
- )
322
- }
323
-
324
- /**
325
- * Two-column layout: slide text on left, media on right.
326
- * Use inside a parallel layout for per-bullet media (see §2.b).
327
- */
328
- export function SplitSlide({ source, mediaSrc, current = 0, mediaPosition = 'right' }) {
329
- const mediaContent = mediaSrc && (
330
- <img src={mediaSrc} style={{
331
- maxWidth: '100%', maxHeight: '100%', objectFit: 'contain',
332
- borderRadius: 12, boxShadow: '0 4px 20px rgba(0,0,0,.4)',
333
- }} />
334
- )
335
- let idx = 1
336
- return (
337
- <div style={{
338
- display: 'flex', flexDirection: mediaPosition === 'right' ? 'row' : 'row-reverse',
339
- width: '100%', height: '100%', padding: 40, boxSizing: 'border-box', gap: 32,
340
- }}>
341
- <div style={{
342
- flex: 1, display: 'flex', flexDirection: 'column',
343
- justifyContent: 'center', minWidth: 0,
344
- }}>
345
- <div className="slide" style={{ padding: 0, margin: 0, width: '100%' }}>
346
- <ReactMarkdown remarkPlugins={[remarkGfm]}
347
- components={{
348
- li: ({ children }) => {
349
- const highlight = idx === current; idx++
350
- return <li className={highlight ? 'highlight' : ''}>{children}</li>
351
- },
352
- pre: ({ children }) => {
353
- const code = React.Children.toArray(children)[0]
354
- if (code?.props?.className === 'language-mermaid') {
355
- return <Mermaid source={String(code.props.children)} />
356
- }
357
- return <pre>{children}</pre>
358
- },
359
- }}>{source}</ReactMarkdown>
360
- </div>
361
- </div>
362
- <div style={{
363
- width: '40%', flexShrink: 0,
364
- display: 'flex', alignItems: 'center', justifyContent: 'center',
365
- }}>
366
- {mediaContent}
367
- </div>
368
- </div>
369
- )
370
- }
371
-
372
- export function Slide({ current = 0, children }) {
373
- let idx = 1
374
- return (
375
- <div className="slide">
376
- <ReactMarkdown remarkPlugins={[remarkGfm]}
377
- components={{
378
- li: ({ children }) => {
379
- const highlight = idx === current; idx++
380
- return <li className={highlight ? 'highlight' : ''}>{children}</li>
381
- },
382
- // Fenced code blocks — detect mermaid, render as diagram
383
- pre: ({ children }) => {
384
- const code = React.Children.toArray(children)[0]
385
- if (code?.props?.className === 'language-mermaid') {
386
- return <Mermaid source={String(code.props.children)} />
387
- }
388
- return <pre>{children}</pre>
389
- },
390
- }}>{children}</ReactMarkdown>
391
- </div>
392
- )
393
- }
394
- ~~~
395
- ~~~~
396
-
397
- ~~~~text
398
- ~~~css stylesheet
399
- /* Container sizing for a perfect 16:9 widescreen presentation slide */
400
- .slide {
401
- color: #f5f5f7;
402
- padding: 40px;
403
- display: flex;
404
- flex-direction: column;
405
- justify-content: center;
406
- align-items: flex-start;
407
- font-family: 'Helvetica Neue', Arial, sans-serif;
408
- font-size: 30px;
409
-
410
- /* Typography rules optimized for visibility from a distance */
411
- h1 {
412
- font-size: 2.8em;
413
- color: #61dafb;
414
- margin-top: 0;
415
- margin-bottom: 20px;
416
- line-height: 1.2;
417
- }
418
-
419
- h2 {
420
- font-size: 2em;
421
- color: #a8dadc;
422
- margin-top: 0;
423
- margin-bottom: 15px;
424
- }
425
-
426
- p {
427
- font-size: 1.3em;
428
- line-height: 1.6;
429
- color: #e0e0e6;
430
- margin-bottom: 15px;
431
- }
432
-
433
- /* List styling specific to presentation bullet points */
434
- ul, ol {
435
- margin-left: 25px;
436
- margin-bottom: 20px;
437
- }
438
-
439
- li {
440
- font-size: 1.3em;
441
- line-height: 1.8;
442
- margin-bottom: 10px;
443
- color: #e0e0e6;
444
- list-style-type: none; /* Remove default bullets for custom styling */
445
-
446
- /* Highlighted list item for event-driven bullet reveal */
447
- &.highlight {
448
- color: red;
449
- font-weight: 700;
450
- }
451
- }
452
-
453
- /* Code block handling inside slides */
454
- pre {
455
- background-color: #2d2d34;
456
- padding: 15px;
457
- border-radius: 6px;
458
- width: 100%;
459
- box-sizing: border-box;
460
- overflow-x: auto;
461
- }
462
-
463
- code {
464
- font-family: 'Courier New', Courier, monospace;
465
- font-size: 1.1em;
466
- color: #ffb703;
467
- }
468
-
469
- table {
470
- width: 100%;
471
- border-collapse: collapse;
472
- margin-bottom: 20px;
473
-
474
- th, td {
475
- border: 1px solid #444;
476
- padding: 10px;
477
- text-align: left;
478
- }
479
- }
480
- }
481
- ~~~
482
- ~~~~
483
-
484
- Theme knobs — change **values only**, never the structure:
485
-
486
- | Knob | Selector | Default |
487
- |---|---|---|
488
- | Accent (h1) | `.slide h1 { color }` | `#61dafb` |
489
- | Secondary (h2) | `.slide h2 { color }` | `#a8dadc` |
490
- | Reveal highlight | `.slide li.highlight { color }` | `red` |
491
- | Deck background | `.slide { background }` | transparent |
492
- | Font | `.slide { font-family }` | Helvetica Neue |
493
-
494
- ## 5. Workflow
495
-
496
- Prompts in `prompts/` are fill-in templates: replace every `{placeholder}`, then execute the prompt **in your own context**. `agents/` definitions run in a **separate session** (e.g. a fresh-context subagent).
497
-
498
- 1. **Outline** — fill `prompts/outline.md` → course outline. **Present the outline to the user and get confirmation** before continuing.
499
- 2. **Scenes** — for each outline section, fill `prompts/scene.md` → slide source + narration script (all languages).
500
- 3. **Assemble** — write `course.md`: §2 grammar, §3 rules, §4 blocks verbatim, root config line with the user's width/height/fps/voice.
501
- - **Basic**: standard bullet-reveal pattern (one scene, one Slide component, N script nodes per scene).
502
- - **With per-bullet media**: use `#### Beat` sub-scenes in `transitionSeries` for each bullet (see §2). Each beat is `layout:parallel` containing the image + script. Add `start:0.5` to each script node.
503
- - **Overlays**: add persistent logo/narrator as `- image isBackground:true` at the root level.
504
- 4. **Render** — `npx @lalalic/markcut render course.md`. On engine errors: fix and re-render, max 3 attempts per error, then ask the user.
505
- 5. **Review (quality gate)** — run `agents/reviewer.md` in a fresh separate session. Give it absolute paths to: `course.md`, the rendered MP4, this `TEMPLATE.md`, plus target duration and language(s). It returns `{verdict, findings[]}` and never edits anything.
506
- 6. **Fix loop** — on FAIL: fill `prompts/fix.md` with the findings, apply the edits yourself, go back to step 4. Max **3** review iterations, then escalate to the user with the open findings.
507
-
508
- ## 6. Quality gate — exit criteria
509
-
510
- Done only when ALL hold:
511
-
512
- - [ ] reviewer verdict = `PASS` (zero blocker/major findings)
513
- - [ ] total duration within ±15% of target
514
- - [ ] structure matches §2 (hook, title, 3–6 concepts, summary, thanks)
515
- - [ ] no blank/black frames at scene boundaries; slides legible at target resolution
516
- - [ ] STT transcript of rendered audio matches scripts (≥90% content match)
517
- - [ ] each bullet has a narration beat — beat count = bullet count in every concept scene
518
- - [ ] ≥80% of slides include a visual element beyond text-only bullets (chart, image, diagram, mermaid)
519
- - [ ] overlays (logo, narrator) positioned without obstructing slide content
520
- - [ ] no TTS audio plays during transitions (checked via `start:` offset on transitionSeries scripts)
521
-
522
-
523
-
524
- ## 7. Reference — worked example
525
-
526
- Golden example: [`tests/fixtures/templates/courseware.md`](../../tests/fixtures/templates/courseware.md) — a bilingual (en + zh) "Introduction to Machine Learning" course demonstrating every rule above: hook with generated image, bullet-reveal with `on:(start, slide1.current=N)`, comparative tables, summary with selection guide, and the `# zh` variant block.
@@ -1,84 +0,0 @@
1
- ---
2
- name: courseware-reviewer
3
- description: Read-only quality gate for videos produced from the courseware template. Runs in a separate session with fresh context.
4
- context: fresh
5
- mode: read-only
6
- tools: read file, bash (ffmpeg/ffprobe, STT CLI), image understanding
7
- ---
8
-
9
- # System prompt
10
-
11
- You are a strict, evidence-based QA reviewer for slide-based course videos. You review; you **never edit** any file. Every finding must cite concrete evidence (a frame image, an STT excerpt, a line in the source, a measured number). If you cannot verify a check, report it as a finding with severity `minor` and say why — never silently skip it.
12
-
13
- ## Inputs (provided in the task)
14
-
15
- - `course_md` — absolute path to the generated courseware markdown
16
- - `mp4` — absolute path to the rendered video
17
- - `template_md` — absolute path to the courseware `TEMPLATE.md` (the rulebook: §2 grammar, §3 authoring rules, §6 exit criteria)
18
- - `duration_min` — target duration in minutes
19
- - `languages` — expected language(s)
20
-
21
- ## Procedure
22
-
23
- ### 1. Static review (source vs rulebook)
24
-
25
- Read `template_md` §2/§3, then check `course_md`:
26
-
27
- - structure: hook → title → 3–6 concepts → summary → thanks
28
- - every concept scene: `layout:parallel` (or `transitionSeries` when bullet-reveal), one `isBackground:true` slide component + script node(s)
29
- - bullet-reveal wiring: `id:slideN` ↔ `on:(start, slideN.current=K)` pairs are consistent and in order
30
- - slide rules: ≤6 bullets, bold-key-phrase shape, table where comparative, one idea per slide
31
- - script rules: word count ≈ scene budget (2.5 w/s), expands rather than reads the slide, concrete example present per concept
32
- - imports/stylesheet blocks match the template canon (only theme-knob values may differ)
33
- - language variants complete and mirrored (every script has its `<lang>:` twin, every source its `<lang>-source`)
34
- - factual sanity: no obviously invented facts, numbers, or citations
35
-
36
- ### 2. Dynamic review (rendered artifact)
37
-
38
- - `ffprobe` the MP4: duration (compare to `duration_min` ±15%), resolution, fps, audio stream present
39
- - Extract frames: one at each scene midpoint plus each scene boundary ±0.5s (estimate boundaries from scene order and TTS-driven durations; evenly-spaced sampling of ~2× scene count is an acceptable fallback). For each frame, verify with image understanding:
40
- - not blank/black
41
- - it shows the expected slide for that timestamp (match heading/bullets against `course_md`)
42
- - legibility: text within safe margins, no overflow/clipping, readable contrast
43
- - Extract audio (`ffmpeg` → wav), transcribe with the STT CLI, and compare against the scripts in `course_md`: ≥90% content match, correct language, scene order correct
44
- - Spot-check subtitle overlay in ≥2 frames if subtitles are configured
45
-
46
- ### 3. Report
47
-
48
- Severity: `blocker` (broken output: blank frames, missing audio, wrong structure, unrendered scene) · `major` (professional-bar violation: slide/script mismatch, pacing off-budget >20%, illegible slide, missing example, variant out of sync) · `minor` (polish: emoji inconsistency, wording, small overflow).
49
-
50
- ## Output — exactly this JSON, nothing after it
51
-
52
- ```json
53
- {
54
- "verdict": "PASS | FAIL",
55
- "measured": { "duration_s": 0, "target_s": 0, "resolution": "", "stt_match_pct": 0 },
56
- "findings": [
57
- {
58
- "id": "F1",
59
- "severity": "blocker | major | minor",
60
- "scene": "<scene heading or 'global'>",
61
- "check": "<which rule/check from the procedure>",
62
- "issue": "<what is wrong>",
63
- "evidence": "<frame path / STT excerpt / source line / measurement>",
64
- "fix_hint": "<one-line suggestion for the orchestrator>"
65
- }
66
- ]
67
- }
68
- ```
69
-
70
- `PASS` requires zero `blocker` and zero `major` findings.
71
-
72
- # Task template (orchestrator fills and sends)
73
-
74
- ```
75
- Review a courseware video against its template.
76
-
77
- course_md: {course_md}
78
- mp4: {mp4}
79
- template_md: {template_md}
80
- duration_min: {duration_min}
81
- languages: {languages}
82
-
83
- Follow your procedure. Output the verdict JSON only.
84
- ```
@@ -1,30 +0,0 @@
1
- # Prompt: Apply Reviewer Findings
2
-
3
- > Fill every `{placeholder}`, then execute in your own (orchestrator) context.
4
- > Used in TEMPLATE.md §5 step 6 after a FAIL verdict. You edit; the reviewer never does.
5
-
6
- ---
7
-
8
- You are fixing a courseware video that failed review. Apply the findings below to `{course_md_path}` with **minimal, targeted edits** — do not rewrite passing scenes.
9
-
10
- Reviewer findings (JSON):
11
-
12
- {findings_json}
13
-
14
- Rules:
15
-
16
- - Address every `blocker` and `major` finding. Address `minor` findings only when the fix is trivial and local.
17
- - Each fix must respect TEMPLATE.md §2 scene grammar and §3 authoring rules — a fix that breaks a rule elsewhere is not a fix.
18
- - Slide/script mismatch → prefer fixing the **script** (cheaper: TTS regenerates; slide layout stays validated).
19
- - Pacing/duration findings → adjust script word counts toward budget (target duration × 150 words total).
20
- - Blank/illegible frame findings → check the scene's node (missing `isBackground:true`, broken `src`/`prompt`, oversized content) before touching styles.
21
- - Never modify the `~~~js imports` or `~~~css stylesheet` blocks except the documented theme knobs (TEMPLATE.md §4).
22
- - Keep all language variants in sync: an edit to a script or slide must be mirrored in every `<lang>:`/`<lang>-source` twin.
23
-
24
- After editing, list what changed:
25
-
26
- ```
27
- - <finding id>: <file location> — <one-line description of the edit>
28
- ```
29
-
30
- Then re-render and re-review (TEMPLATE.md §5 steps 4–5).