@amg555/frameforge-mcp 2.4.8 → 2.4.9

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@amg555/frameforge-mcp",
3
- "version": "2.4.8",
4
- "description": "FrameForge — AI video production engine for agentic IDEs. npx @amg555/frameforge-mcp. FFmpeg + TTS run on your machine. 21 MCP tools.",
3
+ "version": "2.4.9",
4
+ "description": "FrameForge — AI video production engine for agentic IDEs. npx @amg555/frameforge-mcp. FFmpeg + TTS run on your machine. 23 MCP tools.",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,7 +31,7 @@
31
31
  "start:local-render": "cross-env RENDER_MODE=full WORKER_PORT=3211 node src/worker/server-render-free.js",
32
32
  "db:migrate": "node scripts/db-migrate.js",
33
33
  "validate": "node scripts/deep-validate.js",
34
- "test": "node --test src/pipeline/retry-policy.test.js src/pipeline/telemetry.test.js src/pipeline/artifact-manifest.test.js src/pipeline/artifact-retention.test.js src/pipeline/external-adapter.test.js src/workflows/publishing.test.js src/workflows/script.test.js src/workflows/ingest.test.js src/animation/animation-system.test.js src/fact-checker/review-gate.test.js src/fact-checker/checker.test.js src/utils/log-redaction.test.js src/worker/server-render-free.test.js src/worker/server.test.js src/mcp-server/server.test.js src/quality-gate/gate.test.js src/shorts-extractor/extractor.test.js src/audio-mixer/mixer.test.js src/video-renderer/scene-animator.test.js src/video-renderer/renderer.test.js src/video-editor/editor.test.js plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.test.js src/harness/lib/pixels.test.js src/harness/lib/verify-purity.test.js src/harness/harness.test.js src/integration.test.js",
34
+ "test": "node --test src/pipeline/retry-policy.test.js src/pipeline/telemetry.test.js src/pipeline/artifact-manifest.test.js src/pipeline/artifact-retention.test.js src/pipeline/external-adapter.test.js src/workflows/publishing.test.js src/workflows/script.test.js src/workflows/ingest.test.js src/animation/animation-system.test.js src/fact-checker/review-gate.test.js src/fact-checker/checker.test.js src/utils/log-redaction.test.js src/worker/server-render-free.test.js src/worker/server.test.js src/mcp-server/server.test.js src/quality-gate/gate.test.js src/shorts-extractor/extractor.test.js src/audio-mixer/mixer.test.js src/video-renderer/scene-animator.test.js src/video-renderer/renderer.test.js src/video-editor/editor.test.js plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.test.js src/harness/lib/pixels.test.js src/harness/lib/verify-purity.test.js src/harness/lib/verify-quality.test.js src/harness/harness.test.js src/integration.test.js",
35
35
  "lint": "echo 'No linter configured — add eslint for production'",
36
36
  "check-credentials": "node src/utils/log-redaction.js",
37
37
  "generate-assets": "node scripts/generate-initial-assets.js",
