hyper-animator-codex 0.8.0 → 0.8.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/README.md +20 -5
- package/package.json +4 -1
- package/skills/hyper-animator-codex/SKILL.md +25 -19
- package/skills/hyper-animator-codex/agents/openai.yaml +2 -2
- package/skills/hyper-animator-codex/contracts/shot-plan.schema.json +68 -2
- package/skills/hyper-animator-codex/contracts/workflow-policy.json +13 -1
- package/skills/hyper-animator-codex/references/examples/shot-plan-dual-catalog.json +32 -2
- package/skills/hyper-animator-codex/references/hyperframes-intent-workflow.md +16 -8
- package/skills/hyper-animator-codex/references/narration-audio-workflow.md +19 -12
- package/skills/hyper-animator-codex/references/shotcraft/motion-pack/README.md +32 -0
- package/skills/hyper-animator-codex/references/shotcraft/motion-pack/shotcraft-motion-pack.js +178 -0
- package/skills/hyper-animator-codex/scripts/git_checkpoint.mjs +2 -2
- package/skills/hyper-animator-codex/scripts/validate_shot_plan.mjs +82 -9
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
Install the `hyper-animator-codex` Codex skill from npm.
|
|
4
4
|
|
|
5
5
|
The skill guides Codex through a contract-first HyperFrames animation workflow:
|
|
6
|
-
natural-language brief, outline/narration detection, dual-catalog
|
|
7
|
-
(HyperFrames + ShotCraft),
|
|
8
|
-
|
|
6
|
+
natural-language brief, outline/narration detection, visual-first dual-catalog
|
|
7
|
+
shot planning (HyperFrames + ShotCraft), HTML animation authoring, MiniMax
|
|
8
|
+
mainland China audio after visual approval, validation, and render handoff.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -124,6 +124,11 @@ When rendering video, hide preview controls with any one of:
|
|
|
124
124
|
|
|
125
125
|
## Narration And Subtitles
|
|
126
126
|
|
|
127
|
+
The workflow detects narration needs early, but generated narration and
|
|
128
|
+
subtitles are deferred until the user approves the page, transitions, and
|
|
129
|
+
animation. Visual drafts use `audio.narration.mode: "deferred"` in the shot
|
|
130
|
+
plan.
|
|
131
|
+
|
|
127
132
|
Detect outline and narration files in a project directory:
|
|
128
133
|
|
|
129
134
|
```bash
|
|
@@ -180,7 +185,14 @@ node skills/hyper-animator-codex/scripts/query_shotcraft.mjs \
|
|
|
180
185
|
```
|
|
181
186
|
|
|
182
187
|
HyperFrames entries remain the renderable catalog; ShotCraft supplies shot
|
|
183
|
-
grammar and motion references that are translated into
|
|
188
|
+
grammar and motion references that are translated into `motion_contract`
|
|
189
|
+
commitments and then into HyperFrames/GSAP HTML.
|
|
190
|
+
|
|
191
|
+
For high-impact ShotCraft refs, the validator requires an explicit
|
|
192
|
+
`motion_contract.motion_preset`. The bundled
|
|
193
|
+
`references/shotcraft/motion-pack/` directory contains small GSAP helpers to
|
|
194
|
+
copy before freehanding the animation.
|
|
195
|
+
|
|
184
196
|
Validate a dual-catalog plan before authoring:
|
|
185
197
|
|
|
186
198
|
```bash
|
|
@@ -209,7 +221,8 @@ packaging use a faster cost-efficient model.
|
|
|
209
221
|
|
|
210
222
|
The skill can analyze background music and guide Codex to align HyperFrames/GSAP transitions to beats, bars, energy peaks, and detected music segments.
|
|
211
223
|
|
|
212
|
-
Install optional Python dependencies
|
|
224
|
+
Install optional Python dependencies yourself if you want automatic beat
|
|
225
|
+
analysis. The skill will not install them during a run:
|
|
213
226
|
|
|
214
227
|
```bash
|
|
215
228
|
python3 -m pip install librosa pydub numpy click
|
|
@@ -236,6 +249,8 @@ Then analyze the generated audio with `analyze_music_beats.py` and align animati
|
|
|
236
249
|
## Development
|
|
237
250
|
|
|
238
251
|
```bash
|
|
252
|
+
npm run preflight
|
|
239
253
|
npm test
|
|
254
|
+
npm run check
|
|
240
255
|
npm run pack:check
|
|
241
256
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyper-animator-codex",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Install the Hyper Animator skill for Codex and Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"postinstall": "node bin/postinstall.mjs",
|
|
11
|
+
"preflight": "node tools/verify-governance.mjs",
|
|
11
12
|
"test": "node --test",
|
|
13
|
+
"test:contracts": "node --test test/shot-plan-contract.test.mjs test/workflow-policy.test.mjs test/provider-policy.test.mjs",
|
|
14
|
+
"check": "npm run preflight && npm test && npm run pack:check",
|
|
12
15
|
"pack:check": "npm pack --dry-run"
|
|
13
16
|
},
|
|
14
17
|
"files": [
|
|
@@ -11,32 +11,32 @@ Turn a natural-language animation or video brief into a validated HyperFrames HT
|
|
|
11
11
|
|
|
12
12
|
## Required Flow
|
|
13
13
|
|
|
14
|
-
1. Read `contracts/workflow-policy.json` before doing any orchestration. Treat it as an executable product contract, not optional guidance. Generated narration and generated BGM must use `minimax_cn` at the declared `minimaxi.com` endpoints.
|
|
15
|
-
2. Enforce the provider boundary. Never run `hyperframes auth login` or `hyperframes tts`. Do not use HeyGen, Kokoro, or MusicGen. Do not install a fallback audio runtime or
|
|
14
|
+
1. Read `contracts/workflow-policy.json` before doing any orchestration. Treat it as an executable product contract, not optional guidance. Generated narration and generated BGM must use `minimax_cn` at the declared `minimaxi.com` endpoints; generated narration is gated until visual confirmation.
|
|
15
|
+
2. Enforce the provider boundary. Never run `hyperframes auth login` or `hyperframes tts`. Do not use HeyGen, Kokoro, or MusicGen. Do not install a fallback audio runtime, fallback model, or beat-analysis dependency. If MiniMax or optional beat analysis is unavailable, stop that stage and ask the user to choose: retry MiniMax CN, provide a local audio file/beat map, or skip the track.
|
|
16
16
|
3. Read `references/git-checkpoint-workflow.md` and run `scripts/git_checkpoint.mjs --phase init --message "chore: initialize hyper animator workspace" --allow-empty` before requirement capture. If the helper reports an existing dirty repository or linked worktree, stop and ask the user to resolve it.
|
|
17
|
-
4. When starting in a new directory, restarting, or handling outline/narration/voiceover/subtitle/full-mix work, read `references/narration-audio-workflow.md` and run `scripts/detect_project_files.mjs --dir . --pretty` before visual planning.
|
|
17
|
+
4. When starting in a new directory, restarting, or handling outline/narration/voiceover/subtitle/full-mix work, read `references/narration-audio-workflow.md` and run `scripts/detect_project_files.mjs --dir . --pretty` before visual planning. Detect outline/narration inputs only; do not process narration yet.
|
|
18
18
|
5. Capture the raw request and extract an initial intent profile: purpose, format, duration, content inputs, style tags, motion tags, and needed roles.
|
|
19
19
|
6. If purpose, format, or core content is unclear, ask the first clarification round before choosing catalog items.
|
|
20
20
|
7. Query both catalogs:
|
|
21
21
|
- Read `references/hyperframes-catalog-map.json` and score renderable HyperFrames candidates.
|
|
22
22
|
- Run `scripts/query_shotcraft.mjs --query "<purpose style motion>" --limit 8 --json` against `references/shotcraft/catalog.json` for shot grammar, pacing, transition, and camera references.
|
|
23
|
-
8. Pick candidate HyperFrames blocks/components plus ShotCraft references for each main scene, caption, effect, transition, and outro. ShotCraft TSX is a design reference
|
|
23
|
+
8. Pick candidate HyperFrames blocks/components plus ShotCraft references for each main scene, caption, effect, transition, and outro. Open the selected ShotCraft card READMEs and translate their timing, camera, transition, and known pitfalls into explicit `motion_contract.shotcraft_commitments`; ShotCraft TSX is a design reference, not the renderer. If a selected card is covered by `references/shotcraft/motion-pack/README.md`, use its `motion_contract.motion_preset` and GSAP helper before freehanding motion.
|
|
24
24
|
9. Decide generation mode:
|
|
25
25
|
- `generate_new_hyperframes_html` when the user asks to write HTML, create a new effect, customize style, match a brand, use complex animation, or when component snippets are only paste placeholders.
|
|
26
26
|
- `assemble_existing_catalog_items` only when the user asks to use existing catalog items or quickly compose installed blocks/components.
|
|
27
27
|
10. Apply model routing when model selection is available: use `gpt-5.6-sol` for page and animation authoring only; use `gpt-5.6-terra` or another fast cost-efficient model for requirements, discovery, catalog queries, shot planning, audio prompting, validation, and packaging. Do not create an extra agent/entity only to implement this routing.
|
|
28
|
-
11. Write a shot plan that follows `contracts/shot-plan.schema.json`,
|
|
29
|
-
12. Clarify audio: ask whether
|
|
30
|
-
13. If generated
|
|
31
|
-
14.
|
|
32
|
-
15.
|
|
33
|
-
16.
|
|
34
|
-
17.
|
|
35
|
-
18.
|
|
36
|
-
19.
|
|
37
|
-
20.
|
|
38
|
-
21.
|
|
39
|
-
22. Render only after user confirmation. Prefer rendering the base HTML; if rendering the preview copy, use `?render=1`, `?preview=0`, or `<html data-render-mode="video">` so preview controls are hidden. Then report output path and any caveats.
|
|
28
|
+
11. Write a visual-first shot plan that follows `contracts/shot-plan.schema.json`, sets workflow_stage: `visual_draft`, `catalog_strategy: "dual_catalog"`, `audio.narration.mode: "deferred"` when narration is requested, explicit start/end timing, both catalog reference lists, and one `motion_contract` per shot including `motion_preset`. Run `scripts/validate_shot_plan.mjs shot-plan.json`; fix every error before HTML authoring.
|
|
29
|
+
12. Clarify audio intent only: ask whether the final piece should use narration, subtitles, transition sound effects, and background music. Do not generate narration audio or subtitles before the user confirms the page, transition, and animation. If BGM is the rhythm source, it may be generated or imported before HTML; otherwise defer it with narration.
|
|
30
|
+
13. If generated BGM is needed as the rhythm source, read `references/minimax-music-workflow.md`, run `scripts/validate_provider_policy.mjs --kind bgm --provider minimax_cn --endpoint https://api.minimaxi.com/v1/music_generation`, and use `scripts/generate_minimax_music.mjs`. Then read `references/beat-sync-workflow.md` and run `scripts/analyze_music_beats.py` when dependencies and the file are already available. Do not install beat-analysis dependencies during the run.
|
|
31
|
+
14. Ask the second clarification round with dual-catalog candidate context: visual direction, motion rhythm, selected ShotCraft commitments, generation mode, BGM/beat-sync assumptions when present, and the fact that narration is deferred until visual approval.
|
|
32
|
+
15. Write or assemble the visual HTML using `references/HyperFrames-AI-Generation-Patterns-codex.md`. When beat-sync is enabled, align major reveals, cuts, transitions, camera moves, subtitle placeholders, and visual accents to the beat map instead of arbitrary timestamps.
|
|
33
|
+
16. After each file-writing stage, run `scripts/git_checkpoint.mjs` with the phase-specific allowlist from `references/git-checkpoint-workflow.md`. Do not create, switch, or recommend Git branches or worktrees.
|
|
34
|
+
17. Run pre-render quality gates on the base HTML.
|
|
35
|
+
18. Read `references/preview-controls-workflow.md`, run `scripts/inject_preview_controls.mjs` to create a preview HTML copy, and run `scripts/validate_hyperframes_html.py preview.html --preview-controls`.
|
|
36
|
+
19. Show a concise visual plan summary and preview path. Ask the user to confirm or revise the page, transition, and animation before any generated narration or subtitle work.
|
|
37
|
+
20. After visual confirmation, handle narration. If generated narration is used, first run `scripts/validate_provider_policy.mjs --kind narration --provider minimax_cn --endpoint https://api.minimaxi.com/v1/t2a_v2`. Use `scripts/generate_minimax_tts.mjs` for voice listing and TTS; do not construct MiniMax HTTP calls directly. For multi-scene narration, use `--split-scenes` and treat `narration-manifest.json` actual durations as the scene-timing source. Generate subtitles only after this with `scripts/generate_subtitles.mjs --timing-manifest <voice-output>/narration-manifest.json`.
|
|
38
|
+
21. Reconcile shot boundaries with actual narration durations, update the shot plan to `workflow_stage: "audio_integrated"`, re-run `scripts/validate_shot_plan.mjs`, update the HTML/audio preview, and ask for final render confirmation.
|
|
39
|
+
22. Render only after final user confirmation. Prefer rendering the base HTML; if rendering the preview copy, use `?render=1`, `?preview=0`, or `<html data-render-mode="video">` so preview controls are hidden. Then report output path and any caveats.
|
|
40
40
|
|
|
41
41
|
## Interactive Questions
|
|
42
42
|
|
|
@@ -44,8 +44,8 @@ Prefer Codex interactive question tools such as `AskUserQuestion` or `request_us
|
|
|
44
44
|
|
|
45
45
|
Use two rounds:
|
|
46
46
|
|
|
47
|
-
- Round 1: purpose, format, duration, platform, required content, outline/narration status, narration/subtitles, brand assets, sound effects, background music, and whether video render is expected in this turn.
|
|
48
|
-
- Round 2: after catalog scoring, ask about style, motion, candidate selection, generation mode,
|
|
47
|
+
- Round 1: purpose, format, duration, platform, required content, outline/narration status, desired final narration/subtitles, brand assets, sound effects, background music, and whether video render is expected in this turn.
|
|
48
|
+
- Round 2: after catalog scoring, ask about style, motion, candidate selection, generation mode, selected ShotCraft commitments, and beat-sync assumptions when background music is present. Defer voice/emotion/subtitle generation choices until page, transition, and animation approval.
|
|
49
49
|
|
|
50
50
|
Do not ask everything upfront when the brief is already specific. Ask only for missing or decision-changing information.
|
|
51
51
|
|
|
@@ -57,6 +57,7 @@ Do not ask everything upfront when the brief is already specific. Ask only for m
|
|
|
57
57
|
- Read `references/hyperframes-catalog-map.json` whenever selecting catalog candidates or determining visual references.
|
|
58
58
|
- Read `references/shotcraft/catalog.json` and use `scripts/query_shotcraft.mjs` whenever building or revising a shot plan. Open only the selected cards and source references, not the entire bundle.
|
|
59
59
|
- Read selected `references/shotcraft/cards/<name>/README.md` files for intent, timing, parameters, and known failure modes. Treat selected `references/shotcraft/source/**/*.tsx` as motion references that must be translated into the HyperFrames/GSAP contract.
|
|
60
|
+
- Read `references/shotcraft/motion-pack/README.md` before HTML authoring when selected ShotCraft refs include a supported preset. Copy only needed helpers from `references/shotcraft/motion-pack/shotcraft-motion-pack.js`.
|
|
60
61
|
- Read `contracts/shot-plan.schema.json` before writing a shot plan and validate the result with `scripts/validate_shot_plan.mjs`.
|
|
61
62
|
- Read `references/HyperFrames-AI-Generation-Patterns-codex.md` before generating new HyperFrames HTML.
|
|
62
63
|
- Read `references/hyperframes-agent-pseudocode.ts` when implementing the end-to-end loop or when the correct sequence is ambiguous.
|
|
@@ -71,6 +72,8 @@ Hard constraints beat score:
|
|
|
71
72
|
|
|
72
73
|
- Use the HyperFrames map and ShotCraft together. HyperFrames answers “what can be rendered”; ShotCraft answers “how the shot should communicate and move.”
|
|
73
74
|
- Query ShotCraft first, then open only the top relevant cards. By default exclude `reference-only` styles.
|
|
75
|
+
- Every selected ShotCraft reference must become a concrete `motion_contract.shotcraft_commitments` item in the shot plan before HTML authoring.
|
|
76
|
+
- Supported ShotCraft refs must also select the matching `motion_contract.motion_preset`; use the motion pack helper before inventing a new GSAP pattern.
|
|
74
77
|
- Do not install React, Remotion, or ShotCraft dependencies merely to use a ShotCraft reference.
|
|
75
78
|
- Portrait requests prioritize portrait items such as `instagram-follow`, `tiktok-follow`, `spotify-card`, and `flowchart-vertical`.
|
|
76
79
|
- Caption requests prioritize `caption-*` components.
|
|
@@ -113,7 +116,10 @@ Before approval, inspect the preview against the selected ShotCraft cards:
|
|
|
113
116
|
|
|
114
117
|
- every required product capability has a distinct, readable shot;
|
|
115
118
|
- every shot adds new information and preserves the selected card's motion
|
|
116
|
-
grammar, critical timing,
|
|
119
|
+
grammar, critical timing, documented known pitfalls, and written
|
|
120
|
+
`motion_contract.shotcraft_commitments`;
|
|
121
|
+
- supported ShotCraft refs use their declared `motion_contract.motion_preset`
|
|
122
|
+
and corresponding motion-pack helper;
|
|
117
123
|
- real product states/assets are used when supplied, with no invented product
|
|
118
124
|
claims;
|
|
119
125
|
- text, masks, media, fonts, and async resources are fully loaded at render
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Hyper Animator Codex"
|
|
3
|
-
short_description: "Plan
|
|
4
|
-
default_prompt: "Use $hyper-animator-codex to plan
|
|
3
|
+
short_description: "Plan visual-first HyperFrames videos with ShotCraft"
|
|
4
|
+
default_prompt: "Use $hyper-animator-codex to plan visual-first shots with HyperFrames and ShotCraft, create validated HyperFrames HTML, and add MiniMax CN narration or BGM only at the approved stage."
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"additionalProperties": false,
|
|
7
7
|
"required": [
|
|
8
8
|
"schema_version",
|
|
9
|
+
"workflow_stage",
|
|
9
10
|
"generation_mode",
|
|
10
11
|
"catalog_strategy",
|
|
11
12
|
"dimensions",
|
|
@@ -18,6 +19,13 @@
|
|
|
18
19
|
"schema_version": {
|
|
19
20
|
"const": "1.0.0"
|
|
20
21
|
},
|
|
22
|
+
"workflow_stage": {
|
|
23
|
+
"enum": [
|
|
24
|
+
"visual_draft",
|
|
25
|
+
"audio_integrated",
|
|
26
|
+
"render_ready"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
21
29
|
"generation_mode": {
|
|
22
30
|
"enum": [
|
|
23
31
|
"assemble_existing_catalog_items",
|
|
@@ -109,7 +117,8 @@
|
|
|
109
117
|
"enum": [
|
|
110
118
|
"generated",
|
|
111
119
|
"user_local_file",
|
|
112
|
-
"none"
|
|
120
|
+
"none",
|
|
121
|
+
"deferred"
|
|
113
122
|
]
|
|
114
123
|
},
|
|
115
124
|
"provider": {
|
|
@@ -167,7 +176,8 @@
|
|
|
167
176
|
"end_seconds",
|
|
168
177
|
"purpose",
|
|
169
178
|
"hyperframes_refs",
|
|
170
|
-
"shotcraft_refs"
|
|
179
|
+
"shotcraft_refs",
|
|
180
|
+
"motion_contract"
|
|
171
181
|
],
|
|
172
182
|
"properties": {
|
|
173
183
|
"id": {
|
|
@@ -188,6 +198,7 @@
|
|
|
188
198
|
},
|
|
189
199
|
"hyperframes_refs": {
|
|
190
200
|
"type": "array",
|
|
201
|
+
"minItems": 1,
|
|
191
202
|
"items": {
|
|
192
203
|
"type": "string",
|
|
193
204
|
"minLength": 1
|
|
@@ -196,6 +207,7 @@
|
|
|
196
207
|
},
|
|
197
208
|
"shotcraft_refs": {
|
|
198
209
|
"type": "array",
|
|
210
|
+
"minItems": 1,
|
|
199
211
|
"items": {
|
|
200
212
|
"type": "string",
|
|
201
213
|
"minLength": 1
|
|
@@ -207,6 +219,60 @@
|
|
|
207
219
|
"string",
|
|
208
220
|
"null"
|
|
209
221
|
]
|
|
222
|
+
},
|
|
223
|
+
"motion_contract": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"additionalProperties": false,
|
|
226
|
+
"required": [
|
|
227
|
+
"motion_preset",
|
|
228
|
+
"energy",
|
|
229
|
+
"camera",
|
|
230
|
+
"transition",
|
|
231
|
+
"shotcraft_commitments",
|
|
232
|
+
"readable_rest_seconds"
|
|
233
|
+
],
|
|
234
|
+
"properties": {
|
|
235
|
+
"motion_preset": {
|
|
236
|
+
"enum": [
|
|
237
|
+
"none",
|
|
238
|
+
"slam-entrance",
|
|
239
|
+
"whip-transition",
|
|
240
|
+
"crash-zoom",
|
|
241
|
+
"push-stack-wipe",
|
|
242
|
+
"dataviz-landscape-open",
|
|
243
|
+
"panel-grid-reflow",
|
|
244
|
+
"brand-resolve"
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
"energy": {
|
|
248
|
+
"enum": [
|
|
249
|
+
"quiet",
|
|
250
|
+
"balanced",
|
|
251
|
+
"high",
|
|
252
|
+
"cinematic"
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
"camera": {
|
|
256
|
+
"type": "string",
|
|
257
|
+
"minLength": 1
|
|
258
|
+
},
|
|
259
|
+
"transition": {
|
|
260
|
+
"type": "string",
|
|
261
|
+
"minLength": 1
|
|
262
|
+
},
|
|
263
|
+
"shotcraft_commitments": {
|
|
264
|
+
"type": "array",
|
|
265
|
+
"minItems": 1,
|
|
266
|
+
"items": {
|
|
267
|
+
"type": "string",
|
|
268
|
+
"minLength": 1
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
"readable_rest_seconds": {
|
|
272
|
+
"type": "number",
|
|
273
|
+
"minimum": 0
|
|
274
|
+
}
|
|
275
|
+
}
|
|
210
276
|
}
|
|
211
277
|
}
|
|
212
278
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"generated_narration": {
|
|
6
6
|
"provider": "minimax_cn",
|
|
7
7
|
"endpoint": "https://api.minimaxi.com/v1/t2a_v2",
|
|
8
|
+
"stage_after": "visual_confirmation",
|
|
8
9
|
"backup_endpoints": [
|
|
9
10
|
"https://api-bj.minimaxi.com/v1/t2a_v2"
|
|
10
11
|
],
|
|
@@ -26,10 +27,14 @@
|
|
|
26
27
|
],
|
|
27
28
|
"forbidden_commands": [
|
|
28
29
|
"hyperframes auth login",
|
|
29
|
-
"hyperframes tts"
|
|
30
|
+
"hyperframes tts",
|
|
31
|
+
"python3 -m pip install librosa",
|
|
32
|
+
"pip3 install librosa",
|
|
33
|
+
"pip install librosa"
|
|
30
34
|
],
|
|
31
35
|
"automatic_fallback": false,
|
|
32
36
|
"automatic_runtime_install": false,
|
|
37
|
+
"automatic_dependency_install": false,
|
|
33
38
|
"failure_choices": [
|
|
34
39
|
"retry_minimax_cn",
|
|
35
40
|
"use_user_local_file",
|
|
@@ -69,6 +74,13 @@
|
|
|
69
74
|
"default_statuses": [
|
|
70
75
|
"implemented"
|
|
71
76
|
],
|
|
77
|
+
"motion_pack_path": "references/shotcraft/motion-pack/README.md",
|
|
78
|
+
"motion_pack_snippet": "references/shotcraft/motion-pack/shotcraft-motion-pack.js",
|
|
79
|
+
"prefer_motion_pack_before_freehand": true,
|
|
80
|
+
"motion_pack_required_for_supported_refs": true,
|
|
81
|
+
"motion_contract_required": true,
|
|
82
|
+
"minimum_commitments_per_shot": 1,
|
|
83
|
+
"read_selected_cards_before_html": true,
|
|
72
84
|
"excluded_asset_extensions": [
|
|
73
85
|
".mp4",
|
|
74
86
|
".mov",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.0.0",
|
|
3
|
+
"workflow_stage": "visual_draft",
|
|
3
4
|
"generation_mode": "generate_new_hyperframes_html",
|
|
4
5
|
"catalog_strategy": "dual_catalog",
|
|
5
6
|
"dimensions": {
|
|
@@ -9,8 +10,7 @@
|
|
|
9
10
|
"duration_seconds": 12,
|
|
10
11
|
"audio": {
|
|
11
12
|
"narration": {
|
|
12
|
-
"mode": "
|
|
13
|
-
"provider": "minimax_cn"
|
|
13
|
+
"mode": "deferred"
|
|
14
14
|
},
|
|
15
15
|
"bgm": {
|
|
16
16
|
"mode": "generated",
|
|
@@ -33,6 +33,16 @@
|
|
|
33
33
|
"shotcraft_refs": [
|
|
34
34
|
"scene-locked-title"
|
|
35
35
|
],
|
|
36
|
+
"motion_contract": {
|
|
37
|
+
"motion_preset": "none",
|
|
38
|
+
"energy": "cinematic",
|
|
39
|
+
"camera": "slow crane rise from a title detail to the full product stage",
|
|
40
|
+
"transition": "full-frame light wipe into the proof shot",
|
|
41
|
+
"shotcraft_commitments": [
|
|
42
|
+
"keep the title locked and readable before the camera travel opens the scene"
|
|
43
|
+
],
|
|
44
|
+
"readable_rest_seconds": 0.6
|
|
45
|
+
},
|
|
36
46
|
"narration_scene": "1"
|
|
37
47
|
},
|
|
38
48
|
{
|
|
@@ -46,6 +56,16 @@
|
|
|
46
56
|
"shotcraft_refs": [
|
|
47
57
|
"ai-stream-response"
|
|
48
58
|
],
|
|
59
|
+
"motion_contract": {
|
|
60
|
+
"motion_preset": "none",
|
|
61
|
+
"energy": "high",
|
|
62
|
+
"camera": "locked readable screen with short punch-ins on key rows",
|
|
63
|
+
"transition": "snap to the final proof state on a clear beat",
|
|
64
|
+
"shotcraft_commitments": [
|
|
65
|
+
"stream response rows progressively instead of fading static copy"
|
|
66
|
+
],
|
|
67
|
+
"readable_rest_seconds": 0.5
|
|
68
|
+
},
|
|
49
69
|
"narration_scene": "2"
|
|
50
70
|
},
|
|
51
71
|
{
|
|
@@ -59,6 +79,16 @@
|
|
|
59
79
|
"shotcraft_refs": [
|
|
60
80
|
"ui-strip-away-outro"
|
|
61
81
|
],
|
|
82
|
+
"motion_contract": {
|
|
83
|
+
"motion_preset": "none",
|
|
84
|
+
"energy": "cinematic",
|
|
85
|
+
"camera": "wide resolve with a final centered brand hold",
|
|
86
|
+
"transition": "strip away interface fragments into the outro mark",
|
|
87
|
+
"shotcraft_commitments": [
|
|
88
|
+
"resolve dense motion into a clean brand hold for at least one second"
|
|
89
|
+
],
|
|
90
|
+
"readable_rest_seconds": 1
|
|
91
|
+
},
|
|
62
92
|
"narration_scene": "3"
|
|
63
93
|
}
|
|
64
94
|
],
|
|
@@ -19,23 +19,28 @@
|
|
|
19
19
|
Transition、可选 Outro,以及每个镜头的 ShotCraft 参考。
|
|
20
20
|
6. 判断 generation mode:组装现有 catalog,还是生成新 HTML。
|
|
21
21
|
7. 调用 `AskUserQuestion` 第二轮澄清样式、动效、候选取舍,以及 generation mode。
|
|
22
|
-
8.
|
|
23
|
-
|
|
22
|
+
8. 写出 `workflow_stage: visual_draft` 的 shot plan。每个镜头必须包含
|
|
23
|
+
`motion_contract.motion_preset` 和 `motion_contract.shotcraft_commitments`,
|
|
24
|
+
把 ShotCraft 卡片的镜头、转场、节奏和避坑要求变成可验证条目。若最终需要
|
|
25
|
+
旁白,此阶段使用 `audio.narration.mode: deferred`。
|
|
26
|
+
9. 运行 `scripts/validate_shot_plan.mjs shot-plan.json`。
|
|
27
|
+
10. 若选择 `assemble_existing_catalog_items`,生成 wrapper composition,并解析 component paste 指令为真实 snippet。
|
|
28
|
+
11. 若选择 `generate_new_hyperframes_html`,按
|
|
24
29
|
`references/HyperFrames-AI-Generation-Patterns-codex.md` 生成完整
|
|
25
30
|
block/component HTML;ShotCraft TSX 只作为动效参考,不引入 Remotion
|
|
26
31
|
运行时。
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
14. 渲染视频。
|
|
32
|
+
12. 运行 pre-render quality gates。
|
|
33
|
+
13. 展示方案摘要和 HTML preview,让用户确认页面、转场和动画。
|
|
34
|
+
14. 视觉确认后再生成旁白、字幕并按实际时长更新 shot plan。
|
|
35
|
+
15. 根据反馈修订。
|
|
36
|
+
16. 渲染视频。
|
|
33
37
|
|
|
34
38
|
## Intent Profile
|
|
35
39
|
|
|
36
40
|
```json
|
|
37
41
|
{
|
|
38
42
|
"rawRequest": "",
|
|
43
|
+
"workflowStage": "visual_draft",
|
|
39
44
|
"purpose": "product_launch",
|
|
40
45
|
"format": "landscape_16_9",
|
|
41
46
|
"generationMode": "generate_new_hyperframes_html",
|
|
@@ -136,6 +141,8 @@ score =
|
|
|
136
141
|
}
|
|
137
142
|
```
|
|
138
143
|
|
|
144
|
+
Round 2 不选择 voice/emotion,也不生成字幕。旁白选择放到视觉确认后。
|
|
145
|
+
|
|
139
146
|
## HTML Generation Plan
|
|
140
147
|
|
|
141
148
|
生成 HTML 前输出 plan:
|
|
@@ -149,6 +156,7 @@ score =
|
|
|
149
156
|
{"id": "logo-outro", "role": "outro_reference"}
|
|
150
157
|
],
|
|
151
158
|
"patternsRequired": true,
|
|
159
|
+
"audio": {"narration": {"mode": "deferred"}},
|
|
152
160
|
"assumptions": ["用户未提供 logo,先使用文字 logo 占位"],
|
|
153
161
|
"nextValidation": "请确认风格、文案、时长和画幅。"
|
|
154
162
|
}
|
|
@@ -15,19 +15,24 @@ Rules:
|
|
|
15
15
|
- scan only the current directory top level;
|
|
16
16
|
- trust fixed names first: `outline.*` and `narration.*`;
|
|
17
17
|
- if multiple candidates are returned, ask the user to choose;
|
|
18
|
-
- if only outline exists,
|
|
19
|
-
- if only narration exists,
|
|
18
|
+
- if only outline exists, record narration as deferred until visual approval;
|
|
19
|
+
- if only narration exists, use it as content context but do not generate audio or subtitles yet;
|
|
20
20
|
- if narration is not used, do not generate voice audio or subtitles.
|
|
21
21
|
|
|
22
22
|
Codex writes or edits outline/narration content. Scripts only validate, convert, call MiniMax, and write artifacts.
|
|
23
23
|
|
|
24
|
+
Narration is a post-visual-confirmation stage. Do not generate MiniMax TTS,
|
|
25
|
+
voice lists, or subtitles until the user has approved the page, transition,
|
|
26
|
+
and animation direction. A visual draft shot plan should use
|
|
27
|
+
`audio.narration.mode: "deferred"` when narration is requested.
|
|
28
|
+
|
|
24
29
|
## Provider Boundary
|
|
25
30
|
|
|
26
31
|
Generated narration and BGM use MiniMax mainland China (`minimax_cn`) only.
|
|
27
32
|
Never run `hyperframes auth login` or `hyperframes tts`; never invoke HeyGen,
|
|
28
|
-
Kokoro, or MusicGen; and never install a fallback runtime/model
|
|
29
|
-
|
|
30
|
-
not needed.
|
|
33
|
+
Kokoro, or MusicGen; and never install a fallback runtime/model or beat
|
|
34
|
+
analysis dependency during a run. Kokoro is a local open-weight TTS engine,
|
|
35
|
+
but it is outside this product contract and is not needed.
|
|
31
36
|
|
|
32
37
|
Before a generated-audio call, validate the route:
|
|
33
38
|
|
|
@@ -121,15 +126,17 @@ Subtitle rules:
|
|
|
121
126
|
|
|
122
127
|
## BGM, Beat, And Schedule
|
|
123
128
|
|
|
124
|
-
BGM is the timing source when present
|
|
129
|
+
BGM is the timing source when present, but narration still waits for visual
|
|
130
|
+
approval:
|
|
125
131
|
|
|
126
132
|
1. decide BGM generation, local import, or skip;
|
|
127
|
-
2. if
|
|
128
|
-
3. analyze the music with `scripts/analyze_music_beats.py
|
|
129
|
-
4.
|
|
130
|
-
5.
|
|
131
|
-
6. preview and get
|
|
132
|
-
7.
|
|
133
|
+
2. if BGM is needed for beat-sync, generate BGM with `scripts/generate_minimax_music.mjs`;
|
|
134
|
+
3. analyze the music with `scripts/analyze_music_beats.py` only when its dependencies are already available;
|
|
135
|
+
4. if beat analysis dependencies are missing, do not run `pip install`; ask for a beat map, continue with explicit manual cues, or skip beat sync;
|
|
136
|
+
5. schedule transitions and animation against the beat map or manual cues;
|
|
137
|
+
6. generate the visual HTML preview and get page/transition/animation confirmation;
|
|
138
|
+
7. generate narration and subtitles, then reconcile timing;
|
|
139
|
+
8. render and mix final video/audio after final approval.
|
|
133
140
|
|
|
134
141
|
Default mix:
|
|
135
142
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ShotCraft motion pack
|
|
2
|
+
|
|
3
|
+
Use this pack before freehanding animation for the supported ShotCraft cards.
|
|
4
|
+
The goal is to copy the card's actual motion grammar into GSAP/HyperFrames
|
|
5
|
+
instead of merely describing it in prose.
|
|
6
|
+
|
|
7
|
+
| ShotCraft reference | `motion_contract.motion_preset` | Helper |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `slam-entrance-moves` | `slam-entrance` | `slamEntrance` |
|
|
10
|
+
| `shot-transitions` | `whip-transition` | `whipTransition` |
|
|
11
|
+
| `crash-zoom-punch` | `crash-zoom` | `crashZoom` |
|
|
12
|
+
| `bottom-push-stack-wipe` | `push-stack-wipe` | `pushStackWipe` |
|
|
13
|
+
| `dataviz-landscape-open` | `dataviz-landscape-open` | `datavizLandscapeOpen` |
|
|
14
|
+
| `panel-grid-moves` | `panel-grid-reflow` | `panelGridReflow` |
|
|
15
|
+
| `ui-to-brand-morph` | `brand-resolve` | `brandResolve` |
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
|
|
19
|
+
1. Read the selected ShotCraft card README first.
|
|
20
|
+
2. Set the matching `motion_contract.motion_preset` in the shot plan.
|
|
21
|
+
3. Copy only the needed helper from `shotcraft-motion-pack.js` into the
|
|
22
|
+
generated HTML, or include the file as a local reference when the project
|
|
23
|
+
allows that.
|
|
24
|
+
4. Keep the helper on the main paused GSAP timeline; do not run wall-clock
|
|
25
|
+
timers, React, Remotion, or ShotCraft runtime code.
|
|
26
|
+
|
|
27
|
+
Quality bar:
|
|
28
|
+
|
|
29
|
+
- motion starts with a visible camera/geometry event, not a fade;
|
|
30
|
+
- transition covers the cut with a real mask, whip, impact, or spatial travel;
|
|
31
|
+
- dense motion resolves into at least `readable_rest_seconds` of rest;
|
|
32
|
+
- every helper call is tied to a named shot boundary or beat cue.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Copy the needed helpers into a generated HyperFrames HTML file.
|
|
2
|
+
// Requires GSAP and a paused main timeline.
|
|
3
|
+
(function registerShotCraftMotion(global) {
|
|
4
|
+
function gsapRef() {
|
|
5
|
+
if (!global.gsap) throw new Error("GSAP is required for ShotCraft motion helpers");
|
|
6
|
+
return global.gsap;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function slamEntrance(tl, target, cue = 0, opts = {}) {
|
|
10
|
+
const gsap = gsapRef();
|
|
11
|
+
const duration = opts.duration ?? 0.72;
|
|
12
|
+
tl.set(target, { transformPerspective: 900, transformOrigin: "50% 55%" }, cue)
|
|
13
|
+
.fromTo(target, {
|
|
14
|
+
autoAlpha: 0,
|
|
15
|
+
scale: 2.4,
|
|
16
|
+
z: 420,
|
|
17
|
+
rotateX: -18,
|
|
18
|
+
filter: "blur(14px) brightness(1.8)",
|
|
19
|
+
}, {
|
|
20
|
+
autoAlpha: 1,
|
|
21
|
+
scale: 0.92,
|
|
22
|
+
z: 0,
|
|
23
|
+
rotateX: 0,
|
|
24
|
+
filter: "blur(0px) brightness(1)",
|
|
25
|
+
duration: duration * 0.58,
|
|
26
|
+
ease: "expo.out",
|
|
27
|
+
}, cue)
|
|
28
|
+
.to(target, { scale: 1.04, duration: duration * 0.16, ease: "power2.out" }, cue + duration * 0.42)
|
|
29
|
+
.to(target, { scale: 1, duration: duration * 0.26, ease: "back.out(2.4)" }, cue + duration * 0.56);
|
|
30
|
+
if (opts.ring) {
|
|
31
|
+
tl.fromTo(opts.ring, { autoAlpha: 0.9, scale: 0.12 }, {
|
|
32
|
+
autoAlpha: 0,
|
|
33
|
+
scale: 2.8,
|
|
34
|
+
duration: duration * 0.72,
|
|
35
|
+
ease: "expo.out",
|
|
36
|
+
}, cue + 0.04);
|
|
37
|
+
}
|
|
38
|
+
return tl;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function whipTransition(tl, fromTarget, toTarget, cue = 0, opts = {}) {
|
|
42
|
+
const distance = opts.distance ?? 260;
|
|
43
|
+
const blur = opts.blur ?? 18;
|
|
44
|
+
tl.to(fromTarget, {
|
|
45
|
+
x: -distance,
|
|
46
|
+
rotateZ: -3,
|
|
47
|
+
filter: `blur(${blur}px)`,
|
|
48
|
+
autoAlpha: 0,
|
|
49
|
+
duration: 0.2,
|
|
50
|
+
ease: "power4.in",
|
|
51
|
+
}, cue)
|
|
52
|
+
.fromTo(toTarget, {
|
|
53
|
+
x: distance,
|
|
54
|
+
rotateZ: 3,
|
|
55
|
+
filter: `blur(${blur}px)`,
|
|
56
|
+
autoAlpha: 0,
|
|
57
|
+
}, {
|
|
58
|
+
x: 0,
|
|
59
|
+
rotateZ: 0,
|
|
60
|
+
filter: "blur(0px)",
|
|
61
|
+
autoAlpha: 1,
|
|
62
|
+
duration: 0.34,
|
|
63
|
+
ease: "expo.out",
|
|
64
|
+
}, cue + 0.16);
|
|
65
|
+
if (opts.flash) tl.fromTo(opts.flash, { autoAlpha: 0 }, { autoAlpha: 1, yoyo: true, repeat: 1, duration: 0.05 }, cue + 0.14);
|
|
66
|
+
return tl;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function crashZoom(tl, target, cue = 0, opts = {}) {
|
|
70
|
+
tl.fromTo(target, {
|
|
71
|
+
scale: opts.fromScale ?? 0.72,
|
|
72
|
+
filter: "contrast(1.3) saturate(1.25)",
|
|
73
|
+
}, {
|
|
74
|
+
scale: opts.overshoot ?? 1.18,
|
|
75
|
+
duration: 0.16,
|
|
76
|
+
ease: "power4.in",
|
|
77
|
+
}, cue)
|
|
78
|
+
.to(target, { scale: 0.98, duration: 0.12, ease: "power2.out" }, cue + 0.16)
|
|
79
|
+
.to(target, { scale: 1, filter: "contrast(1) saturate(1)", duration: 0.28, ease: "expo.out" }, cue + 0.28);
|
|
80
|
+
if (opts.shock) tl.fromTo(opts.shock, { autoAlpha: 0.85, scale: 0.2 }, { autoAlpha: 0, scale: 2.2, duration: 0.34, ease: "expo.out" }, cue + 0.12);
|
|
81
|
+
return tl;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function pushStackWipe(tl, fromTarget, toTarget, cue = 0, opts = {}) {
|
|
85
|
+
const height = opts.height ?? "112%";
|
|
86
|
+
tl.fromTo(toTarget, { y: height, autoAlpha: 1 }, {
|
|
87
|
+
y: "0%",
|
|
88
|
+
duration: 0.48,
|
|
89
|
+
ease: "power3.inOut",
|
|
90
|
+
}, cue)
|
|
91
|
+
.to(fromTarget, {
|
|
92
|
+
y: "-18%",
|
|
93
|
+
scale: 0.94,
|
|
94
|
+
filter: "blur(8px)",
|
|
95
|
+
autoAlpha: 0,
|
|
96
|
+
duration: 0.42,
|
|
97
|
+
ease: "power3.inOut",
|
|
98
|
+
}, cue + 0.04)
|
|
99
|
+
.fromTo(opts.shadow ?? toTarget, { boxShadow: "0 -40px 80px rgba(0,0,0,0.35)" }, {
|
|
100
|
+
boxShadow: "0 0 0 rgba(0,0,0,0)",
|
|
101
|
+
duration: 0.36,
|
|
102
|
+
ease: "power2.out",
|
|
103
|
+
}, cue + 0.28);
|
|
104
|
+
return tl;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function datavizLandscapeOpen(tl, bars, labels, cue = 0, opts = {}) {
|
|
108
|
+
tl.fromTo(bars, {
|
|
109
|
+
scaleY: 0,
|
|
110
|
+
transformOrigin: "50% 100%",
|
|
111
|
+
}, {
|
|
112
|
+
scaleY: 1,
|
|
113
|
+
duration: opts.duration ?? 0.86,
|
|
114
|
+
stagger: opts.stagger ?? 0.035,
|
|
115
|
+
ease: "expo.out",
|
|
116
|
+
}, cue)
|
|
117
|
+
.fromTo(labels, { y: 18, autoAlpha: 0 }, {
|
|
118
|
+
y: 0,
|
|
119
|
+
autoAlpha: 1,
|
|
120
|
+
duration: 0.34,
|
|
121
|
+
stagger: opts.stagger ?? 0.035,
|
|
122
|
+
ease: "power2.out",
|
|
123
|
+
}, cue + 0.24);
|
|
124
|
+
return tl;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function panelGridReflow(tl, panels, cue = 0, opts = {}) {
|
|
128
|
+
const gsap = gsapRef();
|
|
129
|
+
const panelList = gsap.utils.toArray(panels);
|
|
130
|
+
panelList.forEach((panel, index) => {
|
|
131
|
+
tl.fromTo(panel, {
|
|
132
|
+
xPercent: index % 2 === 0 ? -18 : 18,
|
|
133
|
+
yPercent: index % 3 === 0 ? -12 : 12,
|
|
134
|
+
rotateZ: index % 2 === 0 ? -3 : 3,
|
|
135
|
+
autoAlpha: 0,
|
|
136
|
+
}, {
|
|
137
|
+
xPercent: 0,
|
|
138
|
+
yPercent: 0,
|
|
139
|
+
rotateZ: 0,
|
|
140
|
+
autoAlpha: 1,
|
|
141
|
+
duration: opts.duration ?? 0.58,
|
|
142
|
+
ease: "expo.out",
|
|
143
|
+
}, cue + index * (opts.stagger ?? 0.045));
|
|
144
|
+
});
|
|
145
|
+
return tl;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function brandResolve(tl, tiles, wordmark, cue = 0, opts = {}) {
|
|
149
|
+
tl.to(tiles, {
|
|
150
|
+
x: 0,
|
|
151
|
+
y: 0,
|
|
152
|
+
rotateZ: 0,
|
|
153
|
+
scale: 0.78,
|
|
154
|
+
duration: opts.gatherDuration ?? 0.54,
|
|
155
|
+
stagger: opts.stagger ?? 0.025,
|
|
156
|
+
ease: "power3.inOut",
|
|
157
|
+
}, cue)
|
|
158
|
+
.to(tiles, { autoAlpha: 0, scale: 0.48, duration: 0.2, ease: "power2.in" }, cue + 0.48)
|
|
159
|
+
.fromTo(wordmark, { autoAlpha: 0, scale: 0.86, filter: "blur(10px)" }, {
|
|
160
|
+
autoAlpha: 1,
|
|
161
|
+
scale: 1,
|
|
162
|
+
filter: "blur(0px)",
|
|
163
|
+
duration: 0.42,
|
|
164
|
+
ease: "expo.out",
|
|
165
|
+
}, cue + 0.54);
|
|
166
|
+
return tl;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
global.HyperAnimatorShotCraftMotion = {
|
|
170
|
+
slamEntrance,
|
|
171
|
+
whipTransition,
|
|
172
|
+
crashZoom,
|
|
173
|
+
pushStackWipe,
|
|
174
|
+
datavizLandscapeOpen,
|
|
175
|
+
panelGridReflow,
|
|
176
|
+
brandResolve,
|
|
177
|
+
};
|
|
178
|
+
})(window);
|
|
@@ -193,7 +193,7 @@ function parsePorcelainLine(line) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
function porcelainLines(pathspecs = []) {
|
|
196
|
-
const args = ["status", "--porcelain=v1", "--untracked-files=all"];
|
|
196
|
+
const args = ["-c", "core.quotePath=false", "status", "--porcelain=v1", "--untracked-files=all"];
|
|
197
197
|
|
|
198
198
|
if (pathspecs.length > 0) {
|
|
199
199
|
args.push("--", ...pathspecs);
|
|
@@ -230,7 +230,7 @@ function trackedOrUntrackedMatching(pathspecs) {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
function stagedFiles() {
|
|
233
|
-
return git(["diff", "--cached", "--name-only", "--diff-filter=ACDMR"], { check: true }).stdout
|
|
233
|
+
return git(["-c", "core.quotePath=false", "diff", "--cached", "--name-only", "--diff-filter=ACDMR"], { check: true }).stdout
|
|
234
234
|
.split("\n")
|
|
235
235
|
.map((line) => line.trim())
|
|
236
236
|
.filter(Boolean)
|
|
@@ -5,6 +5,28 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
|
|
6
6
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const skillRoot = dirname(scriptDir);
|
|
8
|
+
const WORKFLOW_STAGES = new Set(["visual_draft", "audio_integrated", "render_ready"]);
|
|
9
|
+
const AUDIO_MODES = new Set(["generated", "user_local_file", "none", "deferred"]);
|
|
10
|
+
const ENERGY_LEVELS = new Set(["quiet", "balanced", "high", "cinematic"]);
|
|
11
|
+
const MOTION_PRESETS = new Set([
|
|
12
|
+
"none",
|
|
13
|
+
"slam-entrance",
|
|
14
|
+
"whip-transition",
|
|
15
|
+
"crash-zoom",
|
|
16
|
+
"push-stack-wipe",
|
|
17
|
+
"dataviz-landscape-open",
|
|
18
|
+
"panel-grid-reflow",
|
|
19
|
+
"brand-resolve",
|
|
20
|
+
]);
|
|
21
|
+
const SHOTCRAFT_MOTION_PRESETS = new Map([
|
|
22
|
+
["slam-entrance-moves", "slam-entrance"],
|
|
23
|
+
["shot-transitions", "whip-transition"],
|
|
24
|
+
["crash-zoom-punch", "crash-zoom"],
|
|
25
|
+
["bottom-push-stack-wipe", "push-stack-wipe"],
|
|
26
|
+
["dataviz-landscape-open", "dataviz-landscape-open"],
|
|
27
|
+
["panel-grid-moves", "panel-grid-reflow"],
|
|
28
|
+
["ui-to-brand-morph", "brand-resolve"],
|
|
29
|
+
]);
|
|
8
30
|
|
|
9
31
|
function isObject(value) {
|
|
10
32
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -16,8 +38,8 @@ function validateAudioTrack(track, label, errors) {
|
|
|
16
38
|
return;
|
|
17
39
|
}
|
|
18
40
|
|
|
19
|
-
if (!
|
|
20
|
-
errors.push(`audio.${label}.mode must be generated, user_local_file, or
|
|
41
|
+
if (!AUDIO_MODES.has(track.mode)) {
|
|
42
|
+
errors.push(`audio.${label}.mode must be generated, user_local_file, none, or deferred`);
|
|
21
43
|
}
|
|
22
44
|
if (track.mode === "generated" && track.provider !== "minimax_cn") {
|
|
23
45
|
errors.push(`audio.${label}.provider must be minimax_cn when mode is generated`);
|
|
@@ -25,8 +47,48 @@ function validateAudioTrack(track, label, errors) {
|
|
|
25
47
|
if (track.mode === "user_local_file" && (typeof track.path !== "string" || !track.path.trim())) {
|
|
26
48
|
errors.push(`audio.${label}.path is required when mode is user_local_file`);
|
|
27
49
|
}
|
|
28
|
-
if (track.mode === "none" && track.provider !== undefined) {
|
|
29
|
-
errors.push(`audio.${label}.provider must be omitted when mode is
|
|
50
|
+
if ((track.mode === "none" || track.mode === "deferred") && track.provider !== undefined) {
|
|
51
|
+
errors.push(`audio.${label}.provider must be omitted when mode is ${track.mode}`);
|
|
52
|
+
}
|
|
53
|
+
if ((track.mode === "none" || track.mode === "deferred") && track.path !== undefined) {
|
|
54
|
+
errors.push(`audio.${label}.path must be omitted when mode is ${track.mode}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function validateMotionContract(contract, label, shotcraftRefs, errors) {
|
|
59
|
+
if (!isObject(contract)) {
|
|
60
|
+
errors.push(`${label}.motion_contract is required`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!MOTION_PRESETS.has(contract.motion_preset)) {
|
|
65
|
+
errors.push(`${label}.motion_contract.motion_preset must be one of ${[...MOTION_PRESETS].join(", ")}`);
|
|
66
|
+
}
|
|
67
|
+
const expectedPresets = shotcraftRefs
|
|
68
|
+
.map((reference) => SHOTCRAFT_MOTION_PRESETS.get(reference))
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
if (expectedPresets.length > 0 && !expectedPresets.includes(contract.motion_preset)) {
|
|
71
|
+
errors.push(`${label}.motion_contract.motion_preset must use ${expectedPresets.join(" or ")} for selected ShotCraft refs`);
|
|
72
|
+
}
|
|
73
|
+
if (!ENERGY_LEVELS.has(contract.energy)) {
|
|
74
|
+
errors.push(`${label}.motion_contract.energy must be quiet, balanced, high, or cinematic`);
|
|
75
|
+
}
|
|
76
|
+
for (const field of ["camera", "transition"]) {
|
|
77
|
+
if (typeof contract[field] !== "string" || !contract[field].trim()) {
|
|
78
|
+
errors.push(`${label}.motion_contract.${field} is required`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!Array.isArray(contract.shotcraft_commitments) || contract.shotcraft_commitments.length === 0) {
|
|
82
|
+
errors.push(`${label}.motion_contract.shotcraft_commitments must contain at least one item`);
|
|
83
|
+
} else {
|
|
84
|
+
contract.shotcraft_commitments.forEach((commitment, index) => {
|
|
85
|
+
if (typeof commitment !== "string" || !commitment.trim()) {
|
|
86
|
+
errors.push(`${label}.motion_contract.shotcraft_commitments[${index}] must be a non-empty string`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (!Number.isFinite(contract.readable_rest_seconds) || contract.readable_rest_seconds < 0) {
|
|
91
|
+
errors.push(`${label}.motion_contract.readable_rest_seconds must be zero or greater`);
|
|
30
92
|
}
|
|
31
93
|
}
|
|
32
94
|
|
|
@@ -35,6 +97,9 @@ function validateShotPlan(plan, catalogs) {
|
|
|
35
97
|
|
|
36
98
|
if (!isObject(plan)) return ["plan must be a JSON object"];
|
|
37
99
|
if (plan.schema_version !== "1.0.0") errors.push("schema_version must be 1.0.0");
|
|
100
|
+
if (!WORKFLOW_STAGES.has(plan.workflow_stage)) {
|
|
101
|
+
errors.push("workflow_stage must be visual_draft, audio_integrated, or render_ready");
|
|
102
|
+
}
|
|
38
103
|
if (!["assemble_existing_catalog_items", "generate_new_hyperframes_html"].includes(plan.generation_mode)) {
|
|
39
104
|
errors.push("generation_mode is invalid");
|
|
40
105
|
}
|
|
@@ -56,6 +121,12 @@ function validateShotPlan(plan, catalogs) {
|
|
|
56
121
|
} else {
|
|
57
122
|
validateAudioTrack(plan.audio.narration, "narration", errors);
|
|
58
123
|
validateAudioTrack(plan.audio.bgm, "bgm", errors);
|
|
124
|
+
if (plan.workflow_stage === "visual_draft" && plan.audio.narration?.mode === "generated") {
|
|
125
|
+
errors.push("audio.narration must not be generated before visual confirmation");
|
|
126
|
+
}
|
|
127
|
+
if (plan.workflow_stage !== "visual_draft" && plan.audio.narration?.mode === "deferred") {
|
|
128
|
+
errors.push("audio.narration.mode cannot stay deferred after visual confirmation");
|
|
129
|
+
}
|
|
59
130
|
}
|
|
60
131
|
|
|
61
132
|
if (!isObject(plan.model_routing)) {
|
|
@@ -102,6 +173,9 @@ function validateShotPlan(plan, catalogs) {
|
|
|
102
173
|
if (!Array.isArray(shot.hyperframes_refs)) {
|
|
103
174
|
errors.push(`${label}.hyperframes_refs must be an array`);
|
|
104
175
|
} else {
|
|
176
|
+
if (shot.hyperframes_refs.length === 0) {
|
|
177
|
+
errors.push(`${label}.hyperframes_refs must contain at least one HyperFrames reference`);
|
|
178
|
+
}
|
|
105
179
|
for (const reference of shot.hyperframes_refs) {
|
|
106
180
|
if (!catalogs.hyperframes.has(reference)) {
|
|
107
181
|
errors.push(`${label} has unknown HyperFrames reference: ${reference}`);
|
|
@@ -111,17 +185,16 @@ function validateShotPlan(plan, catalogs) {
|
|
|
111
185
|
if (!Array.isArray(shot.shotcraft_refs)) {
|
|
112
186
|
errors.push(`${label}.shotcraft_refs must be an array`);
|
|
113
187
|
} else {
|
|
188
|
+
if (shot.shotcraft_refs.length === 0) {
|
|
189
|
+
errors.push(`${label}.shotcraft_refs must contain at least one ShotCraft reference`);
|
|
190
|
+
}
|
|
114
191
|
for (const reference of shot.shotcraft_refs) {
|
|
115
192
|
if (!catalogs.shotcraft.has(reference)) {
|
|
116
193
|
errors.push(`${label} has unknown ShotCraft reference: ${reference}`);
|
|
117
194
|
}
|
|
118
195
|
}
|
|
119
196
|
}
|
|
120
|
-
|
|
121
|
-
&& Array.isArray(shot.shotcraft_refs)
|
|
122
|
-
&& shot.hyperframes_refs.length + shot.shotcraft_refs.length === 0) {
|
|
123
|
-
errors.push(`${label} must reference at least one catalog`);
|
|
124
|
-
}
|
|
197
|
+
validateMotionContract(shot.motion_contract, label, Array.isArray(shot.shotcraft_refs) ? shot.shotcraft_refs : [], errors);
|
|
125
198
|
|
|
126
199
|
if (index === 0 && shot.start_seconds !== 0) {
|
|
127
200
|
errors.push("shots must start at 0 seconds");
|