hyper-animator-codex 0.8.0 → 0.8.3

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 (25) hide show
  1. package/README.md +30 -13
  2. package/bin/hyper-animator-codex.mjs +4 -4
  3. package/lib/install-skill.mjs +49 -20
  4. package/lib/minimax-config.mjs +5 -9
  5. package/package.json +10 -2
  6. package/skills/hyper-animator-codex/SKILL.md +33 -25
  7. package/skills/hyper-animator-codex/agents/openai.yaml +2 -2
  8. package/skills/hyper-animator-codex/contracts/shot-plan.schema.json +113 -7
  9. package/skills/hyper-animator-codex/contracts/workflow-policy.json +21 -3
  10. package/skills/hyper-animator-codex/references/examples/shot-plan-dual-catalog.json +41 -5
  11. package/skills/hyper-animator-codex/references/hyperframes-intent-workflow.md +20 -10
  12. package/skills/hyper-animator-codex/references/minimax-music-workflow.md +5 -7
  13. package/skills/hyper-animator-codex/references/narration-audio-workflow.md +20 -14
  14. package/skills/hyper-animator-codex/references/preview-controls-workflow.md +4 -2
  15. package/skills/hyper-animator-codex/references/shotcraft/motion-pack/README.md +32 -0
  16. package/skills/hyper-animator-codex/references/shotcraft/motion-pack/shotcraft-motion-pack.js +178 -0
  17. package/skills/hyper-animator-codex/scripts/generate_minimax_music.mjs +11 -6
  18. package/skills/hyper-animator-codex/scripts/generate_minimax_tts.mjs +20 -8
  19. package/skills/hyper-animator-codex/scripts/git_checkpoint.mjs +29 -3
  20. package/skills/hyper-animator-codex/scripts/inject_preview_controls.mjs +37 -1
  21. package/skills/hyper-animator-codex/scripts/minimax_runtime_config.mjs +5 -9
  22. package/skills/hyper-animator-codex/scripts/query_shotcraft.mjs +10 -1
  23. package/skills/hyper-animator-codex/scripts/shot_plan_schema_validator.mjs +3 -0
  24. package/skills/hyper-animator-codex/scripts/validate_hyperframes_html.py +122 -36
  25. package/skills/hyper-animator-codex/scripts/validate_shot_plan.mjs +110 -17
@@ -1,10 +1,34 @@
1
1
  #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { dirname, join } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
6
+ import validateSchema, { schemaSha256 } from "./shot_plan_schema_validator.mjs";
5
7
 
6
8
  const scriptDir = dirname(fileURLToPath(import.meta.url));
7
9
  const skillRoot = dirname(scriptDir);
10
+ const WORKFLOW_STAGES = new Set(["visual_draft", "audio_integrated", "render_ready"]);
11
+ const AUDIO_MODES = new Set(["generated", "user_local_file", "none", "deferred"]);
12
+ const ENERGY_LEVELS = new Set(["quiet", "balanced", "high", "cinematic"]);
13
+ const MOTION_PRESETS = new Set([
14
+ "none",
15
+ "slam-entrance",
16
+ "whip-transition",
17
+ "crash-zoom",
18
+ "push-stack-wipe",
19
+ "dataviz-landscape-open",
20
+ "panel-grid-reflow",
21
+ "brand-resolve",
22
+ ]);
23
+ const SHOTCRAFT_MOTION_PRESETS = new Map([
24
+ ["slam-entrance-moves", "slam-entrance"],
25
+ ["shot-transitions", "whip-transition"],
26
+ ["crash-zoom-punch", "crash-zoom"],
27
+ ["bottom-push-stack-wipe", "push-stack-wipe"],
28
+ ["dataviz-landscape-open", "dataviz-landscape-open"],
29
+ ["panel-grid-moves", "panel-grid-reflow"],
30
+ ["ui-to-brand-morph", "brand-resolve"],
31
+ ]);
8
32
 