@@ -0,0 +1,91 @@
1
+ # Motion Craft Reference — 10 Hard Motion Craft Rules
2
+
3
+ This guide defines the production motion design standards implemented across FrameForge and the HTML-as-compositor rendering pipeline.
4
+
5
+ ---
6
+
7
+ ## 1. Springs, Not Linear Easing
8
+ Real physical motion never moves at constant velocity or abrupt linear ramps. Every entrance and transition must model a damped harmonic oscillator:
9
+
10
+ $$\ddot{x} + 2\zeta\omega\dot{x} + \omega^2 x = 0$$
11
+
12
+ - **Default tension**: `160 - 200`
13
+ - **Default friction**: `13 - 16`
14
+ - **Closed-form function**: `spring(t, delay, tension, friction)` maps pure time $t$ to normalized displacement $[0, 1]$ with natural overshoot and settling.
15
+
16
+ ---
17
+
18
+ ## 2. Three-Property Entrances
19
+ Never animate a single property (such as opacity alone or scale alone). High-production visuals orchestrate three coordinated properties for every entering entity:
20
+ 1. **Scale**: $0.88 \to 1.0$ (spatial arrival)
21
+ 2. **Opacity**: $0.0 \to 1.0$ (perceptual fade)
22
+ 3. **Translation**: $Y: +40\text{px} \to 0\text{px}$ or $X: \pm 60\text{px} \to 0\text{px}$ (directional momentum)
23
+
24
+ ---
25
+
26
+ ## 3. Staggered Entrance Choreography
27
+ Never trigger all elements simultaneously. When rendering cards, list items, or diagram nodes:
28
+ - Stagger siblings by $0.15\text{s} - 0.28\text{s}$ relative to base timestamp.
29
+ - Hierarchy flows from primary anchor $\to$ structural components $\to$ explanatory callouts $\to$ micro-badges.
30
+
31
+ ---
32
+
33
+ ## 4. Asymmetric Timing: Exits Faster Than Entrances
34
+ - **Entrances**: Informative, welcoming, gentle settling ($0.5\text{s} - 0.8\text{s}$ duration).
35
+ - **Exits**: Crisp, decisive, purposeful ($0.2\text{s} - 0.35\text{s}$ duration). Exits must clear the stage without lingering.
36
+
37
+ ---
38
+
39
+ ## 5. Five-Layer Depth Stack
40
+ Every scene must maintain clear atmospheric depth across 5 distinct visual layers:
41
+ - **Layer 1: Ambient Backdrop** — Radial gradients, atmospheric tint, depth falloff.
42
+ - **Layer 2: Environment / Context** — Architectural wainscoting, blueprint grid, floor plane, distant mountain ridges, or starry cosmos.
43
+ - **Layer 3: Primary Visual Entity** — The core subject (doodle character, cutaway engine, split comparison cards, neural insight core, or optical loupe).
44
+ - **Layer 4: Explanatory Callouts & Overlays** — Connecting vector lines, animated pulse packets, telemetry tags, and indicator badges.
45
+ - **Layer 5: Foreground Framing & Vignette** — Subtle radial corner shading, specular glass arcs, or foreground foliage.
46
+
47
+ ---
48
+
49
+ ## 6. Continuous Audio-Driven Camera Progression
50
+ Scenes must never switch camera angles at arbitrary fixed timer intervals. Camera motion is a continuous parametric function of speech duration:
51
+
52
+ $$p = \frac{t}{\text{DURATION}}$$
53
+
54
+ - **Dynamic zoom**: $1.0 + p \times 0.08$
55
+ - **Dynamic pan**: Organic Lissajous drift ($\sin(0.4t) \times 6\text{px}$)
56
+ - **Cut synchronization**: Scene cuts occur strictly on spoken sentence boundaries and audio cues.
57
+
58
+ ---
59
+
60
+ ## 7. Continuous Life (Zero Frozen PowerPoint Slides)
61
+ Every frame must contain subtle life. An audience detects static images instantly:
62
+ - Rotating gears ($t \times \omega$)
63
+ - Oscillating telemetry needles and wave pulses
64
+ - Flowing data packets along pipeline tracks
65
+ - Gentle character breathing and foliage sway ($\sin(3t) \times 4\text{px}$)
66
+
67
+ ---
68
+
69
+ ## 8. Hand-Drawn Boil Wobble at 12fps
70
+ When rendering hand-drawn doodle characters and sketch line work, apply authentic 12fps line boil wobble:
71
+ - Quantize step to 12fps: `step = Math.floor(t * 12)`
72
+ - Use seeded deterministic trigonometric noise:
73
+ `Math.sin(seed * 19.37 + step * 2.41) * 2.4`
74
+ - Never use `Math.random()` inside the render loop.
75
+
76
+ ---
77
+
78
+ ## 9. Regional Typography & HarfBuzz Text Shaping
79
+ Ensure professional typographic hierarchy:
80
+ - Auto-detect script (Malayalam: Manjari / Gayathri / Chilanka; Devanagari: Noto Sans Devanagari; Latin: Inter / Rajdhani / Cinzel).
81
+ - Maintain WCAG AAA contrast ratios against backgrounds.
82
+ - Gate on-screen subtitles: display narration text only when explicitly requested in prompt.
83
+
84
+ ---
85
+
86
+ ## 10. Closed-Loop Visual QA Gate
87
+ Never deliver rendered video blind. The production harness must execute automated quality checks across 4 axes:
88
+ 1. **Content Presence**: Non-blank ($> 1.5\%$ foreground variance).
89
+ 2. **Luminance / Exposure**: Non-blown ($< 96\%$ overexposure).
90
+ 3. **Compositional Distribution**: Not corner-confined ($> 12\%$ bbox coverage).
91
+ 4. **Motion Delta Verification**: Test frame delta between $t = 30\%$ and $t = 70\%$ to guarantee continuous animation.
@@ -0,0 +1,65 @@
1
+ # Render Contract — Deterministic Frame Specification
2
+
3
+ This document specifies the strict mathematical and runtime contract required for all FrameForge HTML-as-compositor rendering engines.
4
+
5
+ ---
6
+
7
+ ## 1. Pure Mathematical Function of Time: $F(t) \to \text{Pixels}$
8
+
9
+ Every scene page must behave as a pure, stateless function of time $t$ in seconds:
10
+
11
+ $$\text{Frame}(t) = \mathcal{R}(\text{SceneState}(t))$$
12
+
13
+ - **Zero Memory Leaks**: Frame $N$ must never depend on whether Frame $N-1$ was previously rendered.
14
+ - **Out-of-Order Parallelism**: Frames can be rendered out of sequence across multiple worker threads (e.g. Worker 1 renders $t=1.0\text{s}$, Worker 2 renders $t=0.2\text{s}$, Worker 3 renders $t=2.5\text{s}$).
15
+
16
+ ---
17
+
18
+ ## 2. Virtual Clock Interception
19
+
20
+ The harness injects a virtual clock prior to page script execution (`capture.mjs`):
21
+
22
+ - **Overridden APIs**:
23
+ - `window.performance.now()` $\to$ returns $t \times 1000$
24
+ - `Date.now()` $\to$ returns `virtualEpoch + t * 1000`
25
+ - `window.Date` constructor $\to$ anchored to `VirtualDate(t)`
26
+ - `requestAnimationFrame(cb)` $\to$ registered into a synchronized callback queue
27
+ - `CSS Animations` $\to$ synced via `document.getAnimations().forEach(a => a.currentTime = t * 1000)`
28
+
29
+ ---
30
+
31
+ ## 3. Seeded PRNG Determinism Invariant
32
+
33
+ Standard `Math.random()` produces divergent frames on repeated renders and breaks cache hashing.
34
+ - **Forbidden**: `Math.random()`
35
+ - **Required**: Seeded PRNG (`sfc32` or deterministic integer hashing).
36
+ - **Time-Synchronized Reseed**:
37
+ ```javascript
38
+ let _seed = (0x2F6E2B1 ^ Math.imul(Math.floor(t * 1000), 0x9E3779B9)) | 0;
39
+ function prng() {
40
+ _seed = (_seed + 0x6D2B79F5) | 0;
41
+ let x = Math.imul(_seed ^ (_seed >>> 15), 1 | _seed);
42
+ x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;
43
+ return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 4. Three-Point Purity Gate (`verify-purity.mjs`)
50
+
51
+ Every scene must pass the 3-point purity gate prior to full-rate production encoding:
52
+ 1. **Repeat Check**: Sampling $t = 1.0\text{s}$ twice in succession must yield identical SHA-256 frame hashes.
53
+ 2. **Wander Check**: Seeking forward $0\text{s} \to 2.5\text{s}$ and then jumping back to $1.0\text{s}$ must yield the identical hash as the cold seek.
54
+ 3. **Cold Jump Check**: Spawning a fresh browser session and seeking immediately to $1.0\text{s}$ must match the sequential seek.
55
+
56
+ ---
57
+
58
+ ## 5. Master Encoding Contract (`encode.mjs`)
59
+
60
+ Output MP4s must comply with broadcast and streaming color standards:
61
+ - **Codec**: `libx264` (Profile: High, Level: 4.2)
62
+ - **Pixel Format**: `yuv420p`
63
+ - **Color Matrix**: `BT.709` tags:
64
+ `-colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv`
65
+ - **Audio Synchronization**: Exact audio mux with 18dB voiceover sidechain ducking.
@@ -0,0 +1,91 @@
1
+ # Motion Craft Reference — 10 Hard Motion Craft Rules
2
+
3
+ This guide defines the production motion design standards implemented across FrameForge and the HTML-as-compositor rendering pipeline.
4
+
5
+ ---
6
+
7
+ ## 1. Springs, Not Linear Easing
8
+ Real physical motion never moves at constant velocity or abrupt linear ramps. Every entrance and transition must model a damped harmonic oscillator:
9
+
10
+ $$\ddot{x} + 2\zeta\omega\dot{x} + \omega^2 x = 0$$
11
+
12
+ - **Default tension**: `160 - 200`
13
+ - **Default friction**: `13 - 16`
14
+ - **Closed-form function**: `spring(t, delay, tension, friction)` maps pure time $t$ to normalized displacement $[0, 1]$ with natural overshoot and settling.
15
+
16
+ ---
17
+
18
+ ## 2. Three-Property Entrances
19
+ Never animate a single property (such as opacity alone or scale alone). High-production visuals orchestrate three coordinated properties for every entering entity:
20
+ 1. **Scale**: $0.88 \to 1.0$ (spatial arrival)
21
+ 2. **Opacity**: $0.0 \to 1.0$ (perceptual fade)
22
+ 3. **Translation**: $Y: +40\text{px} \to 0\text{px}$ or $X: \pm 60\text{px} \to 0\text{px}$ (directional momentum)
23
+
24
+ ---
25
+
26
+ ## 3. Staggered Entrance Choreography
27
+ Never trigger all elements simultaneously. When rendering cards, list items, or diagram nodes:
28
+ - Stagger siblings by $0.15\text{s} - 0.28\text{s}$ relative to base timestamp.
29
+ - Hierarchy flows from primary anchor $\to$ structural components $\to$ explanatory callouts $\to$ micro-badges.
30
+
31
+ ---
32
+
33
+ ## 4. Asymmetric Timing: Exits Faster Than Entrances
34
+ - **Entrances**: Informative, welcoming, gentle settling ($0.5\text{s} - 0.8\text{s}$ duration).
35
+ - **Exits**: Crisp, decisive, purposeful ($0.2\text{s} - 0.35\text{s}$ duration). Exits must clear the stage without lingering.
36
+
37
+ ---
38
+
39
+ ## 5. Five-Layer Depth Stack
40
+ Every scene must maintain clear atmospheric depth across 5 distinct visual layers:
41
+ - **Layer 1: Ambient Backdrop** — Radial gradients, atmospheric tint, depth falloff.
42
+ - **Layer 2: Environment / Context** — Architectural wainscoting, blueprint grid, floor plane, distant mountain ridges, or starry cosmos.
43
+ - **Layer 3: Primary Visual Entity** — The core subject (doodle character, cutaway engine, split comparison cards, neural insight core, or optical loupe).
44
+ - **Layer 4: Explanatory Callouts & Overlays** — Connecting vector lines, animated pulse packets, telemetry tags, and indicator badges.
45
+ - **Layer 5: Foreground Framing & Vignette** — Subtle radial corner shading, specular glass arcs, or foreground foliage.
46
+
47
+ ---
48
+
49
+ ## 6. Continuous Audio-Driven Camera Progression
50
+ Scenes must never switch camera angles at arbitrary fixed timer intervals. Camera motion is a continuous parametric function of speech duration:
51
+
52
+ $$p = \frac{t}{\text{DURATION}}$$
53
+
54
+ - **Dynamic zoom**: $1.0 + p \times 0.08$
55
+ - **Dynamic pan**: Organic Lissajous drift ($\sin(0.4t) \times 6\text{px}$)
56
+ - **Cut synchronization**: Scene cuts occur strictly on spoken sentence boundaries and audio cues.
57
+
58
+ ---
59
+
60
+ ## 7. Continuous Life (Zero Frozen PowerPoint Slides)
61
+ Every frame must contain subtle life. An audience detects static images instantly:
62
+ - Rotating gears ($t \times \omega$)
63
+ - Oscillating telemetry needles and wave pulses
64
+ - Flowing data packets along pipeline tracks
65
+ - Gentle character breathing and foliage sway ($\sin(3t) \times 4\text{px}$)
66
+
67
+ ---
68
+
69
+ ## 8. Hand-Drawn Boil Wobble at 12fps
70
+ When rendering hand-drawn doodle characters and sketch line work, apply authentic 12fps line boil wobble:
71
+ - Quantize step to 12fps: `step = Math.floor(t * 12)`
72
+ - Use seeded deterministic trigonometric noise:
73
+ `Math.sin(seed * 19.37 + step * 2.41) * 2.4`
74
+ - Never use `Math.random()` inside the render loop.
75
+
76
+ ---
77
+
78
+ ## 9. Regional Typography & HarfBuzz Text Shaping
79
+ Ensure professional typographic hierarchy:
80
+ - Auto-detect script (Malayalam: Manjari / Gayathri / Chilanka; Devanagari: Noto Sans Devanagari; Latin: Inter / Rajdhani / Cinzel).
81
+ - Maintain WCAG AAA contrast ratios against backgrounds.
82
+ - Gate on-screen subtitles: display narration text only when explicitly requested in prompt.
83
+
84
+ ---
85
+
86
+ ## 10. Closed-Loop Visual QA Gate
87
+ Never deliver rendered video blind. The production harness must execute automated quality checks across 4 axes:
88
+ 1. **Content Presence**: Non-blank ($> 1.5\%$ foreground variance).
89
+ 2. **Luminance / Exposure**: Non-blown ($< 96\%$ overexposure).
90
+ 3. **Compositional Distribution**: Not corner-confined ($> 12\%$ bbox coverage).
91
+ 4. **Motion Delta Verification**: Test frame delta between $t = 30\%$ and $t = 70\%$ to guarantee continuous animation.
@@ -0,0 +1,65 @@
1
+ # Render Contract — Deterministic Frame Specification
2
+
3
+ This document specifies the strict mathematical and runtime contract required for all FrameForge HTML-as-compositor rendering engines.
4
+
5
+ ---
6
+
7
+ ## 1. Pure Mathematical Function of Time: $F(t) \to \text{Pixels}$
8
+
9
+ Every scene page must behave as a pure, stateless function of time $t$ in seconds:
10
+
11
+ $$\text{Frame}(t) = \mathcal{R}(\text{SceneState}(t))$$
12
+
13
+ - **Zero Memory Leaks**: Frame $N$ must never depend on whether Frame $N-1$ was previously rendered.
14
+ - **Out-of-Order Parallelism**: Frames can be rendered out of sequence across multiple worker threads (e.g. Worker 1 renders $t=1.0\text{s}$, Worker 2 renders $t=0.2\text{s}$, Worker 3 renders $t=2.5\text{s}$).
15
+
16
+ ---
17
+
18
+ ## 2. Virtual Clock Interception
19
+
20
+ The harness injects a virtual clock prior to page script execution (`capture.mjs`):
21
+
22
+ - **Overridden APIs**:
23
+ - `window.performance.now()` $\to$ returns $t \times 1000$
24
+ - `Date.now()` $\to$ returns `virtualEpoch + t * 1000`
25
+ - `window.Date` constructor $\to$ anchored to `VirtualDate(t)`
26
+ - `requestAnimationFrame(cb)` $\to$ registered into a synchronized callback queue
27
+ - `CSS Animations` $\to$ synced via `document.getAnimations().forEach(a => a.currentTime = t * 1000)`
28
+
29
+ ---
30
+
31
+ ## 3. Seeded PRNG Determinism Invariant
32
+
33
+ Standard `Math.random()` produces divergent frames on repeated renders and breaks cache hashing.
34
+ - **Forbidden**: `Math.random()`
35
+ - **Required**: Seeded PRNG (`sfc32` or deterministic integer hashing).
36
+ - **Time-Synchronized Reseed**:
37
+ ```javascript
38
+ let _seed = (0x2F6E2B1 ^ Math.imul(Math.floor(t * 1000), 0x9E3779B9)) | 0;
39
+ function prng() {
40
+ _seed = (_seed + 0x6D2B79F5) | 0;
41
+ let x = Math.imul(_seed ^ (_seed >>> 15), 1 | _seed);
42
+ x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;
43
+ return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 4. Three-Point Purity Gate (`verify-purity.mjs`)
50
+
51
+ Every scene must pass the 3-point purity gate prior to full-rate production encoding:
52
+ 1. **Repeat Check**: Sampling $t = 1.0\text{s}$ twice in succession must yield identical SHA-256 frame hashes.
53
+ 2. **Wander Check**: Seeking forward $0\text{s} \to 2.5\text{s}$ and then jumping back to $1.0\text{s}$ must yield the identical hash as the cold seek.
54
+ 3. **Cold Jump Check**: Spawning a fresh browser session and seeking immediately to $1.0\text{s}$ must match the sequential seek.
55
+
56
+ ---
57
+
58
+ ## 5. Master Encoding Contract (`encode.mjs`)
59
+
60
+ Output MP4s must comply with broadcast and streaming color standards:
61
+ - **Codec**: `libx264` (Profile: High, Level: 4.2)
62
+ - **Pixel Format**: `yuv420p`
63
+ - **Color Matrix**: `BT.709` tags:
64
+ `-colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv`
65
+ - **Audio Synchronization**: Exact audio mux with 18dB voiceover sidechain ducking.
@@ -20,12 +20,14 @@ check('batch handling', code.includes('Array.isArray(body)'));
20
20
  check('ping handler', code.includes("method === 'ping'"));
21
21
  check('notifications/init', code.includes("notifications/initialized"));
22
22
  check('body size limit', code.includes('MAX_BODY_BYTES'));
23
- check('SERVER_INFO version 2.4.8', code.includes("version: '2.4.8'"));
23
+ check('SERVER_INFO version 2.4.9', code.includes("version: '2.4.9'"));
24
24
  check('18 tools defined (incl get_job_status)', code.includes('get_job_status'));
25
25
  check('export_to_editor tool defined', code.includes('export_to_editor'));
26
26
  check('edit_video tool defined', code.includes('edit_video'));
27
27
  check('generate_scene_code tool defined', code.includes('generate_scene_code'));
28
28
  check('preview_scene tool defined', code.includes('preview_scene'));
29
+ check('verify_scene_quality tool defined', code.includes('verify_scene_quality'));
30
+ check('plan_video_edit tool defined', code.includes('plan_video_edit'));
29
31
  check('video-editor exists', fs.existsSync('./src/video-editor/editor.js'));
30
32
  check('voxforge TTS provider', code.includes('voxforge'));
31
33
  const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
@@ -287,10 +289,12 @@ check('harness/lib/capture.mjs exists', fs.existsSync('./src/harness/lib/c
287
289
  check('harness/lib/jobs.mjs exists', fs.existsSync('./src/harness/lib/jobs.mjs'));
288
290
  check('harness/lib/sheets.mjs exists', fs.existsSync('./src/harness/lib/sheets.mjs'));
289
291
  check('harness/lib/verify-purity.mjs exists', fs.existsSync('./src/harness/lib/verify-purity.mjs'));
292
+ check('harness/lib/verify-quality.mjs exists', fs.existsSync('./src/harness/lib/verify-quality.mjs'));
290
293
  check('harness/lib/pixels.mjs exists', fs.existsSync('./src/harness/lib/pixels.mjs'));
291
294
  check('harness/lib/encode.mjs exists', fs.existsSync('./src/harness/lib/encode.mjs'));
292
295
  check('asset harness.mjs exists', fs.existsSync(`${engineAssetBase}/scripts/harness.mjs`));
293
296
  check('asset verify-purity.mjs exists', fs.existsSync(`${engineAssetBase}/scripts/verify-purity.mjs`));
297
+ check('asset verify-quality.mjs exists', fs.existsSync(`${engineAssetBase}/scripts/verify-quality.mjs`));
294
298
 
295
299
  // ── Cloudflare Worker rate limiting ───────────────────────────────────────
296
300
  console.log('\n=== Cloudflare Worker rate limiting ===');
@@ -299,13 +303,15 @@ check('cf-worker has checkRateLimit', cfWorker.includes('checkRateLimit'));
299
303
  check('cf-worker hashes token (SHA-256)', cfWorker.includes('SHA-256'));
300
304
  check('cf-worker uses RATE_LIMITS KV', cfWorker.includes('env.RATE_LIMITS'));
301
305
  check('cf-worker returns 429 on limit', cfWorker.includes('429'));
302
- check('cf-worker version 2.4.8', cfWorker.includes("'2.4.8'"));
306
+ check('cf-worker version 2.4.9', cfWorker.includes("'2.4.9'"));
303
307
  check('wrangler.toml has kv_namespaces',fs.readFileSync('./cloudflare-mcp/wrangler.toml','utf8').includes('kv_namespaces'));
304
308
 
305
309
  // ── plugins/skills present ────────────────────────────────────────────────
306
310
  console.log('\n=== Plugin skills ===');
307
311
  check('video-pipeline SKILL.md exists', fs.existsSync('./plugins/video-pipeline/skills/video-pipeline/SKILL.md'));
308
312
  check('html-renderer SKILL.md exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/SKILL.md'));
313
+ check('motion-craft reference exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/references/motion-craft.md'));
314
+ check('render-contract reference exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/references/render-contract.md'));
309
315
 
