Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9df879e
add agents guidance pytest path and skill contract guard
DonIsmaelito Sep 2, 2026
1312518
add plain language comments to helpers and tests
DonIsmaelito Sep 2, 2026
b27cd4f
add plain language comments to the original render helper
DonIsmaelito Sep 2, 2026
70e6aa1
add comment convention guard test
DonIsmaelito Sep 2, 2026
f4bf4b4
add plain language comments to the orientation test
DonIsmaelito Sep 2, 2026
2aca4bb
trim internal harness guidance out of the agent rules
DonIsmaelito Sep 2, 2026
54199c0
use module docstrings instead of hash headers and guard that shape
DonIsmaelito Sep 2, 2026
4f8ab2d
derive comment guard definitions from the ast and check bare helper n…
DonIsmaelito Sep 2, 2026
b48e603
require comment text above a definition instead of a bare hash
DonIsmaelito Sep 2, 2026
8eb96e9
add manim teaching assets semantic chapters and preview tooling
DonIsmaelito Sep 2, 2026
d376201
add plain language comments to manim teaching assets and scripts
DonIsmaelito Sep 2, 2026
3e5ecc5
add measured scope and legibility limits to the explainer contract
DonIsmaelito Sep 2, 2026
decab15
convert file headers to module docstrings
DonIsmaelito Sep 2, 2026
0ab716b
mention original explainers and the manim extra in the readme and ins…
DonIsmaelito Sep 2, 2026
4919783
export easing names from the concept explainer asset keep swapped cel…
DonIsmaelito Sep 2, 2026
8502d15
isolate chapter preview attempts and expose render quality controls
DonIsmaelito Sep 17, 2026
010fb8c
export measured layout bounds and document preview review limits
DonIsmaelito Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# video-use repository context

video-use is a public, conversation-driven video production framework. Changes
made here may be packaged into the skill and reused by people with different
machines, media, workflows, providers, brands, and output goals. Treat the
repository as a general product, never as one person's customized clone.

## Product principles

- The delivered video is the product. Prioritize visual and audio quality,
editorial judgment, factual correctness, synchronization, pacing, and
production reliability.
- Design reusable contracts and capabilities. Do not hardcode personal paths,
credentials, account ids, prompts, brands, preferences, or assumptions
about one project.
- Keep provider-specific behavior behind narrow boundaries. Core EDL validation,
rendering, reframing, and QC must remain usable from the command line without
any optional client or remote runner.
- Preserve backwards compatibility when practical. If a format must change,
provide a clear migration path and reject unsupported input with an actionable
error.
- Never silently downgrade a requested feature. A missing source, track, model,
codec, or dependency should fail before expensive work begins and explain what
is required.
- Defaults should be safe and broadly useful, while explicit project or user
requirements always win.
- Keep credentials out of source, logs, fixtures, prompts, and generated
artifacts. Configuration belongs in environment variables or provider secret
stores.

## Architecture boundaries

- `SKILL.md` defines the agent workflow and public editing contract.
- `helpers/` contains provider-independent production tools and validation.

@cubic-dev-ai cubic-dev-ai Bot Sep 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The provider-independent helpers/ contract contradicts the existing ElevenLabs-specific transcription helpers. Either move provider integrations behind an explicit adapter boundary or narrow this statement so contributors do not place or assume provider-specific code is provider-independent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At AGENTS.md, line 34:

<comment>The provider-independent `helpers/` contract contradicts the existing ElevenLabs-specific transcription helpers. Either move provider integrations behind an explicit adapter boundary or narrow this statement so contributors do not place or assume provider-specific code is provider-independent.</comment>

<file context>
@@ -0,0 +1,87 @@
+## Architecture boundaries
+
+- `SKILL.md` defines the agent workflow and public editing contract.
+- `helpers/` contains provider-independent production tools and validation.
+- `skills/` contains focused companion skills and reusable production assets.
+- `tests/` protects public behavior. Optional clients or remote runners may
</file context>
Suggested change
- `helpers/` contains provider-independent production tools and validation.
- `helpers/` contains reusable production tools and narrow provider adapters.
Fix with cubic

- `skills/` contains focused companion skills and reusable production assets.
- `tests/` protects public behavior. Optional clients or remote runners may
orchestrate core features but must never become the only place a feature
exists.

Keep decision data explicit in portable project files such as `edit/edl.json`.
Renderers should consume declared inputs deterministically. UI state, agent
history, and cloud runtime state must not be required to reproduce an output.

## Change workflow

1. Identify whether a change belongs to the public editing contract, a reusable
helper, a focused skill, or an optional adapter.
2. Implement the smallest complete general capability at the lowest reusable
layer. Wire adapters to that capability instead of duplicating it.
3. Validate inputs locally before uploads or paid compute. Validate again at
remote execution boundaries.
4. Add tests for successful use, invalid input, backwards compatibility, and
provider-boundary behavior where relevant.
5. For render changes, create representative media and inspect the encoded
dimensions, duration, frame rate, visual framing, and audible output.
6. Update the public EDL example or usage documentation whenever users or agents
need to author a new field.