9
33
  function isObject(value) {
10
34
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -16,8 +40,8 @@ function validateAudioTrack(track, label, errors) {
16
40
  return;
17
41
  }
18
42
 
19
- if (!["generated", "user_local_file", "none"].includes(track.mode)) {
20
- errors.push(`audio.${label}.mode must be generated, user_local_file, or none`);
43
+ if (!AUDIO_MODES.has(track.mode)) {
44
+ errors.push(`audio.${label}.mode must be generated, user_local_file, none, or deferred`);
21
45
  }
22
46
  if (track.mode === "generated" && track.provider !== "minimax_cn") {
23
47
  errors.push(`audio.${label}.provider must be minimax_cn when mode is generated`);
@@ -25,8 +49,51 @@ function validateAudioTrack(track, label, errors) {
25
49
  if (track.mode === "user_local_file" && (typeof track.path !== "string" || !track.path.trim())) {
26
50
  errors.push(`audio.${label}.path is required when mode is user_local_file`);
27
51
  }
28
- if (track.mode === "none" && track.provider !== undefined) {
29
- errors.push(`audio.${label}.provider must be omitted when mode is none`);
52
+ if ((track.mode === "none" || track.mode === "deferred") && track.provider !== undefined) {
53
+ errors.push(`audio.${label}.provider must be omitted when mode is ${track.mode}`);
54
+ }
55
+ if ((track.mode === "none" || track.mode === "deferred") && track.path !== undefined) {
56
+ errors.push(`audio.${label}.path must be omitted when mode is ${track.mode}`);
57
+ }
58
+ }
59
+
60
+ function validateMotionContract(contract, label, shotcraftRefs, errors) {
61
+ if (!isObject(contract)) {
62
+ errors.push(`${label}.motion_contract is required`);
63
+ return;
64
+ }
65
+
66
+ if (!MOTION_PRESETS.has(contract.motion_preset)) {
67
+ errors.push(`${label}.motion_contract.motion_preset must be one of ${[...MOTION_PRESETS].join(", ")}`);
68
+ }
69
+ const expectedPresets = shotcraftRefs
70
+ .map((reference) => SHOTCRAFT_MOTION_PRESETS.get(reference))
71
+ .filter(Boolean);
72
+ if (new Set(expectedPresets).size > 1) {
73
+ errors.push(`${label}.shotcraft_refs require multiple motion presets; separate them into distinct shots`);
74
+ }
75
+ if (expectedPresets.length > 0 && !expectedPresets.includes(contract.motion_preset)) {
76
+ errors.push(`${label}.motion_contract.motion_preset must use ${expectedPresets.join(" or ")} for selected ShotCraft refs`);
77
+ }
78
+ if (!ENERGY_LEVELS.has(contract.energy)) {
79
+ errors.push(`${label}.motion_contract.energy must be quiet, balanced, high, or cinematic`);
80
+ }
81
+ for (const field of ["camera", "transition"]) {
82
+ if (typeof contract[field] !== "string" || !contract[field].trim()) {
83
+ errors.push(`${label}.motion_contract.${field} is required`);
84
+ }
85
+ }
86
+ if (!Array.isArray(contract.shotcraft_commitments) || contract.shotcraft_commitments.length === 0) {
87
+ errors.push(`${label}.motion_contract.shotcraft_commitments must contain at least one item`);
88
+ } else {
89
+ contract.shotcraft_commitments.forEach((commitment, index) => {
90
+ if (typeof commitment !== "string" || !commitment.trim()) {
91
+ errors.push(`${label}.motion_contract.shotcraft_commitments[${index}] must be a non-empty string`);
92
+ }
93
+ });
94
+ }
95
+ if (!Number.isFinite(contract.readable_rest_seconds) || contract.readable_rest_seconds < 0) {
96
+ errors.push(`${label}.motion_contract.readable_rest_seconds must be zero or greater`);
30
97
  }
31
98
  }
32
99
 
@@ -34,7 +101,10 @@ function validateShotPlan(plan, catalogs) {
34
101
  const errors = [];
35
102
 
36
103
  if (!isObject(plan)) return ["plan must be a JSON object"];
37
- if (plan.schema_version !== "1.0.0") errors.push("schema_version must be 1.0.0");
104
+ if (plan.schema_version !== "1.1.0") errors.push("schema_version must be 1.1.0");
105
+ if (!WORKFLOW_STAGES.has(plan.workflow_stage)) {
106
+ errors.push("workflow_stage must be visual_draft, audio_integrated, or render_ready");
107
+ }
38
108
  if (!["assemble_existing_catalog_items", "generate_new_hyperframes_html"].includes(plan.generation_mode)) {
39
109
  errors.push("generation_mode is invalid");
40
110
  }
@@ -56,18 +126,26 @@ function validateShotPlan(plan, catalogs) {
56
126
  } else {
57
127
  validateAudioTrack(plan.audio.narration, "narration", errors);
58
128
  validateAudioTrack(plan.audio.bgm, "bgm", errors);
129
+ if (plan.workflow_stage === "visual_draft" && plan.audio.narration?.mode === "generated") {
130
+ errors.push("audio.narration must not be generated before visual confirmation");
131
+ }
132
+ if (plan.workflow_stage !== "visual_draft" && plan.audio.narration?.mode === "deferred") {
133
+ errors.push("audio.narration.mode cannot stay deferred after visual confirmation");
134
+ }
59
135
  }
60
136
 
61
137
  if (!isObject(plan.model_routing)) {
62
138
  errors.push("model_routing must be an object");
63
139
  } else {
64
- if (plan.model_routing.page_animation_authoring !== "gpt-5.6-sol") {
65
- errors.push("model_routing.page_animation_authoring must be gpt-5.6-sol");
140
+ const authoring = plan.model_routing.page_animation_authoring;
141
+ const planning = plan.model_routing.planning_and_validation;
142
+ if (!isObject(authoring) || authoring.tier !== "high_quality" || !authoring.actual_model) {
143
+ errors.push("model_routing.page_animation_authoring must declare high_quality tier and actual_model");
66
144
  }
67
- if (!plan.model_routing.planning_and_validation) {
68
- errors.push("model_routing.planning_and_validation is required");
69
- } else if (plan.model_routing.planning_and_validation === "gpt-5.6-sol") {
70
- errors.push("model_routing.planning_and_validation must not use gpt-5.6-sol");
145
+ if (!isObject(planning) || planning.tier !== "fast" || !planning.actual_model) {
146
+ errors.push("model_routing.planning_and_validation must declare fast tier and actual_model");
147
+ } else if (authoring?.actual_model === planning.actual_model) {
148
+ errors.push("model_routing.planning_and_validation must use a different, fast model");
71
149
  }
72
150
  }
73
151
 
@@ -102,6 +180,9 @@ function validateShotPlan(plan, catalogs) {
102
180
  if (!Array.isArray(shot.hyperframes_refs)) {
103
181
  errors.push(`${label}.hyperframes_refs must be an array`);
104
182
  } else {
183
+ if (shot.hyperframes_refs.length === 0) {
184
+ errors.push(`${label}.hyperframes_refs must contain at least one HyperFrames reference`);
185
+ }
105
186
  for (const reference of shot.hyperframes_refs) {
106
187
  if (!catalogs.hyperframes.has(reference)) {
107
188
  errors.push(`${label} has unknown HyperFrames reference: ${reference}`);
@@ -111,17 +192,16 @@ function validateShotPlan(plan, catalogs) {
111
192
  if (!Array.isArray(shot.shotcraft_refs)) {
112
193
  errors.push(`${label}.shotcraft_refs must be an array`);
113
194
  } else {
195
+ if (shot.shotcraft_refs.length === 0) {
196
+ errors.push(`${label}.shotcraft_refs must contain at least one ShotCraft reference`);
197
+ }
114
198
  for (const reference of shot.shotcraft_refs) {
115
199
  if (!catalogs.shotcraft.has(reference)) {
116
200
  errors.push(`${label} has unknown ShotCraft reference: ${reference}`);
117
201
  }
118
202
  }
119
203
  }
120
- if (Array.isArray(shot.hyperframes_refs)
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
- }
204
+ validateMotionContract(shot.motion_contract, label, Array.isArray(shot.shotcraft_refs) ? shot.shotcraft_refs : [], errors);
125
205
 
126
206
  if (index === 0 && shot.start_seconds !== 0) {
127
207
  errors.push("shots must start at 0 seconds");
@@ -158,6 +238,14 @@ async function main() {
158
238
  return;
159
239
  }
160
240
  const plan = JSON.parse(await readFile(path, "utf8"));
241
+ const schemaText = await readFile(
242
+ join(skillRoot, "contracts", "shot-plan.schema.json"),
243
+ "utf8",
244
+ );
245
+ const actualSchemaSha256 = createHash("sha256").update(schemaText).digest("hex");
246
+ if (actualSchemaSha256 !== schemaSha256) {
247
+ throw new Error("shot-plan schema validator is stale; run npm run build:contracts");
248
+ }
161
249
  const hyperframesCatalog = JSON.parse(await readFile(
162
250
  join(skillRoot, "references", "hyperframes-catalog-map.json"),
163
251
  "utf8",
@@ -173,7 +261,12 @@ async function main() {
173
261
  ...card.styles.map((style) => style.key),
174
262
  ])),
175
263
  };
176
- const errors = validateShotPlan(plan, catalogs);
264
+ const schemaErrors = validateSchema(plan)
265
+ ? []
266
+ : validateSchema.errors.map((error) => (
267
+ `${error.instancePath || "/"} ${error.message}${error.params?.additionalProperty ? `: ${error.params.additionalProperty}` : ""}`
268
+ ));
269
+ const errors = [...schemaErrors, ...validateShotPlan(plan, catalogs)];
177
270
  if (errors.length > 0) {
178
271
  output({ ok: false, path, errors }, 1);
179
272
  return;