@vibeframe/cli 0.108.0 → 0.108.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.
|
@@ -85,6 +85,25 @@ export declare function isEpsilonOverlapFinding(finding: LintFinding): boolean;
|
|
|
85
85
|
* findings pass through unchanged.
|
|
86
86
|
*/
|
|
87
87
|
export declare function filterSubCompFalsePositives(findings: LintFinding[], isSubComposition: boolean): LintFinding[];
|
|
88
|
+
/**
|
|
89
|
+
* VibeFrame-side finding: inner phase clips inside a sub-composition.
|
|
90
|
+
*
|
|
91
|
+
* The producer's screenshot render mode does NOT toggle the visibility of
|
|
92
|
+
* `.clip` elements INSIDE a sub-composition by their `data-start` window —
|
|
93
|
+
* a scene split into clip phases (0-10s, 10-26s) renders BOTH phases
|
|
94
|
+
* simultaneously and text stacks on top of itself. Root index.html clips
|
|
95
|
+
* are unaffected (their windows work); this rule only applies to
|
|
96
|
+
* sub-composition internals. Phase changes must instead be driven by GSAP
|
|
97
|
+
* (animate the old phase out with autoAlpha, the new one in) inside
|
|
98
|
+
* full-window clips.
|
|
99
|
+
*/
|
|
100
|
+
export declare function findInternalPhaseClipFindings(html: string): LintFinding[];
|
|
101
|
+
/**
|
|
102
|
+
* Append VibeFrame-side findings for sub-composition HTML. Call at every
|
|
103
|
+
* lint site (batch composer, scene_submit, scene repair, project lint) so
|
|
104
|
+
* all authoring paths share the exact same contract.
|
|
105
|
+
*/
|
|
106
|
+
export declare function withVibeframeSubCompFindings(findings: LintFinding[], html: string, isSubComposition: boolean): LintFinding[];
|
|
88
107
|
/**
|
|
89
108
|
* Apply mechanical `--fix` rewrites for known-safe finding codes. Returns the
|
|
90
109
|
* (possibly unchanged) HTML and the list of codes that were fixed at least
|
|
@@ -12,6 +12,8 @@ export interface StoryboardValidationResult {
|
|
|
12
12
|
beats: Beat[];
|
|
13
13
|
issues: StoryboardValidationIssue[];
|
|
14
14
|
}
|
|
15
|
+
/** Beats beyond this render as static, overstuffed scenes. */
|
|
16
|
+
export declare const MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
15
17
|
export declare function validateStoryboardMarkdown(markdown: string): StoryboardValidationResult;
|
|
16
18
|
export declare function getStoryboardBeat(markdown: string, beatId: string): Beat | null;
|
|
17
19
|
export declare function setStoryboardCue(markdown: string, opts: {
|
package/dist/index.js
CHANGED
|
@@ -26662,6 +26662,8 @@ imageProvider: openai
|
|
|
26662
26662
|
|
|
26663
26663
|
Edit these beats before running \`vibe build\`. Each beat starts with
|
|
26664
26664
|
YAML cues that drive narration, backdrop generation, and timing.
|
|
26665
|
+
Pacing: keep beats 6-15 seconds (split longer ones); a 90s video should
|
|
26666
|
+
have 6-8 beats, not 3 long ones.
|
|
26665
26667
|
|
|
26666
26668
|
## Beat hook \u2014 Hook
|
|
26667
26669
|
|
|
@@ -27778,6 +27780,29 @@ function filterSubCompFalsePositives(findings, isSubComposition) {
|
|
|
27778
27780
|
if (!isSubComposition) return noEpsilon;
|
|
27779
27781
|
return noEpsilon.filter((f) => !SUB_COMP_FALSE_POSITIVES.has(f.code));
|
|
27780
27782
|
}
|
|
27783
|
+
function findInternalPhaseClipFindings(html) {
|
|
27784
|
+
const findings = [];
|
|
27785
|
+
for (const tagMatch of html.matchAll(/<[a-z][a-z0-9-]*\s[^>]*>/gi)) {
|
|
27786
|
+
const tag = tagMatch[0];
|
|
27787
|
+
if (!/class="[^"]*\bclip\b[^"]*"/i.test(tag)) continue;
|
|
27788
|
+
const startAttr = tag.match(/data-start="([^"]+)"/i);
|
|
27789
|
+
if (!startAttr) continue;
|
|
27790
|
+
const start = Number(startAttr[1]);
|
|
27791
|
+
if (!Number.isFinite(start) || start === 0) continue;
|
|
27792
|
+
findings.push({
|
|
27793
|
+
code: "internal_phase_clip_unsupported",
|
|
27794
|
+
severity: "error",
|
|
27795
|
+
message: `Inner .clip has data-start="${startAttr[1]}". The renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render ALL phases at once (overlapping text).`,
|
|
27796
|
+
fixHint: 'Give every inner .clip data-start="0" spanning the full beat, and drive phase changes with GSAP autoAlpha instead (animate the previous phase out, the next one in).',
|
|
27797
|
+
snippet: tag.slice(0, 120)
|
|
27798
|
+
});
|
|
27799
|
+
}
|
|
27800
|
+
return findings;
|
|
27801
|
+
}
|
|
27802
|
+
function withVibeframeSubCompFindings(findings, html, isSubComposition) {
|
|
27803
|
+
if (!isSubComposition) return findings;
|
|
27804
|
+
return [...findings, ...findInternalPhaseClipFindings(html)];
|
|
27805
|
+
}
|
|
27781
27806
|
function applyMechanicalFixes(html, findings) {
|
|
27782
27807
|
const fixedCodes = /* @__PURE__ */ new Set();
|
|
27783
27808
|
let updated = html;
|
|
@@ -27823,7 +27848,11 @@ async function runProjectLint(opts) {
|
|
|
27823
27848
|
source: "projectDir"
|
|
27824
27849
|
};
|
|
27825
27850
|
const raw = runHyperframeLint(prepared);
|
|
27826
|
-
const findings =
|
|
27851
|
+
const findings = withVibeframeSubCompFindings(
|
|
27852
|
+
filterSubCompFalsePositives(raw.findings, isSub),
|
|
27853
|
+
html,
|
|
27854
|
+
isSub
|
|
27855
|
+
);
|
|
27827
27856
|
if (opts.fix && findings.length > 0) {
|
|
27828
27857
|
const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
|
|
27829
27858
|
if (fixedCodes.length > 0) {
|
|
@@ -27833,7 +27862,11 @@ async function runProjectLint(opts) {
|
|
|
27833
27862
|
files.push({
|
|
27834
27863
|
file: rel,
|
|
27835
27864
|
isSubComposition: isSub,
|
|
27836
|
-
findings:
|
|
27865
|
+
findings: withVibeframeSubCompFindings(
|
|
27866
|
+
filterSubCompFalsePositives(reLinted.findings, isSub),
|
|
27867
|
+
nextHtml,
|
|
27868
|
+
isSub
|
|
27869
|
+
)
|
|
27837
27870
|
});
|
|
27838
27871
|
continue;
|
|
27839
27872
|
}
|
|
@@ -29713,8 +29746,14 @@ ${finalDurationBullet}
|
|
|
29713
29746
|
Animate the backdrop/media plane instead; let text enter briefly, then hold
|
|
29714
29747
|
still at its final CSS position. Continuous transforms on text ancestors can
|
|
29715
29748
|
create subpixel shimmer in screenshot-captured renders.
|
|
29716
|
-
-
|
|
29717
|
-
\`data-
|
|
29749
|
+
- Inner \`.clip\` elements carry \`data-start\`, \`data-duration\`,
|
|
29750
|
+
\`data-track-index\` \u2014 but **every inner clip MUST be full-window:
|
|
29751
|
+
\`data-start="0"\` spanning the whole beat.** The renderer does NOT toggle
|
|
29752
|
+
internal clip visibility inside sub-compositions, so "phase clips" with a
|
|
29753
|
+
non-zero \`data-start\` render ALL phases at once and text stacks on top of
|
|
29754
|
+
itself. For multi-phase beats, drive phase changes with GSAP \`autoAlpha\`
|
|
29755
|
+
inside full-window clips: animate phase A out (\`autoAlpha: 0\`), then
|
|
29756
|
+
phase B in, on the same timeline.
|
|
29718
29757
|
- If \`assets/backdrop-${ctx.beat.id}.png\` exists, use that local file as the
|
|
29719
29758
|
full-frame visual backdrop. The exact path string is
|
|
29720
29759
|
\`assets/backdrop-${ctx.beat.id}.png\`; do NOT prefix it with \`../\` or \`./\`
|
|
@@ -29728,7 +29767,9 @@ ${finalDurationBullet}
|
|
|
29728
29767
|
narration.
|
|
29729
29768
|
- Keep a meaningful visual progression across the full beat duration: line
|
|
29730
29769
|
tracing, gentle camera drift, staged labels, value cards, or parallax. Avoid
|
|
29731
|
-
a completed static frame holding silently for most of the beat.
|
|
29770
|
+
a completed static frame holding silently for most of the beat. For longer
|
|
29771
|
+
beats, progress through content phases with GSAP autoAlpha (old phase out,
|
|
29772
|
+
new phase in) \u2014 never with inner clip windows.
|
|
29732
29773
|
- Do not import external font or image URLs. Use the project DESIGN.md font
|
|
29733
29774
|
choice when explicit; otherwise use \`Inter\`, which is available in the
|
|
29734
29775
|
deterministic render font map.
|
|
@@ -29903,7 +29944,11 @@ function lintBeatHtml(html, beatId) {
|
|
|
29903
29944
|
source: "projectDir"
|
|
29904
29945
|
};
|
|
29905
29946
|
const raw = runHyperframeLint2(prepared);
|
|
29906
|
-
const findings =
|
|
29947
|
+
const findings = withVibeframeSubCompFindings(
|
|
29948
|
+
filterSubCompFalsePositives(raw.findings, true),
|
|
29949
|
+
html,
|
|
29950
|
+
true
|
|
29951
|
+
);
|
|
29907
29952
|
return {
|
|
29908
29953
|
errorCount: findings.filter((f) => f.severity === "error").length,
|
|
29909
29954
|
warningCount: findings.filter((f) => f.severity === "warning").length,
|
|
@@ -30338,7 +30383,8 @@ function buildInstructions(args) {
|
|
|
30338
30383
|
lines.push(`2. Read \`DESIGN.md\` for project-specific palette, typography, motion signature.`);
|
|
30339
30384
|
lines.push(`3. For each beat in the \`beats\` array below, author HTML at \`outputPath\` matching the \`userPrompt\`. The beat \`body\` carries the narrative + visual + animation intent; \`cues\` carries machine-readable per-beat overrides (narration, duration, backdrop, voice).`);
|
|
30340
30385
|
lines.push(`3b. Use each beat's \`finalDurationSec\` (narration-synced) for \`data-duration\` and timeline anchors when present \u2014 NOT the storyboard \`duration\`, which is only the minimum. Scenes composed at the storyboard duration end early and render black tails.`);
|
|
30341
|
-
lines.push(`3c.
|
|
30386
|
+
lines.push(`3c. Never give inner \`.clip\` elements a non-zero \`data-start\` \u2014 the renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render all phases at once (overlapping text). Use full-window clips and GSAP autoAlpha phase transitions instead. Also keep beats 6-15s; split anything longer in the storyboard first.`);
|
|
30387
|
+
lines.push(`3d. If your environment cannot write files (e.g. Claude Desktop / MCP-only hosts), author each beat's HTML and submit it with the \`scene_submit\` tool (beat id + html). It validates with the same Hyperframes lint and writes the file for you; on lint errors it returns the findings without writing \u2014 fix and resubmit.`);
|
|
30342
30388
|
if (args.beatCount > 1) {
|
|
30343
30389
|
lines.push(`4. After authoring all ${args.beatCount} beat(s), run \`vibe scene lint --fix\` to validate. Fix any remaining errors by editing the HTML directly.`);
|
|
30344
30390
|
} else if (args.filtered) {
|
|
@@ -30780,7 +30826,11 @@ function lintHtml(html, rel, isSub) {
|
|
|
30780
30826
|
source: "projectDir"
|
|
30781
30827
|
};
|
|
30782
30828
|
const raw = runHyperframeLint3(prepared);
|
|
30783
|
-
return
|
|
30829
|
+
return withVibeframeSubCompFindings(
|
|
30830
|
+
filterSubCompFalsePositives(raw.findings, isSub),
|
|
30831
|
+
html,
|
|
30832
|
+
isSub
|
|
30833
|
+
);
|
|
30784
30834
|
}
|
|
30785
30835
|
function lintFilesToIssues(files) {
|
|
30786
30836
|
const issues = [];
|
|
@@ -44130,6 +44180,16 @@ function validateStoryboardMarkdown(markdown) {
|
|
|
44130
44180
|
}
|
|
44131
44181
|
}
|
|
44132
44182
|
}
|
|
44183
|
+
for (const beat of parsed.beats) {
|
|
44184
|
+
if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
|
|
44185
|
+
issues.push({
|
|
44186
|
+
severity: "warning",
|
|
44187
|
+
code: "BEAT_DURATION_TOO_LONG",
|
|
44188
|
+
beatId: beat.id,
|
|
44189
|
+
message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
|
|
44190
|
+
});
|
|
44191
|
+
}
|
|
44192
|
+
}
|
|
44133
44193
|
return {
|
|
44134
44194
|
ok: !issues.some((i) => i.severity === "error"),
|
|
44135
44195
|
beats: parsed.beats,
|
|
@@ -44228,7 +44288,7 @@ function readLeadingCueBlock(body) {
|
|
|
44228
44288
|
return { full: match[0], error: error instanceof Error ? error.message : String(error) };
|
|
44229
44289
|
}
|
|
44230
44290
|
}
|
|
44231
|
-
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS;
|
|
44291
|
+
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
|
|
44232
44292
|
var init_storyboard_edit = __esm({
|
|
44233
44293
|
"src/commands/_shared/storyboard-edit.ts"() {
|
|
44234
44294
|
"use strict";
|
|
@@ -44248,6 +44308,7 @@ var init_storyboard_edit = __esm({
|
|
|
44248
44308
|
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
44249
44309
|
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
44250
44310
|
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
44311
|
+
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
44251
44312
|
}
|
|
44252
44313
|
});
|
|
44253
44314
|
|
|
@@ -59092,7 +59153,8 @@ function revisionMessages(opts) {
|
|
|
59092
59153
|
"The JSON object must have: storyboardMd string, summary string, changedBeats string[], warnings string[].",
|
|
59093
59154
|
"Preserve existing frontmatter, beat ids, cue YAML keys, and useful prose unless the user asks to change structure.",
|
|
59094
59155
|
"Keep cue YAML valid. Allowed cue keys are duration, narration, backdrop, video, motion, voice, music, asset.",
|
|
59095
|
-
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target."
|
|
59156
|
+
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target.",
|
|
59157
|
+
"Aim for 6-15 seconds per beat and never exceed ~15s \u2014 long beats render as static, overstuffed scenes. A 90-second video should have 6-8 beats."
|
|
59096
59158
|
].join("\n");
|
|
59097
59159
|
const repairBlock = opts.invalidStoryboard ? [
|
|
59098
59160
|
"The previous revision failed validation. Repair it.",
|
|
@@ -72828,7 +72890,7 @@ init_source();
|
|
|
72828
72890
|
// package.json
|
|
72829
72891
|
var package_default = {
|
|
72830
72892
|
name: "@vibeframe/cli",
|
|
72831
|
-
version: "0.108.
|
|
72893
|
+
version: "0.108.1",
|
|
72832
72894
|
description: "VibeFrame CLI - agentic video workflow runtime",
|
|
72833
72895
|
private: false,
|
|
72834
72896
|
license: "MIT",
|
|
@@ -81287,6 +81349,9 @@ providers:
|
|
|
81287
81349
|
|
|
81288
81350
|
# ${product} - Storyboard
|
|
81289
81351
|
|
|
81352
|
+
Pacing: keep beats 6-15 seconds. Longer beats render as static, overstuffed
|
|
81353
|
+
scenes \u2014 split them instead.
|
|
81354
|
+
|
|
81290
81355
|
Brief: ${brief}
|
|
81291
81356
|
|
|
81292
81357
|
${beats.map(renderBeatDraft).join("\n\n")}
|
|
@@ -25266,6 +25266,8 @@ imageProvider: openai
|
|
|
25266
25266
|
|
|
25267
25267
|
Edit these beats before running \`vibe build\`. Each beat starts with
|
|
25268
25268
|
YAML cues that drive narration, backdrop generation, and timing.
|
|
25269
|
+
Pacing: keep beats 6-15 seconds (split longer ones); a 90s video should
|
|
25270
|
+
have 6-8 beats, not 3 long ones.
|
|
25269
25271
|
|
|
25270
25272
|
## Beat hook \u2014 Hook
|
|
25271
25273
|
|
|
@@ -28829,6 +28831,29 @@ function filterSubCompFalsePositives(findings, isSubComposition) {
|
|
|
28829
28831
|
if (!isSubComposition) return noEpsilon;
|
|
28830
28832
|
return noEpsilon.filter((f) => !SUB_COMP_FALSE_POSITIVES.has(f.code));
|
|
28831
28833
|
}
|
|
28834
|
+
function findInternalPhaseClipFindings(html) {
|
|
28835
|
+
const findings = [];
|
|
28836
|
+
for (const tagMatch of html.matchAll(/<[a-z][a-z0-9-]*\s[^>]*>/gi)) {
|
|
28837
|
+
const tag = tagMatch[0];
|
|
28838
|
+
if (!/class="[^"]*\bclip\b[^"]*"/i.test(tag)) continue;
|
|
28839
|
+
const startAttr = tag.match(/data-start="([^"]+)"/i);
|
|
28840
|
+
if (!startAttr) continue;
|
|
28841
|
+
const start = Number(startAttr[1]);
|
|
28842
|
+
if (!Number.isFinite(start) || start === 0) continue;
|
|
28843
|
+
findings.push({
|
|
28844
|
+
code: "internal_phase_clip_unsupported",
|
|
28845
|
+
severity: "error",
|
|
28846
|
+
message: `Inner .clip has data-start="${startAttr[1]}". The renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render ALL phases at once (overlapping text).`,
|
|
28847
|
+
fixHint: 'Give every inner .clip data-start="0" spanning the full beat, and drive phase changes with GSAP autoAlpha instead (animate the previous phase out, the next one in).',
|
|
28848
|
+
snippet: tag.slice(0, 120)
|
|
28849
|
+
});
|
|
28850
|
+
}
|
|
28851
|
+
return findings;
|
|
28852
|
+
}
|
|
28853
|
+
function withVibeframeSubCompFindings(findings, html, isSubComposition) {
|
|
28854
|
+
if (!isSubComposition) return findings;
|
|
28855
|
+
return [...findings, ...findInternalPhaseClipFindings(html)];
|
|
28856
|
+
}
|
|
28832
28857
|
function applyMechanicalFixes(html, findings) {
|
|
28833
28858
|
const fixedCodes = /* @__PURE__ */ new Set();
|
|
28834
28859
|
let updated = html;
|
|
@@ -28874,7 +28899,11 @@ async function runProjectLint(opts) {
|
|
|
28874
28899
|
source: "projectDir"
|
|
28875
28900
|
};
|
|
28876
28901
|
const raw = runHyperframeLint(prepared);
|
|
28877
|
-
const findings =
|
|
28902
|
+
const findings = withVibeframeSubCompFindings(
|
|
28903
|
+
filterSubCompFalsePositives(raw.findings, isSub),
|
|
28904
|
+
html,
|
|
28905
|
+
isSub
|
|
28906
|
+
);
|
|
28878
28907
|
if (opts.fix && findings.length > 0) {
|
|
28879
28908
|
const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
|
|
28880
28909
|
if (fixedCodes.length > 0) {
|
|
@@ -28884,7 +28913,11 @@ async function runProjectLint(opts) {
|
|
|
28884
28913
|
files.push({
|
|
28885
28914
|
file: rel,
|
|
28886
28915
|
isSubComposition: isSub,
|
|
28887
|
-
findings:
|
|
28916
|
+
findings: withVibeframeSubCompFindings(
|
|
28917
|
+
filterSubCompFalsePositives(reLinted.findings, isSub),
|
|
28918
|
+
nextHtml,
|
|
28919
|
+
isSub
|
|
28920
|
+
)
|
|
28888
28921
|
});
|
|
28889
28922
|
continue;
|
|
28890
28923
|
}
|
|
@@ -30419,8 +30452,14 @@ ${finalDurationBullet}
|
|
|
30419
30452
|
Animate the backdrop/media plane instead; let text enter briefly, then hold
|
|
30420
30453
|
still at its final CSS position. Continuous transforms on text ancestors can
|
|
30421
30454
|
create subpixel shimmer in screenshot-captured renders.
|
|
30422
|
-
-
|
|
30423
|
-
\`data-
|
|
30455
|
+
- Inner \`.clip\` elements carry \`data-start\`, \`data-duration\`,
|
|
30456
|
+
\`data-track-index\` \u2014 but **every inner clip MUST be full-window:
|
|
30457
|
+
\`data-start="0"\` spanning the whole beat.** The renderer does NOT toggle
|
|
30458
|
+
internal clip visibility inside sub-compositions, so "phase clips" with a
|
|
30459
|
+
non-zero \`data-start\` render ALL phases at once and text stacks on top of
|
|
30460
|
+
itself. For multi-phase beats, drive phase changes with GSAP \`autoAlpha\`
|
|
30461
|
+
inside full-window clips: animate phase A out (\`autoAlpha: 0\`), then
|
|
30462
|
+
phase B in, on the same timeline.
|
|
30424
30463
|
- If \`assets/backdrop-${ctx.beat.id}.png\` exists, use that local file as the
|
|
30425
30464
|
full-frame visual backdrop. The exact path string is
|
|
30426
30465
|
\`assets/backdrop-${ctx.beat.id}.png\`; do NOT prefix it with \`../\` or \`./\`
|
|
@@ -30434,7 +30473,9 @@ ${finalDurationBullet}
|
|
|
30434
30473
|
narration.
|
|
30435
30474
|
- Keep a meaningful visual progression across the full beat duration: line
|
|
30436
30475
|
tracing, gentle camera drift, staged labels, value cards, or parallax. Avoid
|
|
30437
|
-
a completed static frame holding silently for most of the beat.
|
|
30476
|
+
a completed static frame holding silently for most of the beat. For longer
|
|
30477
|
+
beats, progress through content phases with GSAP autoAlpha (old phase out,
|
|
30478
|
+
new phase in) \u2014 never with inner clip windows.
|
|
30438
30479
|
- Do not import external font or image URLs. Use the project DESIGN.md font
|
|
30439
30480
|
choice when explicit; otherwise use \`Inter\`, which is available in the
|
|
30440
30481
|
deterministic render font map.
|
|
@@ -30609,7 +30650,11 @@ function lintBeatHtml(html, beatId) {
|
|
|
30609
30650
|
source: "projectDir"
|
|
30610
30651
|
};
|
|
30611
30652
|
const raw = runHyperframeLint2(prepared);
|
|
30612
|
-
const findings =
|
|
30653
|
+
const findings = withVibeframeSubCompFindings(
|
|
30654
|
+
filterSubCompFalsePositives(raw.findings, true),
|
|
30655
|
+
html,
|
|
30656
|
+
true
|
|
30657
|
+
);
|
|
30613
30658
|
return {
|
|
30614
30659
|
errorCount: findings.filter((f) => f.severity === "error").length,
|
|
30615
30660
|
warningCount: findings.filter((f) => f.severity === "warning").length,
|
|
@@ -31044,7 +31089,8 @@ function buildInstructions(args) {
|
|
|
31044
31089
|
lines.push(`2. Read \`DESIGN.md\` for project-specific palette, typography, motion signature.`);
|
|
31045
31090
|
lines.push(`3. For each beat in the \`beats\` array below, author HTML at \`outputPath\` matching the \`userPrompt\`. The beat \`body\` carries the narrative + visual + animation intent; \`cues\` carries machine-readable per-beat overrides (narration, duration, backdrop, voice).`);
|
|
31046
31091
|
lines.push(`3b. Use each beat's \`finalDurationSec\` (narration-synced) for \`data-duration\` and timeline anchors when present \u2014 NOT the storyboard \`duration\`, which is only the minimum. Scenes composed at the storyboard duration end early and render black tails.`);
|
|
31047
|
-
lines.push(`3c.
|
|
31092
|
+
lines.push(`3c. Never give inner \`.clip\` elements a non-zero \`data-start\` \u2014 the renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render all phases at once (overlapping text). Use full-window clips and GSAP autoAlpha phase transitions instead. Also keep beats 6-15s; split anything longer in the storyboard first.`);
|
|
31093
|
+
lines.push(`3d. If your environment cannot write files (e.g. Claude Desktop / MCP-only hosts), author each beat's HTML and submit it with the \`scene_submit\` tool (beat id + html). It validates with the same Hyperframes lint and writes the file for you; on lint errors it returns the findings without writing \u2014 fix and resubmit.`);
|
|
31048
31094
|
if (args.beatCount > 1) {
|
|
31049
31095
|
lines.push(`4. After authoring all ${args.beatCount} beat(s), run \`vibe scene lint --fix\` to validate. Fix any remaining errors by editing the HTML directly.`);
|
|
31050
31096
|
} else if (args.filtered) {
|
|
@@ -31367,7 +31413,11 @@ function lintHtml(html, rel, isSub) {
|
|
|
31367
31413
|
source: "projectDir"
|
|
31368
31414
|
};
|
|
31369
31415
|
const raw = runHyperframeLint3(prepared);
|
|
31370
|
-
return
|
|
31416
|
+
return withVibeframeSubCompFindings(
|
|
31417
|
+
filterSubCompFalsePositives(raw.findings, isSub),
|
|
31418
|
+
html,
|
|
31419
|
+
isSub
|
|
31420
|
+
);
|
|
31371
31421
|
}
|
|
31372
31422
|
function lintFilesToIssues(files) {
|
|
31373
31423
|
const issues = [];
|
|
@@ -32440,6 +32490,16 @@ function validateStoryboardMarkdown(markdown) {
|
|
|
32440
32490
|
}
|
|
32441
32491
|
}
|
|
32442
32492
|
}
|
|
32493
|
+
for (const beat of parsed.beats) {
|
|
32494
|
+
if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
|
|
32495
|
+
issues.push({
|
|
32496
|
+
severity: "warning",
|
|
32497
|
+
code: "BEAT_DURATION_TOO_LONG",
|
|
32498
|
+
beatId: beat.id,
|
|
32499
|
+
message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
|
|
32500
|
+
});
|
|
32501
|
+
}
|
|
32502
|
+
}
|
|
32443
32503
|
return {
|
|
32444
32504
|
ok: !issues.some((i) => i.severity === "error"),
|
|
32445
32505
|
beats: parsed.beats,
|
|
@@ -32538,7 +32598,7 @@ function readLeadingCueBlock(body) {
|
|
|
32538
32598
|
return { full: match[0], error: error instanceof Error ? error.message : String(error) };
|
|
32539
32599
|
}
|
|
32540
32600
|
}
|
|
32541
|
-
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS;
|
|
32601
|
+
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
|
|
32542
32602
|
var init_storyboard_edit = __esm({
|
|
32543
32603
|
"src/commands/_shared/storyboard-edit.ts"() {
|
|
32544
32604
|
"use strict";
|
|
@@ -32558,6 +32618,7 @@ var init_storyboard_edit = __esm({
|
|
|
32558
32618
|
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
32559
32619
|
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
32560
32620
|
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
32621
|
+
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
32561
32622
|
}
|
|
32562
32623
|
});
|
|
32563
32624
|
|
|
@@ -56751,7 +56812,8 @@ function revisionMessages(opts) {
|
|
|
56751
56812
|
"The JSON object must have: storyboardMd string, summary string, changedBeats string[], warnings string[].",
|
|
56752
56813
|
"Preserve existing frontmatter, beat ids, cue YAML keys, and useful prose unless the user asks to change structure.",
|
|
56753
56814
|
"Keep cue YAML valid. Allowed cue keys are duration, narration, backdrop, video, motion, voice, music, asset.",
|
|
56754
|
-
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target."
|
|
56815
|
+
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target.",
|
|
56816
|
+
"Aim for 6-15 seconds per beat and never exceed ~15s \u2014 long beats render as static, overstuffed scenes. A 90-second video should have 6-8 beats."
|
|
56755
56817
|
].join("\n");
|
|
56756
56818
|
const repairBlock = opts.invalidStoryboard ? [
|
|
56757
56819
|
"The previous revision failed validation. Repair it.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/cli",
|
|
3
|
-
"version": "0.108.
|
|
3
|
+
"version": "0.108.1",
|
|
4
4
|
"description": "VibeFrame CLI - agentic video workflow runtime",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"tsx": "^4.21.0",
|
|
84
84
|
"typescript": "^5.3.3",
|
|
85
85
|
"vitest": "^1.2.2",
|
|
86
|
-
"@vibeframe/ai-providers": "0.108.
|
|
87
|
-
"@vibeframe/core": "0.108.
|
|
86
|
+
"@vibeframe/ai-providers": "0.108.1",
|
|
87
|
+
"@vibeframe/core": "0.108.1"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=20"
|