Cost and latency are secondary unless the user sets a budget or deadline. Improve
them only when output quality and reliability remain equal or improve.

## Communication and commits

After code changes, summarize the affected files, the functions or contracts
added, and what each does in plain language. Keep this technical context compact
so someone can learn an unfamiliar codebase without reading every diff.

Write simple, readable commit messages. Prefer short lowercase wording without
punctuation.

## Branch discipline

Several agents work on this repository at once. To keep one agent's progress
from being overwritten by another:

- One feature per branch, one agent per branch. Never edit a worktree that
belongs to another branch; take files from a commit or tag instead.
- Commit early. Uncommitted work in a worktree has no merge base and no
history, so a later sync silently discards it.
- Hard rules in `SKILL.md` are append-only. New rules get the next number.
Removing or renumbering a rule requires an explicit reason in the commit.
- Procedure prose belongs in `references/<feature>.md` at the repository root;
create that folder with the first reference file. Edits to `SKILL.md`
are limited to rules, helper-index bullets, directory-tree lines, the EDL
example, and one-line pointers to the reference files.
Comment on lines +82 to +85

@cubic-dev-ai cubic-dev-ai Bot Sep 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This repository-wide rule sends companion-skill documentation to the wrong directory. Keep root-skill procedures in references/, but place companion-skill procedures in that skill's own references/ directory so its existing relative links remain valid.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At AGENTS.md, line 82:

<comment>This repository-wide rule sends companion-skill documentation to the wrong directory. Keep root-skill procedures in `references/`, but place companion-skill procedures in that skill's own `references/` directory so its existing relative links remain valid.</comment>

<file context>
@@ -0,0 +1,87 @@
+  history, so a later sync silently discards it.
+- Hard rules in `SKILL.md` are append-only. New rules get the next number.
+  Removing or renumbering a rule requires an explicit reason in the commit.
+- Procedure prose belongs in `references/<feature>.md` at the repository root;
+  create that folder with the first reference file. Edits to `SKILL.md`
+  are limited to rules, helper-index bullets, directory-tree lines, the EDL
</file context>
Suggested change
- Procedure prose belongs in `references/<feature>.md` at the repository root;
create that folder with the first reference file. Edits to `SKILL.md`
are limited to rules, helper-index bullets, directory-tree lines, the EDL
example, and one-line pointers to the reference files.
- Procedure prose for the root skill belongs in `references/<feature>.md`; companion-skill prose belongs in that skill's `references/` directory.
Create the relevant directory with the first reference file. Edits to `SKILL.md` are limited to rules, helper-index bullets, directory-tree lines, the EDL example, and one-line pointers to reference files.
Fix with cubic