310
316
  // ── Security: no .kiro references in committed source ────────────────────
311
317
  console.log('\n=== No stale .kiro references in source ===');
@@ -21,6 +21,7 @@ import { fileURLToPath } from 'url';
21
21
  import { doctorBrowser, launchHarnessBrowser } from '../lib/browser.mjs';
22
22
  import { openPageWithVirtualClock } from '../lib/capture.mjs';
23
23
  import { verifyScenePurity } from '../lib/verify-purity.mjs';
24
+ import { verifySceneQuality } from '../lib/verify-quality.mjs';
24
25
  import { renderSheet, renderStrip, renderCrop, renderStoryboard } from '../lib/sheets.mjs';
25
26
  import { renderFrameJobs } from '../lib/jobs.mjs';
26
27
  import { encodeFramesToMp4 } from '../lib/encode.mjs';
@@ -305,12 +306,40 @@ async function cmdPixels() {
305
306
  }
306
307
  }
307
308
 
309
+ async function cmdQuality() {
310
+ console.log(`🎯 Running Closed-Loop Visual QA Gate (1–10 Scoring) on: ${HTML_PATH}`);
311
+ const { browser } = await launchHarnessBrowser();
312
+ const sheetTarget = path.join(CHECK_DIR, 'sheet.jpg');
313
+ const res = await verifySceneQuality({
314
+ browser,
315
+ htmlPath: HTML_PATH,
316
+ sheetPath: sheetTarget,
317
+ });
318
+ await browser.close();
319
+
320
+ console.log(` Overall Scene Score: ${res.overallScore}/10 [${res.passed ? 'PASSED ✅' : 'FAILED ❌'}]`);
321
+ console.log(` Motion Delta: ${(res.motionDelta * 100).toFixed(0)}%`);
322
+ for (const f of res.sampledFrames) {
323
+ const issueStr = f.issues.length > 0 ? ` (${f.issues.join(', ')})` : '';
324
+ console.log(` t=${f.time.toFixed(2)}s: ${f.score}/10${issueStr}`);
325
+ }
326
+
327
+ const qualityReportPath = path.join(CHECK_DIR, 'quality_report.json');
328
+ fs.writeFileSync(qualityReportPath, JSON.stringify(res, null, 2), 'utf8');
329
+ console.log(` Quality report saved: ${qualityReportPath}`);
330
+
331
+ if (!res.passed && !flags['skip-quality']) {
332
+ throw new Error(`Scene failed closed-loop quality gate (Score: ${res.overallScore}/10). Fix worst frames: ${res.worstFrames.map(w => `${w.time}s`).join(', ')}`);
333
+ }
334
+ }
335
+
308
336
  async function cmdAll() {
309
- console.log('🚀 Running Complete Production Pipeline: Storyboard → Verify → Frames → Encode → Inspect');
337
+ console.log('🚀 Running Complete Production Pipeline: Storyboard → Verify → Quality → Frames → Encode → Inspect');
310
338
  await cmdDoctor();
311
339
  await cmdStoryboard();
312
340
  await cmdSheet();
313
341
  await cmdVerify();
342
+ await cmdQuality();
314
343
  await cmdFrames();
315
344
  await cmdEncode();
316
345
  console.log('\n✨ Production complete! Output ready at out/film.mp4');
@@ -321,6 +350,7 @@ try {
321
350
  case 'doctor': await cmdDoctor(); break;
322
351
  case 'storyboard': await cmdStoryboard(); break;
323
352
  case 'verify': await cmdVerify(); break;
353
+ case 'quality': await cmdQuality(); break;
324
354
  case 'sheet': await cmdSheet(); break;
325
355
  case 'strip': await cmdStrip(); break;
326
356
  case 'crop': await cmdCrop(); break;
@@ -329,7 +359,7 @@ try {
329
359
  case 'pixels': await cmdPixels(); break;
330
360
  case 'all': await cmdAll(); break;
331
361
  default:
332
- console.log(`Usage: harness <doctor|storyboard|verify|sheet|strip|crop|frames|encode|pixels|all> [options]`);
362
+ console.log(`Usage: harness <doctor|storyboard|verify|quality|sheet|strip|crop|frames|encode|pixels|all> [options]`);
333
363
  break;
334
364
  }
335
365
  } catch (err) {