Initial release — OpenMontage: the first open-source agentic video production system

11 production pipelines, 47 tools, 124 agent skills.
Supports cloud APIs (fal.ai, OpenAI, ElevenLabs, Suno, HeyGen, Runway) and
free local providers (diffusers, Piper TTS, WAN 2.1, Hunyuan, CogVideo).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
calesthio
2026-03-29 08:25:17 -07:00
commit a3e735cc7a
1147 changed files with 240221 additions and 0 deletions
View File
+493
View File
@@ -0,0 +1,493 @@
"""Phase 0 contract tests — infrastructure layer.
Tests config, schemas, checkpoints, pipeline manifests, tools, cost tracker,
and media profiles. The intelligence layer (orchestrator, reviewer, checkpoint
policy, handlers) has been replaced by instruction-driven architecture:
pipeline manifests + stage director skills + meta skills.
"""
import importlib
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
import pytest
# Add project root to path
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from lib.config_model import OpenMontageConfig
from lib.checkpoint import (
CheckpointValidationError,
STAGES,
get_next_stage,
read_checkpoint,
write_checkpoint,
)
from lib.media_profiles import get_profile, ffmpeg_output_args, ALL_PROFILES
from lib.pipeline_loader import (
get_required_tools,
get_stage_order,
get_stage_skill,
get_stage_review_focus,
list_pipelines,
load_pipeline,
)
from tools.base_tool import BaseTool, ToolResult, ToolTier, ToolStatus
from tools.tool_registry import ToolRegistry
from tools.cost_tracker import CostTracker, BudgetMode, BudgetExceededError, ApprovalRequiredError
from schemas.artifacts import load_schema, validate_artifact, list_schemas
def sample_artifact(name: str) -> dict:
"""Return a minimal schema-valid artifact for tests."""
if name == "research_brief":
return {
"version": "1.0",
"topic": "Test Topic",
"research_date": "2026-03-27",
"landscape": {
"existing_content": [
{"title": "Existing Video 1", "source": "youtube", "angle": "tutorial", "what_it_covers": "basics"},
{"title": "Existing Video 2", "source": "blog", "angle": "deep dive", "what_it_covers": "advanced"},
{"title": "Existing Video 3", "source": "youtube", "angle": "comparison", "what_it_covers": "alternatives"},
],
"saturated_angles": ["basic tutorial"],
"underserved_gaps": ["misconceptions about topic"],
},
"data_points": [
{"claim": "73% of users prefer X", "source_url": "https://example.com/study", "credibility": "primary_source"},
{"claim": "Market grew 40% in 2025", "source_url": "https://example.com/report", "credibility": "secondary_source"},
{"claim": "Most experts agree on Y", "source_url": "https://example.com/survey", "credibility": "primary_source"},
],
"audience_insights": {
"common_questions": ["What is X?", "How does X work?", "Why is X important?"],
"misconceptions": [{"myth": "X is slow", "reality": "X is fast"}],
"knowledge_level": "Beginner to intermediate",
},
"angles_discovered": [
{"name": "The Surprising Truth", "hook": "You think X is slow. It's not.", "type": "contrarian", "why_now": "New benchmark data", "grounded_in": ["data_point_1"]},
{"name": "X From Scratch", "hook": "Build X in 5 minutes.", "type": "evergreen", "why_now": "Audience demand", "grounded_in": ["audience_q1"]},
{"name": "Why X Matters Now", "hook": "X just changed everything.", "type": "trending", "why_now": "Recent announcement", "grounded_in": ["trending_1"]},
],
"sources": [
{"url": "https://example.com/study", "title": "Study on X", "used_for": "data_points"},
{"url": "https://example.com/report", "title": "Market Report", "used_for": "data_points"},
{"url": "https://example.com/survey", "title": "Expert Survey", "used_for": "data_points"},
{"url": "https://example.com/reddit", "title": "Reddit Discussion", "used_for": "audience_insights"},
{"url": "https://example.com/blog", "title": "Tech Blog", "used_for": "landscape"},
],
}
if name == "proposal_packet":
return {
"version": "1.0",
"concept_options": [
{
"id": "c1", "title": "The Surprising Truth About X", "hook": "You think X is slow.",
"narrative_structure": "myth_busting", "visual_approach": "animated diagrams",
"target_duration_seconds": 60, "why_this_works": "Strong misconception found in research",
},
{
"id": "c2", "title": "X From Scratch", "hook": "Build X in 5 minutes.",
"narrative_structure": "tutorial", "visual_approach": "code walkthrough",
"target_duration_seconds": 90, "why_this_works": "High demand in audience questions",
},
{
"id": "c3", "title": "Why X Matters Now", "hook": "X just changed everything.",
"narrative_structure": "timeline", "visual_approach": "motion graphics",
"target_duration_seconds": 75, "why_this_works": "Recent announcement creates timeliness",
},
],
"selected_concept": {"concept_id": "c1", "rationale": "Strongest research backing"},
"production_plan": {
"pipeline": "animated-explainer",
"stages": [
{"stage": "script", "tools": [], "approach": "Write from research"},
{"stage": "assets", "tools": [{"tool_name": "tts_selector", "role": "narration", "available": True}], "approach": "Generate assets"},
],
},
"cost_estimate": {
"total_estimated_usd": 0.52,
"line_items": [{"tool": "elevenlabs_tts", "operation": "narration", "estimated_usd": 0.18}],
"budget_verdict": "within_budget",
},
"approval": {"status": "approved"},
}
if name == "brief":
return {
"version": "1.0",
"title": "Test Brief",
"hook": "Did you know?",
"key_points": ["point 1"],
"tone": "casual",
"style": "clean-professional",
"target_platform": "youtube",
"target_duration_seconds": 60,
}
if name == "script":
return {
"version": "1.0",
"title": "Test Script",
"total_duration_seconds": 60,
"sections": [
{
"id": "s1",
"text": "Hello world",
"start_seconds": 0,
"end_seconds": 10,
}
],
}
if name == "scene_plan":
return {
"version": "1.0",
"scenes": [
{
"id": "scene-1",
"type": "talking_head",
"description": "Host on camera",
"start_seconds": 0,
"end_seconds": 10,
}
],
}
if name == "asset_manifest":
return {
"version": "1.0",
"assets": [
{
"id": "asset-1",
"type": "video",
"path": "assets/clip.mp4",
"source_tool": "ffmpeg",
"scene_id": "scene-1",
}
],
}
if name == "edit_decisions":
return {
"version": "1.0",
"cuts": [
{
"id": "cut-1",
"source": "asset-1",
"in_seconds": 0,
"out_seconds": 10,
}
],
}
if name == "render_report":
return {
"version": "1.0",
"outputs": [
{
"path": "renders/output.mp4",
"format": "mp4",
"resolution": "1920x1080",
"duration_seconds": 60,
}
],
}
if name == "publish_log":
return {
"version": "1.0",
"entries": [
{
"platform": "youtube",
"status": "draft",
"timestamp": datetime.now(timezone.utc).isoformat(),
}
],
}
raise KeyError(f"Unknown artifact sample: {name}")
# ---- Config ----
class TestConfig:
def test_load_defaults(self):
config = OpenMontageConfig()
assert config.llm.provider == "anthropic"
assert config.budget.mode.value == "warn"
assert config.checkpoint.policy.value == "guided"
def test_load_from_yaml(self):
config = OpenMontageConfig.load()
assert config.budget.total_usd == 10.0
# ---- Schemas ----
class TestSchemas:
def test_all_schemas_loadable(self):
names = list_schemas()
assert len(names) >= 7
for name in names:
schema = load_schema(name)
assert "$schema" in schema
def test_brief_validates(self):
validate_artifact("brief", sample_artifact("brief"))
def test_brief_rejects_invalid(self):
with pytest.raises(Exception):
validate_artifact("brief", {"version": "1.0"})
# ---- Checkpoint ----
class TestCheckpoint:
def test_write_read_roundtrip(self, tmp_path):
write_checkpoint(
tmp_path, "test_project", "research", "completed",
{"research_brief": sample_artifact("research_brief")},
)
cp = read_checkpoint(tmp_path, "test_project", "research")
assert cp is not None
assert cp["stage"] == "research"
assert cp["status"] == "completed"
assert cp["artifacts"]["research_brief"]["topic"] == "Test Topic"
def test_get_next_stage(self, tmp_path):
assert get_next_stage(tmp_path, "proj") == "research"
write_checkpoint(
tmp_path,
"proj",
"research",
"completed",
{"research_brief": sample_artifact("research_brief")},
)
assert get_next_stage(tmp_path, "proj") == "proposal"
def test_invalid_stage_rejected(self, tmp_path):
with pytest.raises(ValueError):
write_checkpoint(tmp_path, "proj", "invalid_stage", "completed", {})
def test_invalid_canonical_artifact_rejected(self, tmp_path):
with pytest.raises(CheckpointValidationError):
write_checkpoint(
tmp_path,
"proj",
"research",
"completed",
{"research_brief": {"topic": "missing schema fields"}},
)
def test_missing_canonical_artifact_rejected(self, tmp_path):
with pytest.raises(CheckpointValidationError):
write_checkpoint(tmp_path, "proj", "research", "completed", {})
def test_invalid_status_rejected(self, tmp_path):
with pytest.raises(CheckpointValidationError):
write_checkpoint(
tmp_path,
"proj",
"research",
"mystery",
{"research_brief": sample_artifact("research_brief")},
)
# ---- Pipeline manifests ----
class TestPipelineManifests:
def test_framework_smoke_manifest_loads(self):
manifest = load_pipeline("framework-smoke")
assert manifest["name"] == "framework-smoke"
assert get_stage_order(manifest) == ["research", "script"]
assert get_required_tools(manifest) == set()
def test_framework_smoke_manifest_listed(self):
assert "framework-smoke" in list_pipelines()
# ---- BaseTool ----
class DummyTool(BaseTool):
name = "dummy"
version = "0.1.0"
tier = ToolTier.CORE
capabilities = ["test"]
dependencies = []
def execute(self, inputs):
return ToolResult(success=True, data={"echo": inputs})
class TestBaseTool:
def test_get_info(self):
tool = DummyTool()
info = tool.get_info()
assert info["name"] == "dummy"
assert info["tier"] == "core"
assert info["status"] == "available"
def test_execute(self):
tool = DummyTool()
result = tool.execute({"msg": "hello"})
assert result.success
def test_unavailable_when_deps_missing(self):
class MissingDepTool(BaseTool):
name = "missing"
dependencies = ["cmd:nonexistent_binary_xyz"]
def execute(self, inputs):
return ToolResult(success=True)
tool = MissingDepTool()
assert tool.get_status() == ToolStatus.UNAVAILABLE
# ---- ToolRegistry ----
class TestToolRegistry:
def test_register_and_find(self):
reg = ToolRegistry()
reg.register(DummyTool())
assert reg.get("dummy") is not None
assert "dummy" in reg.list_all()
assert len(reg.get_by_tier(ToolTier.CORE)) == 1
assert len(reg.find_by_capability("test")) == 1
def test_support_envelope(self):
reg = ToolRegistry()
reg.register(DummyTool())
envelope = reg.support_envelope()
assert "dummy" in envelope
assert envelope["dummy"]["status"] == "available"
def test_discovers_concrete_tools_from_package(self, tmp_path, monkeypatch):
package_dir = tmp_path / "demo_tools"
package_dir.mkdir()
(package_dir / "__init__.py").write_text("", encoding="utf-8")
(package_dir / "demo_tool.py").write_text(
"\n".join(
[
"from tools.base_tool import BaseTool, ToolResult, ToolTier",
"",
"class DiscoveredTool(BaseTool):",
" name = 'discovered'",
" tier = ToolTier.CORE",
" capabilities = ['discover']",
" dependencies = []",
"",
" def execute(self, inputs):",
" return ToolResult(success=True, data=inputs)",
"",
]
),
encoding="utf-8",
)
monkeypatch.syspath_prepend(str(tmp_path))
importlib.invalidate_caches()
reg = ToolRegistry()
discovered = reg.discover("demo_tools")
assert discovered == ["discovered"]
assert reg.get("discovered") is not None
assert reg.find_by_capability("discover")[0].name == "discovered"
# ---- CostTracker ----
class TestCostTracker:
def test_estimate_reserve_reconcile(self):
tracker = CostTracker(budget_total_usd=10.0, mode=BudgetMode.OBSERVE)
entry_id = tracker.estimate("image_selector", "generate", 0.05)
tracker.reserve(entry_id)
assert tracker.budget_reserved_usd == 0.05
tracker.reconcile(entry_id, 0.04, success=True)
assert tracker.budget_spent_usd == 0.04
assert tracker.budget_reserved_usd == 0.0
def test_cap_mode_blocks_overspend(self):
tracker = CostTracker(
budget_total_usd=1.0,
mode=BudgetMode.CAP,
single_action_approval_usd=10.0, # raise threshold so budget check triggers
)
tracker.approve_tool("expensive")
eid = tracker.estimate("expensive", "op", 5.0)
with pytest.raises(BudgetExceededError):
tracker.reserve(eid)
def test_persistence(self, tmp_path):
log_path = tmp_path / "cost_log.json"
t1 = CostTracker(budget_total_usd=10.0, mode=BudgetMode.OBSERVE, cost_log_path=log_path)
eid = t1.estimate("tool", "op", 0.10)
t1.reserve(eid)
t1.reconcile(eid, 0.08)
t2 = CostTracker(cost_log_path=log_path)
assert t2.budget_spent_usd == 0.08
# ---- Pipeline Instruction Architecture ----
class TestPipelineInstructionArchitecture:
"""Verify that the instruction-driven architecture is in place:
manifests reference skills, not Python handlers."""
def test_animated_explainer_stages_have_skills(self):
try:
manifest = load_pipeline("animated-explainer")
except FileNotFoundError:
pytest.skip("animated-explainer manifest not yet created")
for stage in manifest["stages"]:
assert "skill" in stage, f"Stage {stage['name']} missing skill field"
def test_stage_skill_lookup(self):
manifest = load_pipeline("framework-smoke")
# framework-smoke may not have skills yet — just verify the function works
result = get_stage_skill(manifest, "idea")
assert result is None or isinstance(result, str)
def test_stage_review_focus_lookup(self):
manifest = load_pipeline("framework-smoke")
result = get_stage_review_focus(manifest, "idea")
assert isinstance(result, list)
# ---- Agent context files ----
class TestAgentContextFiles:
def test_agent_guide_contains_canonical_sections(self):
contents = (PROJECT_ROOT / "AGENT_GUIDE.md").read_text(encoding="utf-8")
for header in (
"## Orchestrator",
"## Stage Agents",
"## Reviewer Protocol",
"## Communication Protocol",
"## Human Checkpoint Protocol",
):
assert header in contents
def test_platform_wrappers_reference_agent_guide(self):
for path in ("CLAUDE.md", "CODEX.md", "CURSOR.md", "COPILOT.md", "AGENTS.md"):
contents = (PROJECT_ROOT / path).read_text(encoding="utf-8")
assert "AGENT_GUIDE.md" in contents
# ---- Media Profiles ----
class TestMediaProfiles:
def test_all_profiles_exist(self):
assert len(ALL_PROFILES) >= 9
def test_get_profile(self):
p = get_profile("youtube_landscape")
assert p.width == 1920
assert p.height == 1080
def test_ffmpeg_args(self):
args = ffmpeg_output_args(get_profile("tiktok"))
assert "-c:v" in args
assert "1080" in args[-1]
def test_unknown_profile_raises(self):
with pytest.raises(ValueError):
get_profile("nonexistent")
+362
View File
@@ -0,0 +1,362 @@
"""Phase 1 contract tests — Core Talking-Head Pipeline tools."""
import json
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from tools.base_tool import BaseTool, ToolResult, ToolTier, ToolStatus, DependencyError
from tools.tool_registry import ToolRegistry
from lib.pipeline_loader import load_pipeline, get_stage_order, get_required_tools, list_pipelines
# ---- Tool imports ----
from tools.analysis.transcriber import Transcriber
from tools.video.video_trimmer import VideoTrimmer
from tools.subtitle.subtitle_gen import SubtitleGen
from tools.analysis.frame_sampler import FrameSampler
from tools.audio.audio_mixer import AudioMixer
from tools.video.video_compose import VideoCompose
# ---- Contract: every tool inherits BaseTool and has required fields ----
PHASE1_TOOLS = [
Transcriber,
VideoTrimmer,
SubtitleGen,
FrameSampler,
AudioMixer,
VideoCompose,
]
class TestPhase1ToolContracts:
"""Verify all Phase 1 tools satisfy the ToolContract."""
@pytest.mark.parametrize("tool_cls", PHASE1_TOOLS)
def test_inherits_base_tool(self, tool_cls):
assert issubclass(tool_cls, BaseTool)
@pytest.mark.parametrize("tool_cls", PHASE1_TOOLS)
def test_has_required_identity(self, tool_cls):
tool = tool_cls()
assert tool.name, f"{tool_cls.__name__} must have a non-empty name"
assert tool.version, f"{tool_cls.__name__} must have a version"
assert tool.tier in ToolTier
assert len(tool.capabilities) > 0, f"{tool_cls.__name__} must declare capabilities"
@pytest.mark.parametrize("tool_cls", PHASE1_TOOLS)
def test_get_info_returns_valid_dict(self, tool_cls):
tool = tool_cls()
info = tool.get_info()
assert isinstance(info, dict)
assert info["name"] == tool.name
assert info["tier"] in [t.value for t in ToolTier]
assert info["status"] in ["available", "unavailable", "degraded"]
@pytest.mark.parametrize("tool_cls", PHASE1_TOOLS)
def test_has_input_schema(self, tool_cls):
tool = tool_cls()
assert isinstance(tool.input_schema, dict)
assert "properties" in tool.input_schema or "type" in tool.input_schema
@pytest.mark.parametrize("tool_cls", PHASE1_TOOLS)
def test_execute_is_implemented(self, tool_cls):
"""Verify execute() is not the abstract stub."""
tool = tool_cls()
# Should not raise TypeError — it's implemented
assert callable(tool.execute)
@pytest.mark.parametrize("tool_cls", PHASE1_TOOLS)
def test_dry_run_returns_dict(self, tool_cls):
tool = tool_cls()
result = tool.dry_run({})
assert isinstance(result, dict)
assert "tool" in result
assert result["tool"] == tool.name
# ---- Contract: tools report correct status based on dependencies ----
class TestPhase1ToolStatus:
def test_subtitle_gen_always_available(self):
"""SubtitleGen has no external dependencies — always available."""
tool = SubtitleGen()
assert tool.get_status() == ToolStatus.AVAILABLE
def test_transcriber_reports_status_correctly(self):
"""Transcriber should report unavailable if faster_whisper not installed."""
tool = Transcriber()
status = tool.get_status()
assert status in (ToolStatus.AVAILABLE, ToolStatus.UNAVAILABLE)
def test_ffmpeg_tools_report_status(self):
"""FFmpeg-dependent tools should report based on ffmpeg availability."""
for cls in [VideoTrimmer, FrameSampler, AudioMixer, VideoCompose]:
tool = cls()
status = tool.get_status()
assert status in (ToolStatus.AVAILABLE, ToolStatus.UNAVAILABLE)
# ---- Contract: tool names are unique ----
class TestPhase1ToolNames:
def test_unique_names(self):
names = [cls().name for cls in PHASE1_TOOLS]
assert len(names) == len(set(names)), f"Duplicate tool names: {names}"
def test_expected_names(self):
names = {cls().name for cls in PHASE1_TOOLS}
expected = {"transcriber", "video_trimmer", "subtitle_gen", "frame_sampler", "audio_mixer", "video_compose"}
assert names == expected
# ---- Contract: tools are discoverable via registry ----
class TestPhase1ToolDiscovery:
def test_all_phase1_tools_discoverable(self):
"""Registry.discover() should find all Phase 1 tools."""
reg = ToolRegistry()
discovered = reg.discover("tools")
for cls in PHASE1_TOOLS:
name = cls().name
assert name in discovered or reg.get(name) is not None, (
f"Tool {name!r} not discovered by registry"
)
def test_phase1_tools_are_core_tier(self):
"""All Phase 1 tools should be in the CORE tier."""
for cls in PHASE1_TOOLS:
tool = cls()
assert tool.tier == ToolTier.CORE, f"{tool.name} should be CORE tier"
# ---- Contract: SubtitleGen produces valid output without FFmpeg ----
class TestSubtitleGenUnit:
def test_srt_generation(self):
segments = [
{
"text": "Hello world",
"start": 0.0,
"end": 1.5,
"words": [
{"word": "Hello", "start": 0.0, "end": 0.5},
{"word": "world", "start": 0.6, "end": 1.5},
],
},
{
"text": "This is a test",
"start": 2.0,
"end": 4.0,
"words": [
{"word": "This", "start": 2.0, "end": 2.3},
{"word": "is", "start": 2.4, "end": 2.5},
{"word": "a", "start": 2.6, "end": 2.7},
{"word": "test", "start": 2.8, "end": 4.0},
],
},
]
tool = SubtitleGen()
result = tool.execute({
"segments": segments,
"format": "srt",
"output_path": "test_output.srt",
})
assert result.success
assert len(result.artifacts) == 1
content = Path(result.artifacts[0]).read_text()
assert "-->" in content
assert "Hello world" in content
# Cleanup
Path(result.artifacts[0]).unlink(missing_ok=True)
def test_vtt_generation(self):
segments = [
{
"text": "Test cue",
"start": 1.0,
"end": 3.0,
"words": [
{"word": "Test", "start": 1.0, "end": 1.5},
{"word": "cue", "start": 1.6, "end": 3.0},
],
},
]
tool = SubtitleGen()
result = tool.execute({
"segments": segments,
"format": "vtt",
"output_path": "test_output.vtt",
})
assert result.success
content = Path(result.artifacts[0]).read_text()
assert content.startswith("WEBVTT")
Path(result.artifacts[0]).unlink(missing_ok=True)
def test_json_generation(self):
segments = [
{
"text": "JSON test",
"start": 0.0,
"end": 2.0,
"words": [
{"word": "JSON", "start": 0.0, "end": 0.8},
{"word": "test", "start": 0.9, "end": 2.0},
],
},
]
tool = SubtitleGen()
result = tool.execute({
"segments": segments,
"format": "json",
"output_path": "test_output.caption.json",
})
assert result.success
data = json.loads(Path(result.artifacts[0]).read_text())
assert "cues" in data
assert len(data["cues"]) >= 1
Path(result.artifacts[0]).unlink(missing_ok=True)
def test_word_grouping_respects_max_words(self):
words = [{"word": f"w{i}", "start": i * 0.5, "end": i * 0.5 + 0.4} for i in range(20)]
segments = [{"text": " ".join(w["word"] for w in words), "start": 0.0, "end": 10.0, "words": words}]
tool = SubtitleGen()
result = tool.execute({
"segments": segments,
"format": "json",
"max_words_per_cue": 4,
"output_path": "test_grouping.caption.json",
})
assert result.success
data = json.loads(Path(result.artifacts[0]).read_text())
for cue in data["cues"]:
assert len(cue["words"]) <= 4
Path(result.artifacts[0]).unlink(missing_ok=True)
def test_segment_fallback_without_words(self):
"""Segments without word-level timestamps use segment-level timing."""
segments = [
{"text": "No word data", "start": 0.0, "end": 2.0},
]
tool = SubtitleGen()
result = tool.execute({
"segments": segments,
"format": "srt",
"output_path": "test_fallback.srt",
})
assert result.success
content = Path(result.artifacts[0]).read_text()
assert "No word data" in content
Path(result.artifacts[0]).unlink(missing_ok=True)
# ---- Contract: missing input file returns error, not crash ----
class TestPhase1ErrorHandling:
def test_transcriber_missing_file(self):
tool = Transcriber()
result = tool.execute({"input_path": "/nonexistent/file.mp4"})
assert not result.success
assert "not found" in result.error.lower() or "not installed" in result.error.lower()
def test_video_trimmer_missing_file(self):
tool = VideoTrimmer()
result = tool.execute({
"operation": "cut",
"input_path": "/nonexistent/file.mp4",
})
assert not result.success
def test_frame_sampler_missing_file(self):
tool = FrameSampler()
result = tool.execute({
"input_path": "/nonexistent/file.mp4",
"strategy": "interval",
})
assert not result.success
def test_audio_mixer_missing_tracks(self):
tool = AudioMixer()
result = tool.execute({"operation": "mix", "tracks": []})
assert not result.success
def test_video_compose_missing_decisions(self):
tool = VideoCompose()
result = tool.execute({"operation": "compose"})
assert not result.success
# ---- Contract: talking-head pipeline manifest ----
class TestTalkingHeadManifest:
def test_manifest_loads(self):
manifest = load_pipeline("talking-head")
assert manifest["name"] == "talking-head"
assert manifest["category"] == "talking_head"
def test_manifest_has_all_stages(self):
manifest = load_pipeline("talking-head")
stages = get_stage_order(manifest)
assert stages == ["idea", "script", "scene_plan", "assets", "edit", "compose", "publish"]
def test_manifest_references_phase1_tools(self):
manifest = load_pipeline("talking-head")
tools = get_required_tools(manifest)
phase1_tools = {"transcriber", "video_trimmer", "subtitle_gen", "frame_sampler", "audio_mixer", "video_compose"}
# At least some Phase 1 tools should be referenced
assert len(tools & phase1_tools) > 0
def test_manifest_listed(self):
assert "talking-head" in list_pipelines()
def test_manifest_has_required_skills(self):
manifest = load_pipeline("talking-head")
skills = manifest.get("required_skills", [])
# Instruction-driven architecture: skills are stage director + meta skills
assert any("talking-head" in s for s in skills)
assert any("reviewer" in s for s in skills)
assert any("checkpoint-protocol" in s for s in skills)
def test_idea_and_publish_require_approval(self):
manifest = load_pipeline("talking-head")
for stage in manifest["stages"]:
if stage["name"] in ("idea", "publish"):
assert stage.get("human_approval_default") is True
# ---- Contract: skill files exist ----
class TestPhase1Skills:
@pytest.mark.parametrize("skill_path", [
"skills/core/ffmpeg.md",
"skills/core/whisperx.md",
"skills/core/subtitle-sync.md",
"skills/creative/video-editing.md",
"skills/creative/enhancement-strategy.md",
])
def test_skill_file_exists(self, skill_path):
full_path = PROJECT_ROOT / skill_path
assert full_path.exists(), f"Skill file missing: {skill_path}"
@pytest.mark.parametrize("skill_path", [
"skills/core/ffmpeg.md",
"skills/core/whisperx.md",
"skills/core/subtitle-sync.md",
"skills/creative/video-editing.md",
"skills/creative/enhancement-strategy.md",
])
def test_skill_has_content(self, skill_path):
full_path = PROJECT_ROOT / skill_path
content = full_path.read_text(encoding="utf-8")
assert len(content) > 100, f"Skill file too short: {skill_path}"
assert "## When to Use" in content, f"Skill missing 'When to Use' section: {skill_path}"
assert "## Quality Checklist" in content, f"Skill missing 'Quality Checklist' section: {skill_path}"
+62
View File
@@ -0,0 +1,62 @@
"""Phase 1 golden scenario test — validates the talking-head pipeline
manifest and skill architecture are in place.
The old test ran the Python orchestrator pipeline end-to-end. That layer
has been removed in favor of instruction-driven architecture: the agent
reads pipeline manifests + stage director skills and drives the pipeline
itself. These tests verify the infrastructure is correctly wired.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from lib.checkpoint import STAGES
from lib.pipeline_loader import (
load_pipeline,
get_stage_order,
get_stage_skill,
get_stage_review_focus,
list_pipelines,
)
from schemas.artifacts import load_schema, validate_artifact, list_schemas
class TestTalkingHeadManifest:
"""Verify the talking-head pipeline manifest is well-formed."""
def test_manifest_loads(self):
manifest = load_pipeline("talking-head")
assert manifest["name"] == "talking-head"
def test_all_stages_present(self):
manifest = load_pipeline("talking-head")
stage_names = get_stage_order(manifest)
expected = ["idea", "script", "scene_plan", "assets", "edit", "compose", "publish"]
assert stage_names == expected
def test_manifest_listed(self):
assert "talking-head" in list_pipelines()
class TestGoldenScenarioArtifacts:
"""Validate golden scenario artifact samples against schemas."""
GOLDEN_PATH = PROJECT_ROOT / "eval" / "golden_scenarios" / "talking_head_basic.json"
@pytest.fixture
def golden(self):
if not self.GOLDEN_PATH.exists():
pytest.skip("Golden scenario file not found")
return json.loads(self.GOLDEN_PATH.read_text())
def test_golden_file_structure(self, golden):
assert "inputs" in golden
assert "expected_artifacts" in golden
+170
View File
@@ -0,0 +1,170 @@
"""Phase 2 side-by-side comparison: Phase 1 vs Phase 2 enhanced output.
Runs the talking-head compose handler twice — once with enhance=False
(Phase 1 baseline) and once with enhance=True (Phase 2 enhanced) —
then asserts both outputs exist and reports file size / duration for
manual comparison.
"""
import json
import subprocess
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
FOOTAGE = Path(
r"C:\Users\ishan\Documents\SocialMedia\IshanAIVideos\pipeline\2026-03-08\seg1_intro.mp4"
)
def _has_ffmpeg() -> bool:
import shutil
return shutil.which("ffmpeg") is not None
def _get_duration(path: str) -> float:
result = subprocess.run(
["ffprobe", "-v", "quiet", "-show_entries", "format=duration", "-of", "json", path],
capture_output=True, text=True, check=True,
)
data = json.loads(result.stdout)
return float(data.get("format", {}).get("duration", 0))
def _get_file_size_mb(path: str) -> float:
return Path(path).stat().st_size / (1024 * 1024)
@pytest.fixture(scope="module")
def comparison_outputs(tmp_path_factory):
"""Run compose handler twice: baseline and enhanced."""
if not FOOTAGE.exists():
pytest.skip("Test footage not available")
if not _has_ffmpeg():
pytest.skip("FFmpeg not available")
from tools.video.video_compose import VideoCompose
from tools.enhancement.face_enhance import FaceEnhance
from tools.enhancement.color_grade import ColorGrade
from tools.audio.audio_enhance import AudioEnhance
tmp = tmp_path_factory.mktemp("comparison")
results = {}
# Phase 1 baseline: just encode
composer = VideoCompose()
baseline_path = str(tmp / "phase1_baseline.mp4")
r = composer.execute({
"operation": "encode",
"input_path": str(FOOTAGE),
"output_path": baseline_path,
"codec": "libx264",
"crf": 20,
})
assert r.success, f"Baseline encode failed: {r.error}"
results["baseline"] = baseline_path
# Phase 2 enhanced: face -> color -> audio
current = str(FOOTAGE)
enhancements = []
face = FaceEnhance()
face_out = str(tmp / "step_face.mp4")
r = face.execute({
"input_path": current,
"output_path": face_out,
"presets": ["talking_head_standard"],
})
if r.success:
current = r.data["output"]
enhancements.append("face_enhance:talking_head_standard")
color = ColorGrade()
color_out = str(tmp / "step_color.mp4")
r = color.execute({
"input_path": current,
"output_path": color_out,
"profile": "cinematic_warm",
"intensity": 0.85,
})
if r.success:
current = r.data["output"]
enhancements.append("color_grade:cinematic_warm@0.85")
audio = AudioEnhance()
audio_out = str(tmp / "step_audio.mp4")
r = audio.execute({
"input_path": current,
"output_path": audio_out,
"preset": "clean_speech",
})
if r.success:
current = r.data["output"]
enhancements.append("audio_enhance:clean_speech")
# Final encode
enhanced_path = str(tmp / "phase2_enhanced.mp4")
r = composer.execute({
"operation": "encode",
"input_path": current,
"output_path": enhanced_path,
"codec": "libx264",
"crf": 20,
})
assert r.success, f"Enhanced encode failed: {r.error}"
results["enhanced"] = enhanced_path
results["enhancements"] = enhancements
return results
class TestPhase2Comparison:
def test_baseline_exists(self, comparison_outputs):
assert Path(comparison_outputs["baseline"]).exists()
def test_enhanced_exists(self, comparison_outputs):
assert Path(comparison_outputs["enhanced"]).exists()
def test_both_have_valid_duration(self, comparison_outputs):
base_dur = _get_duration(comparison_outputs["baseline"])
enh_dur = _get_duration(comparison_outputs["enhanced"])
assert base_dur > 0
assert enh_dur > 0
# Durations should be within 1 second of each other
assert abs(base_dur - enh_dur) < 1.0, (
f"Duration mismatch: baseline={base_dur:.1f}s, enhanced={enh_dur:.1f}s"
)
def test_enhancements_were_applied(self, comparison_outputs):
enhancements = comparison_outputs["enhancements"]
assert len(enhancements) > 0, "No enhancements were applied"
def test_report(self, comparison_outputs):
"""Print comparison report for manual review."""
baseline = comparison_outputs["baseline"]
enhanced = comparison_outputs["enhanced"]
base_size = _get_file_size_mb(baseline)
enh_size = _get_file_size_mb(enhanced)
base_dur = _get_duration(baseline)
enh_dur = _get_duration(enhanced)
enhancements = comparison_outputs["enhancements"]
report = (
f"\n{'='*60}\n"
f" Phase 1 vs Phase 2 Comparison\n"
f"{'='*60}\n"
f" Baseline: {base_size:.2f} MB, {base_dur:.1f}s\n"
f" Enhanced: {enh_size:.2f} MB, {enh_dur:.1f}s\n"
f" Enhancements: {', '.join(enhancements)}\n"
f" Baseline path: {baseline}\n"
f" Enhanced path: {enhanced}\n"
f"{'='*60}\n"
)
print(report)
# Always passes — this test is for reporting
assert True
+266
View File
@@ -0,0 +1,266 @@
"""Phase 2 contract tests — Enhancement Layer tools."""
import json
import shutil
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from tools.base_tool import BaseTool, ToolResult, ToolTier, ToolStatus
from tools.tool_registry import ToolRegistry
from tools.enhancement.face_enhance import FaceEnhance, PRESETS as FACE_PRESETS
from tools.analysis.scene_detect import SceneDetect
from tools.enhancement.color_grade import ColorGrade, PROFILES as COLOR_PROFILES
from tools.audio.audio_enhance import AudioEnhance, PRESETS as AUDIO_PRESETS
from tools.graphics.image_selector import ImageSelector
from tools.graphics.code_snippet import CodeSnippet, THEMES as CODE_THEMES
from tools.graphics.diagram_gen import DiagramGen
PHASE2_TOOLS = [
FaceEnhance,
SceneDetect,
ColorGrade,
AudioEnhance,
ImageSelector,
CodeSnippet,
DiagramGen,
]
# ---- Contract: every tool inherits BaseTool and has required fields ----
class TestPhase2ToolContracts:
@pytest.mark.parametrize("tool_cls", PHASE2_TOOLS)
def test_inherits_base_tool(self, tool_cls):
assert issubclass(tool_cls, BaseTool)
@pytest.mark.parametrize("tool_cls", PHASE2_TOOLS)
def test_has_required_identity(self, tool_cls):
tool = tool_cls()
assert tool.name
assert tool.version
assert tool.tier in ToolTier
assert len(tool.capabilities) > 0
@pytest.mark.parametrize("tool_cls", PHASE2_TOOLS)
def test_get_info_returns_valid_dict(self, tool_cls):
tool = tool_cls()
info = tool.get_info()
assert isinstance(info, dict)
assert info["name"] == tool.name
assert info["status"] in ["available", "unavailable", "degraded"]
@pytest.mark.parametrize("tool_cls", PHASE2_TOOLS)
def test_execute_is_implemented(self, tool_cls):
tool = tool_cls()
assert callable(tool.execute)
@pytest.mark.parametrize("tool_cls", PHASE2_TOOLS)
def test_dry_run_returns_dict(self, tool_cls):
tool = tool_cls()
result = tool.dry_run({})
assert isinstance(result, dict)
assert result["tool"] == tool.name
# ---- Contract: unique names, all CORE tier ----
class TestPhase2ToolNames:
def test_unique_names(self):
names = [cls().name for cls in PHASE2_TOOLS]
assert len(names) == len(set(names))
def test_expected_names(self):
names = {cls().name for cls in PHASE2_TOOLS}
expected = {
"face_enhance", "scene_detect", "color_grade", "audio_enhance",
"image_selector", "code_snippet", "diagram_gen",
}
assert names == expected
def test_expected_tiers(self):
for cls in PHASE2_TOOLS:
tool = cls()
if tool.name == "image_selector":
assert tool.tier == ToolTier.GENERATE
else:
assert tool.tier == ToolTier.CORE, f"{tool.name} should be CORE"
# ---- Contract: discoverable via registry ----
class TestPhase2ToolDiscovery:
def test_all_phase2_tools_discoverable(self):
reg = ToolRegistry()
reg.discover("tools")
for cls in PHASE2_TOOLS:
name = cls().name
assert reg.get(name) is not None, f"{name} not discovered"
# ---- Contract: presets/profiles are well-formed ----
class TestPresets:
def test_face_presets_have_vf(self):
for name, preset in FACE_PRESETS.items():
assert "vf" in preset, f"Face preset {name} missing 'vf'"
assert "description" in preset
def test_color_profiles_have_vf(self):
for name, profile in COLOR_PROFILES.items():
assert "vf" in profile, f"Color profile {name} missing 'vf'"
assert "description" in profile
def test_audio_presets_have_af(self):
for name, preset in AUDIO_PRESETS.items():
assert "af" in preset, f"Audio preset {name} missing 'af'"
assert "description" in preset
def test_code_themes_have_required_fields(self):
for name, theme in CODE_THEMES.items():
assert "pygments_style" in theme
assert "bg_color" in theme
assert "text_color" in theme
def test_face_list_presets(self):
presets = FaceEnhance.list_presets()
assert len(presets) >= 8
assert "talking_head_standard" in presets
def test_color_list_profiles(self):
profiles = ColorGrade.list_profiles()
assert len(profiles) >= 7
assert "cinematic_warm" in profiles
def test_audio_list_presets(self):
presets = AudioEnhance.list_presets()
assert len(presets) >= 5
assert "clean_speech" in presets
# ---- Contract: error handling for missing inputs ----
class TestPhase2ErrorHandling:
def test_face_enhance_missing_file(self):
tool = FaceEnhance()
r = tool.execute({"input_path": "/nonexistent.mp4"})
assert not r.success
def test_scene_detect_missing_file(self):
tool = SceneDetect()
r = tool.execute({"input_path": "/nonexistent.mp4"})
assert not r.success
def test_color_grade_missing_file(self):
tool = ColorGrade()
r = tool.execute({"input_path": "/nonexistent.mp4"})
assert not r.success
def test_audio_enhance_missing_file(self):
tool = AudioEnhance()
r = tool.execute({"input_path": "/nonexistent.mp4"})
assert not r.success
def test_image_selector_no_provider(self):
tool = ImageSelector()
# Will fail if no API key or local model
r = tool.execute({"prompt": "test"})
# Either succeeds (provider available) or fails gracefully
assert isinstance(r, ToolResult)
def test_diagram_gen_empty_boxes(self):
tool = DiagramGen()
if tool.get_status() == ToolStatus.AVAILABLE:
r = tool.execute({"diagram_type": "boxes", "boxes": []})
assert isinstance(r, ToolResult)
# ---- Contract: code_snippet renders valid images ----
class TestCodeSnippetUnit:
@pytest.fixture
def has_deps(self):
tool = CodeSnippet()
if tool.get_status() != ToolStatus.AVAILABLE:
pytest.skip("Pygments/Pillow not installed")
def test_render_python(self, has_deps, tmp_path):
tool = CodeSnippet()
r = tool.execute({
"code": "def hello():\n print('Hello, world!')\n",
"language": "python",
"theme": "monokai",
"output_path": str(tmp_path / "test.png"),
})
assert r.success
assert Path(r.data["output"]).exists()
assert r.data["line_count"] == 3
def test_render_with_title(self, has_deps, tmp_path):
tool = CodeSnippet()
r = tool.execute({
"code": "console.log('test');",
"language": "javascript",
"theme": "dracula",
"title": "example.js",
"output_path": str(tmp_path / "titled.png"),
})
assert r.success
def test_render_different_themes(self, has_deps, tmp_path):
tool = CodeSnippet()
for theme_name in ["monokai", "github_dark", "light"]:
r = tool.execute({
"code": "x = 42",
"language": "python",
"theme": theme_name,
"output_path": str(tmp_path / f"{theme_name}.png"),
})
assert r.success, f"Theme {theme_name} failed"
# ---- Contract: diagram_gen renders valid images ----
class TestDiagramGenUnit:
@pytest.fixture
def has_deps(self):
tool = DiagramGen()
if tool.get_status() != ToolStatus.AVAILABLE:
pytest.skip("No diagram renderer available")
def test_render_box_diagram(self, has_deps, tmp_path):
tool = DiagramGen()
r = tool.execute({
"diagram_type": "boxes",
"title": "Test Flow",
"boxes": [
{"label": "Input", "color": "#2563eb"},
{"label": "Process", "color": "#7c3aed"},
{"label": "Output", "color": "#059669"},
],
"connections": [
{"from": 0, "to": 1, "label": "data"},
{"from": 1, "to": 2, "label": "result"},
],
"theme": "dark",
"output_path": str(tmp_path / "boxes.png"),
})
assert r.success
assert Path(r.data["output"]).exists()
assert r.data["box_count"] == 3
def test_render_mermaid_fallback(self, has_deps, tmp_path):
"""If mmdc not installed, falls back to text card."""
tool = DiagramGen()
r = tool.execute({
"diagram_type": "mermaid",
"definition": "graph TD\n A[Start] --> B[End]",
"output_path": str(tmp_path / "mermaid.png"),
})
assert r.success
+342
View File
@@ -0,0 +1,342 @@
"""Phase 3 contract tests — instruction-driven architecture.
Tests the new tools (TTS, music gen), pipeline manifests, style playbooks,
stage director skills, meta skills, and the animated-explainer pipeline.
"""
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from lib.pipeline_loader import (
load_pipeline,
get_stage_order,
get_required_tools,
get_stage_skill,
get_stage_review_focus,
list_pipelines,
)
from lib.checkpoint import STAGES
from schemas.artifacts import list_schemas
from styles.playbook_loader import load_playbook, list_playbooks, validate_playbook
from tools.base_tool import ToolTier
from tools.audio.music_gen import MusicGen
from tools.tool_registry import ToolRegistry
from tools.audio.elevenlabs_tts import ElevenLabsTTS
from tools.audio.openai_tts import OpenAITTS
from tools.audio.piper_tts import PiperTTS
from tools.audio.tts_selector import TTSSelector
# ---- TTS Provider Tools ----
class TestElevenLabsTTS:
def test_identity(self):
tool = ElevenLabsTTS()
info = tool.get_info()
assert info["name"] == "elevenlabs_tts"
assert info["tier"] == "voice"
assert info["capability"] == "tts"
assert info["provider"] == "elevenlabs"
def test_cost_estimate(self):
tool = ElevenLabsTTS()
cost = tool.estimate_cost({"text": "Hello world, this is a test."})
assert cost > 0
assert cost < 0.01 # short text should be cheap
def test_capabilities(self):
tool = ElevenLabsTTS()
assert "text_to_speech" in tool.capabilities
assert "voice_selection" in tool.capabilities
class TestPiperTTS:
def test_identity(self):
tool = PiperTTS()
info = tool.get_info()
assert info["name"] == "piper_tts"
assert info["tier"] == "voice"
assert info["capability"] == "tts"
assert info["provider"] == "piper"
def test_cost_is_free(self):
tool = PiperTTS()
assert tool.estimate_cost({"text": "anything"}) == 0.0
def test_capabilities(self):
tool = PiperTTS()
assert "text_to_speech" in tool.capabilities
assert "offline_generation" in tool.capabilities
class TestMusicGen:
def test_identity(self):
tool = MusicGen()
info = tool.get_info()
assert info["name"] == "music_gen"
assert info["tier"] == "generate"
def test_cost_estimate_scales_with_duration(self):
tool = MusicGen()
cost_30 = tool.estimate_cost({"prompt": "ambient", "duration_seconds": 30})
cost_60 = tool.estimate_cost({"prompt": "ambient", "duration_seconds": 60})
assert cost_60 > cost_30
def test_capabilities(self):
tool = MusicGen()
assert "generate_background_music" in tool.capabilities
class TestNewToolsRegistry:
def test_all_register(self):
reg = ToolRegistry()
reg.register(ElevenLabsTTS())
reg.register(PiperTTS())
reg.register(MusicGen())
assert len(reg.list_all()) == 3
def test_voice_tier_tools(self):
reg = ToolRegistry()
reg.register(ElevenLabsTTS())
reg.register(OpenAITTS())
reg.register(PiperTTS())
voice_tools = reg.get_by_tier(ToolTier.VOICE)
assert len(voice_tools) == 3
names = {t.name for t in voice_tools}
assert names == {"elevenlabs_tts", "openai_tts", "piper_tts"}
class TestCapabilityMetadata:
def test_tts_tools_expose_capability_provider_and_location(self):
tool = ElevenLabsTTS()
info = tool.get_info()
assert info["capability"] == "tts"
assert info["provider"] == "elevenlabs"
assert info["usage_location"].endswith("tools\\audio\\elevenlabs_tts.py") or info["usage_location"].endswith("tools/audio/elevenlabs_tts.py")
assert "related_skills" in info
assert "fallback_tools" in info
def test_provider_specific_tts_tools_register(self):
reg = ToolRegistry()
reg.register(ElevenLabsTTS())
reg.register(OpenAITTS())
reg.register(PiperTTS())
reg.register(TTSSelector())
assert {tool.name for tool in reg.get_by_capability("tts")} == {
"elevenlabs_tts",
"openai_tts",
"piper_tts",
"tts_selector",
}
assert {tool.name for tool in reg.get_by_provider("elevenlabs")} == {"elevenlabs_tts"}
def test_registry_catalog_views(self):
reg = ToolRegistry()
reg.register(ElevenLabsTTS())
reg.register(OpenAITTS())
reg.register(PiperTTS())
catalog = reg.capability_catalog()
assert "tts" in catalog
providers = {item["provider"] for item in catalog["tts"] if item["provider"] != "selector"}
assert providers == {"elevenlabs", "openai", "piper"}
# ---- Animated Explainer Pipeline ----
class TestAnimatedExplainerManifest:
def test_loads(self):
manifest = load_pipeline("animated-explainer")
assert manifest["name"] == "animated-explainer"
assert manifest["version"] == "2.0"
def test_all_stages_present(self):
manifest = load_pipeline("animated-explainer")
stage_names = get_stage_order(manifest)
expected = ["research", "proposal", "script", "scene_plan", "assets", "edit", "compose", "publish"]
assert stage_names == expected
def test_every_stage_has_skill(self):
manifest = load_pipeline("animated-explainer")
for stage in manifest["stages"]:
assert "skill" in stage, f"Stage {stage['name']} missing skill"
skill = get_stage_skill(manifest, stage["name"])
assert skill is not None
assert skill.startswith("pipelines/explainer/")
def test_every_stage_has_review_focus(self):
manifest = load_pipeline("animated-explainer")
for stage in manifest["stages"]:
focus = get_stage_review_focus(manifest, stage["name"])
assert len(focus) >= 3, f"Stage {stage['name']} needs more review focus items"
def test_required_tools_complete(self):
manifest = load_pipeline("animated-explainer")
tools = get_required_tools(manifest)
expected = {"tts_selector", "image_selector", "video_compose", "audio_mixer"}
for t in expected:
assert t in tools, f"Missing required tool: {t}"
def test_creative_stages_require_human_approval(self):
manifest = load_pipeline("animated-explainer")
approval_stages = {"proposal", "script", "scene_plan", "publish"}
for stage in manifest["stages"]:
if stage["name"] in approval_stages:
assert stage.get("human_approval_default") is True, (
f"Stage {stage['name']} should require human approval"
)
def test_listed(self):
assert "animated-explainer" in list_pipelines()
# ---- Style Playbooks ----
class TestStylePlaybooks:
def test_all_listed(self):
playbooks = list_playbooks()
assert "clean-professional" in playbooks
assert "flat-motion-graphics" in playbooks
assert "minimalist-diagram" in playbooks
@pytest.mark.parametrize("name", ["clean-professional", "flat-motion-graphics", "minimalist-diagram"])
def test_loads_and_validates(self, name):
pb = load_playbook(name)
assert pb["identity"]["name"]
assert pb["identity"]["category"]
@pytest.mark.parametrize("name", ["clean-professional", "flat-motion-graphics", "minimalist-diagram"])
def test_has_required_sections(self, name):
pb = load_playbook(name)
assert "visual_language" in pb
assert "typography" in pb
assert "motion" in pb
assert "audio" in pb
assert "asset_generation" in pb
assert "quality_rules" in pb
assert len(pb["quality_rules"]) >= 3
@pytest.mark.parametrize("name", ["clean-professional", "flat-motion-graphics", "minimalist-diagram"])
def test_color_palette_complete(self, name):
pb = load_playbook(name)
palette = pb["visual_language"]["color_palette"]
assert "primary" in palette
assert "accent" in palette
assert "background" in palette
assert "text" in palette
@pytest.mark.parametrize("name", ["clean-professional", "flat-motion-graphics", "minimalist-diagram"])
def test_pacing_rules_present(self, name):
pb = load_playbook(name)
pacing = pb["motion"]["pacing_rules"]
assert "min_scene_hold_seconds" in pacing
assert "max_scene_hold_seconds" in pacing
def test_compatible_with_manifest(self):
manifest = load_pipeline("animated-explainer")
available = list_playbooks()
for name in manifest.get("compatible_playbooks", []):
assert name in available, f"Manifest references unavailable playbook: {name}"
# ---- Skills Existence ----
class TestSkillsExist:
SKILLS_DIR = PROJECT_ROOT / "skills"
@pytest.mark.parametrize("skill_path", [
"pipelines/explainer/idea-director.md",
"pipelines/explainer/script-director.md",
"pipelines/explainer/scene-director.md",
"pipelines/explainer/asset-director.md",
"pipelines/explainer/edit-director.md",
"pipelines/explainer/compose-director.md",
"pipelines/explainer/publish-director.md",
])
def test_director_skills_exist(self, skill_path):
full_path = self.SKILLS_DIR / skill_path
assert full_path.exists(), f"Missing director skill: {skill_path}"
content = full_path.read_text(encoding="utf-8")
assert len(content) > 500, f"Skill too short to be useful: {skill_path}"
@pytest.mark.parametrize("skill_path", [
"meta/reviewer.md",
"meta/checkpoint-protocol.md",
"meta/skill-creator.md",
])
def test_meta_skills_exist(self, skill_path):
full_path = self.SKILLS_DIR / skill_path
assert full_path.exists(), f"Missing meta skill: {skill_path}"
content = full_path.read_text(encoding="utf-8")
assert len(content) > 500, f"Skill too short to be useful: {skill_path}"
@pytest.mark.parametrize("skill_path", [
"pipelines/explainer/idea-director.md",
"pipelines/explainer/script-director.md",
"pipelines/explainer/scene-director.md",
"pipelines/explainer/asset-director.md",
"pipelines/explainer/edit-director.md",
"pipelines/explainer/compose-director.md",
"pipelines/explainer/publish-director.md",
])
def test_director_skills_have_required_sections(self, skill_path):
content = (self.SKILLS_DIR / skill_path).read_text(encoding="utf-8")
assert "## When to Use" in content
assert "## Process" in content or "## Protocol" in content
assert "Self-Evaluate" in content or "self-evaluate" in content.lower()
@pytest.mark.parametrize("skill_path", [
"meta/reviewer.md",
"meta/checkpoint-protocol.md",
"meta/skill-creator.md",
])
def test_meta_skills_have_required_sections(self, skill_path):
content = (self.SKILLS_DIR / skill_path).read_text(encoding="utf-8")
assert "## When to Use" in content
assert "## Protocol" in content or "## Process" in content
# ---- Remotion Scaffold ----
class TestRemotionScaffold:
REMOTION_DIR = PROJECT_ROOT / "remotion-composer"
def test_package_json_exists(self):
assert (self.REMOTION_DIR / "package.json").exists()
def test_entry_point_exists(self):
assert (self.REMOTION_DIR / "src" / "index.tsx").exists()
def test_root_composition_exists(self):
assert (self.REMOTION_DIR / "src" / "Root.tsx").exists()
def test_explainer_component_exists(self):
assert (self.REMOTION_DIR / "src" / "Explainer.tsx").exists()
def test_text_card_component_exists(self):
assert (self.REMOTION_DIR / "src" / "components" / "TextCard.tsx").exists()
def test_stat_card_component_exists(self):
assert (self.REMOTION_DIR / "src" / "components" / "StatCard.tsx").exists()
# ---- Video Compose Operations ----
class TestVideoComposeOperations:
def test_render_operation_exists(self):
from tools.video.video_compose import VideoCompose
tool = VideoCompose()
ops = tool.input_schema["properties"]["operation"]["enum"]
assert "render" in ops
assert "remotion_render" in ops
def test_render_rejects_missing_inputs(self):
from tools.video.video_compose import VideoCompose
tool = VideoCompose()
result = tool.execute({"operation": "render"})
assert not result.success
assert "edit_decisions" in result.error