- `tests/test_skill_contract.py` checks that the rules and every referenced
path still exist. Run it before committing a `SKILL.md` change.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Try video-use in [Browser Use Cloud](https://cloud.browser-use.com/v4?utm_campai
- **Burns subtitles** in your style — 2-word UPPERCASE chunks by default, fully customizable
- **Generates animation overlays** via [HyperFrames](https://github.com/heygen-com/hyperframes), [Remotion](https://www.remotion.dev/), [Manim](https://www.manim.community/), or PIL — spawned in parallel sub-agents, one per animation
- **Self-evaluates the rendered output** at every cut boundary before showing you anything
- **Builds original narrated explainers from a topic** — semantic Manim teaching assets, chapter previews, generated narration with word-timed captions
- **Persists session memory** in `project.md` so next week's session picks up where you left off

## Setup prompt
Expand Down Expand Up @@ -66,6 +67,9 @@ brew install yt-dlp # optional, for downloading online sources
# 3. Add your ElevenLabs API key
cp .env.example .env
$EDITOR .env # ELEVENLABS_API_KEY=...

# 4. Optional: Manim for original explainers built from a topic
uv sync --extra animations

@cubic-dev-ai cubic-dev-ai Bot Sep 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Following this optional setup does not install or check LaTeX, a hard prerequisite for the documented Manim explainer workflow. Add the platform-specific LaTeX prerequisite or point users to the Manim setup instructions before claiming the feature is set up.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 72:

<comment>Following this optional setup does not install or check LaTeX, a hard prerequisite for the documented Manim explainer workflow. Add the platform-specific LaTeX prerequisite or point users to the Manim setup instructions before claiming the feature is set up.</comment>

<file context>
@@ -66,6 +67,9 @@ brew install yt-dlp             # optional, for downloading online sources
 $EDITOR .env                    # ELEVENLABS_API_KEY=...
+
+# 4. Optional: Manim for original explainers built from a topic
+uv sync --extra animations

</file context>


</details>

<a href="https://www.cubic.dev/action/fix/violation/aa829fce-53fc-4331-8612-47158cb2bf4c" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://cubic.dev/buttons/fix-with-cubic-dark.svg">
    <source media="(prefers-color-scheme: light)" srcset="https://cubic.dev/buttons/fix-with-cubic-light.svg">
    <img alt="Fix with cubic" src="https://cubic.dev/buttons/fix-with-cubic-dark.svg">
  </picture>
</a>

```

## How it works
Expand Down
11 changes: 11 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ Pick the engine per animation slot. Do not default to Remotion just because the
- **Manim** — formal diagrams, state machines, equation derivations, graph morphs. Read `skills/manim-video/SKILL.md` and its references for depth.
- **PIL + PNG sequence + ffmpeg** — simple overlay cards: counters, typewriter text, single bar reveals, progressive draws. Fast to iterate, any aesthetic you want. The launch video used this.

For an original end-to-end explainer, complete `edit/visual_plan.md`, use one
independently renderable Manim class per narrative chapter with named internal
beats, and build persistent semantic objects from
`skills/manim-video/assets/teaching.py` and `skills/manim-video/assets/domains/`.
Narration is part of this deliverable by default unless the user explicitly
requests silence. Teach one mechanism per explainer under a minute, keep chapters to
three, never time stretch recorded narration to fit, and keep labels at font size 24
or larger; the full contract is in `skills/manim-video/references/concept-explainer.md`. This requirement does not apply to clip editing or an isolated
illustration overlay. Read `skills/manim-video/references/concept-explainer.md`
before authoring a Manim scene.
Comment on lines +210 to +219

@cubic-dev-ai cubic-dev-ai Bot Sep 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This adds procedure prose to SKILL.md, outside the repository’s documentation boundary, and duplicates the existing explainer contract. Move the details to the reference file and keep only a one-line pointer here to prevent the two contracts from drifting.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At SKILL.md, line 210:

<comment>This adds procedure prose to `SKILL.md`, outside the repository’s documentation boundary, and duplicates the existing explainer contract. Move the details to the reference file and keep only a one-line pointer here to prevent the two contracts from drifting.</comment>

<file context>
@@ -207,6 +207,17 @@ Pick the engine per animation slot. Do not default to Remotion just because the
 - **Manim** — formal diagrams, state machines, equation derivations, graph morphs. Read `skills/manim-video/SKILL.md` and its references for depth.
 - **PIL + PNG sequence + ffmpeg** — simple overlay cards: counters, typewriter text, single bar reveals, progressive draws. Fast to iterate, any aesthetic you want. The launch video used this.
 
+For an original end-to-end explainer, complete `edit/visual_plan.md`, use one
+independently renderable Manim class per narrative chapter with named internal
+beats, and build persistent semantic objects from
</file context>
Suggested change
For an original end-to-end explainer, complete `edit/visual_plan.md`, use one
independently renderable Manim class per narrative chapter with named internal
beats, and build persistent semantic objects from
`skills/manim-video/assets/teaching.py` and `skills/manim-video/assets/domains/`.
Narration is part of this deliverable by default unless the user explicitly
requests silence. Teach one mechanism per explainer under a minute, keep chapters to
three, never time stretch recorded narration to fit, and keep labels at font size 24
or larger; the full contract is in `skills/manim-video/references/concept-explainer.md`. This requirement does not apply to clip editing or an isolated
illustration overlay. Read `skills/manim-video/references/concept-explainer.md`
before authoring a Manim scene.
For original end-to-end explainers, read `skills/manim-video/references/concept-explainer.md` before authoring a Manim scene.
Fix with cubic


For HyperFrames slots, scaffold the slot inside `edit/animations/slot_<id>/` with `npx --yes hyperframes init . --example blank --non-interactive --skip-skills`, build the HTML composition there, run the HyperFrames checks that fit the slot (`lint`, `validate`, and a draft render when practical), then produce the final overlay video with `npx --yes hyperframes render . -o render.mp4` or `--format webm -o render.webm` when alpha is required. Point the EDL overlay `file` at the actual rendered path.

For Remotion slots, keep the Remotion project isolated inside the same slot directory, scaffold with `npx create-video@latest` or install Remotion locally there, render the composition to `render.mp4` with the project-local `remotion render` command, and verify duration and dimensions with `ffprobe`.
Expand Down
7 changes: 7 additions & 0 deletions helpers/grade.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
}


# look up the ffmpeg filter string for a named preset and fail loudly on unknown names
def get_preset(name: str) -> str:
"""Return the ffmpeg filter string for a preset name. Empty string for 'none'."""
if name not in PRESETS:
Expand All @@ -75,6 +76,7 @@ def get_preset(name: str) -> str:
# -------- Auto grade (data-driven, per-clip) --------------------------------


# run ffmpeg signalstats over a sampled range and average the luma and saturation readings into normalized stats
def _sample_frame_stats(
video: Path,
start: float,
Expand Down Expand Up @@ -121,6 +123,7 @@ def _sample_frame_stats(
sat_avgs: list[float] = []
bit_depth: int = 8

# pull the numeric value after the last equals sign on a metadata line
def _parse_value(line: str) -> float | None:
try:
return float(line.rsplit("=", 1)[1])
Expand Down Expand Up @@ -175,6 +178,7 @@ def _parse_value(line: str) -> float | None:
Path(metadata_path).unlink(missing_ok=True)


# analyze a clip range and derive a bounded corrective eq filter with no creative color shift
def auto_grade_for_clip(
video: Path,
start: float = 0.0,
Expand Down Expand Up @@ -271,8 +275,10 @@ def auto_grade_for_clip(
return filter_string, stats


# run ffmpeg to write the graded output or stream copy when the filter is empty
def apply_grade(input_path: Path, output_path: Path, filter_string: str) -> None:
output_path.parent.mkdir(parents=True, exist_ok=True)
# an empty filter means no grade so copy streams without re encoding
if not filter_string:
cmd = [
"ffmpeg", "-y", "-i", str(input_path),
Expand All @@ -291,6 +297,7 @@ def apply_grade(input_path: Path, output_path: Path, filter_string: str) -> None
subprocess.run(cmd, check=True)


# cli entry point that handles preset listing analysis and the normal grade run
def main() -> None:
ap = argparse.ArgumentParser(description="Apply a color grade via ffmpeg filter chain")
ap.add_argument("input", type=Path, nargs="?", help="Input video")
Expand Down
10 changes: 10 additions & 0 deletions helpers/pack_transcripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
from pathlib import Path


# render seconds as a zero padded fixed width string so stamps line up in columns
def format_time(seconds: float) -> str:
"""Format a time in seconds as "NNN.NN" with fixed 6-char width for alignment."""
return f"{seconds:06.2f}"


# render a duration as seconds or minutes plus seconds for the section headers
def format_duration(seconds: float) -> str:
"""Format a duration as "Ms" or "Mm SSs"."""
if seconds < 60:
Expand All @@ -35,6 +37,7 @@ def format_duration(seconds: float) -> str:
return f"{m}m {s:04.1f}s"


# walk scribe word entries and cut them into phrases on silence gaps or speaker changes
def group_into_phrases(
words: list[dict],
silence_threshold: float = 0.5,
Expand All @@ -51,6 +54,7 @@ def group_into_phrases(
current_start: float | None = None
current_speaker: str | None = None

# emit the current phrase if it has any visible text then reset the accumulator
def flush() -> None:
nonlocal current_words, current_start, current_speaker
if not current_words:
Expand All @@ -61,6 +65,7 @@ def flush() -> None:
raw = (w.get("text") or "").strip()
if not raw:
continue
# wrap audio events in parentheses unless scribe already did
if t == "audio_event":
if not raw.startswith("("):
raw = f"({raw})"
Expand All @@ -71,7 +76,9 @@ def flush() -> None:
current_speaker = None
return
text = " ".join(text_parts)
# glue punctuation tokens back onto the preceding word
text = text.replace(" ,", ",").replace(" .", ".").replace(" ?", "?").replace(" !", "!")
# fall back through end then start then phrase start so a missing timestamp cannot break the flush
end_time = current_words[-1].get("end", current_words[-1].get("start", current_start or 0.0))
phrases.append({
"start": current_start,
Expand Down Expand Up @@ -122,6 +129,7 @@ def flush() -> None:
return phrases


# load one transcript json and return its name duration and phrase list
def pack_one_file(json_path: Path, silence_threshold: float) -> tuple[str, float, list[dict]]:
"""Return (header_name, duration, phrases) for one transcript file."""
data = json.loads(json_path.read_text())
Expand All @@ -134,6 +142,7 @@ def pack_one_file(json_path: Path, silence_threshold: float) -> tuple[str, float
return json_path.stem, duration, phrases


# build the markdown document with a header block and one section per transcript
def render_markdown(entries: list[tuple[str, float, list[dict]]], silence_threshold: float) -> str:
lines: list[str] = []
lines.append("# Packed transcripts")
Expand Down Expand Up @@ -162,6 +171,7 @@ def render_markdown(entries: list[tuple[str, float, list[dict]]], silence_thresh
return "\n".join(lines)


# cli entry point that finds transcript json files packs them and writes takes_packed md
def main() -> None:
ap = argparse.ArgumentParser(description="Pack Scribe transcripts into takes_packed.md")
ap.add_argument("--edit-dir", type=Path, required=True, help="Edit directory containing transcripts/")
Expand Down
Loading