     1|"""Renderer manifest generation for retention-led AutoShortsBot videos."""
     2|
     3|from __future__ import annotations
     4|
     5|import json
     6|from pathlib import Path
     7|from typing import Any
     8|
     9|from autoshorts.script_drafts import _slug
    10|
    11|RESOLUTION = {"width": 1080, "height": 1920}
    12|ASPECT_RATIO = "9:16"
    13|FPS = 30
    14|
    15|
    16|ASSET_REQUIREMENT_HINTS = {
    17|    "kinetic_text": "Generated by renderer from text and style instructions.",
    18|    "screen_recording": "Needs captured or synthesized vertical screen recording; avoid fake unreadable UI text.",
    19|    "card_animation": "Generated by renderer from concise text cards; animate one idea per beat.",
    20|    "stock_broll": "Needs human/phone/context B-roll; avoid generic corporate stock feel.",
    21|    "generated_background": "Optional abstract background only; must not become the main content.",
    22|}
    23|
    24|
    25|def _duration_ms(start: int, end: int) -> int:
    26|    return max(500, (int(end) - int(start)) * 1000)
    27|
    28|
    29|def _placeholder(asset_type: str, video_id: str, scene: int) -> str:
    30|    return f"placeholder://{video_id}/scene-{scene:02d}/{asset_type}"
    31|
    32|
    33|def _visual_style(scene: dict[str, Any]) -> dict[str, Any]:
    34|    base = {
    35|        "safe_area_pct": 8,
    36|        "font": "bold_sans",
    37|        "caption_case": "sentence",
    38|        "motion": "push_zoom" if scene["scene"] == 1 else "cut_or_slide",
    39|        "contrast": "high",
    40|    }
    41|    if scene["asset_type"] == "card_animation":
    42|        base.update({"layout": "minimal_cards", "max_words_on_screen": 18})
    43|    elif scene["asset_type"] == "screen_recording":
    44|        base.update({"layout": "screen_plus_caption", "blur_sensitive_ui": True})
    45|    elif scene["asset_type"] == "stock_broll":
    46|        base.update({"layout": "broll_under_caption", "pace": "fast_cut"})
    47|    return base
    48|
    49|
    50|def _visual_timeline_items(plan: dict[str, Any]) -> list[dict[str, Any]]:
    51|    video_id = plan["id"]
    52|    items: list[dict[str, Any]] = []
    53|    for scene in plan["scenes"]:
    54|        items.append(
    55|            {
    56|                "id": f"scene_{scene['scene']:02d}",
    57|                "track": "visual",
    58|                "type": scene["asset_type"],
    59|                "asset_ref": _placeholder(scene["asset_type"], video_id, scene["scene"]),
    60|                "start_ms": int(scene["start"]) * 1000,
    61|                "duration_ms": _duration_ms(scene["start"], scene["end"]),
    62|                "text": scene["on_screen_text"],
    63|                "visual_direction": scene["visual_direction"],
    64|                "retention_goal": scene["retention_goal"],
    65|                "style": _visual_style(scene),
    66|            }
    67|        )
    68|    return items
    69|
    70|
    71|def _caption_timeline_items(plan: dict[str, Any]) -> list[dict[str, Any]]:
    72|    beats = plan.get("caption_beats", [])
    73|    total_ms = int(plan["estimated_seconds"]) * 1000
    74|    items: list[dict[str, Any]] = []
    75|    for index, beat in enumerate(beats):
    76|        start_ms = int(beat["start"]) * 1000
    77|        next_start_ms = int(beats[index + 1]["start"]) * 1000 if index + 1 < len(beats) else total_ms
    78|        items.append(
    79|            {
    80|                "id": f"caption_{index + 1:02d}",
    81|                "track": "captions",
    82|                "type": "kinetic_caption",
    83|                "start_ms": start_ms,
    84|                "duration_ms": max(750, next_start_ms - start_ms),
    85|                "text": beat["text"],
    86|                "style": {
    87|                    "safe_area_pct": 8,
    88|                    "position": "lower_third",
    89|                    "max_chars_per_line": 28,
    90|                    "highlight_keywords": True,
    91|                },
    92|            }
    93|        )
    94|    return items
    95|
    96|
    97|def _asset_requirements(plan: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
    98|    requirements: dict[str, list[dict[str, Any]]] = {key: [] for key in ASSET_REQUIREMENT_HINTS}
    99|    for scene in plan["scenes"]:
   100|        asset_type = scene["asset_type"]
   101|        requirements.setdefault(asset_type, []).append(
   102|            {
   103|                "scene": scene["scene"],
   104|                "asset_ref": _placeholder(asset_type, plan["id"], scene["scene"]),
   105|                "brief": scene["visual_direction"],
   106|                "hint": ASSET_REQUIREMENT_HINTS.get(asset_type, "Provide asset matching the visual direction."),
   107|            }
   108|        )
   109|    return requirements
   110|
   111|
   112|def build_render_manifest(plan: dict[str, Any]) -> dict[str, Any]:
   113|    """Convert a human shot plan into a structured renderer contract."""
   114|    timeline = _visual_timeline_items(plan) + _caption_timeline_items(plan)
   115|    track_order = {"visual": 0, "captions": 1}
   116|    timeline.sort(key=lambda item: (item["start_ms"], track_order.get(item["track"], 99), item["id"]))
   117|    return {
   118|        "schema_version": "render_manifest.v1",
   119|        "video": {
   120|            "id": plan["id"],
   121|            "title": plan["title"],
   122|            "pillar": plan["pillar"],
   123|            "duration_ms": int(plan["estimated_seconds"]) * 1000,
   124|            "aspect_ratio": ASPECT_RATIO,
   125|            "resolution": RESOLUTION,
   126|            "fps": FPS,
   127|        },
   128|        "timeline": timeline,
   129|        "asset_requirements": _asset_requirements(plan),
   130|        "render_constraints": {
   131|            "avoid_static_slideshow": True,
   132|            "pattern_interrupt_every_seconds": 3,
   133|            "first_frame_hook_required": True,
   134|            "max_words_per_visual_card": 18,
   135|            "human_approval_required_before_publish": True,
   136|        },
   137|        "production_notes": plan.get("production_notes", []),
   138|    }
   139|
   140|
   141|def write_render_manifests(plans: list[dict[str, Any]], output_dir: Path) -> list[Path]:
   142|    """Write render manifests as pretty JSON files."""
   143|    output_dir.mkdir(parents=True, exist_ok=True)
   144|    paths: list[Path] = []
   145|    for plan in plans:
   146|        manifest = build_render_manifest(plan)
   147|        path = output_dir / f"{plan['id']}-{_slug(plan['title'])}.json"
   148|        path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
   149|        paths.append(path)
   150|    return paths
   151|


     1|"""Shot plan generation for finalized AutoShortsBot voiceover scripts."""
     2|
     3|from __future__ import annotations
     4|
     5|from pathlib import Path
     6|from typing import Any
     7|
     8|from autoshorts.script_drafts import _slug
     9|
    10|SCENE_PURPOSES = [
    11|    "first_frame_hook",
    12|    "pattern_interrupt",
    13|    "core_explanation",
    14|    "screen_or_card_visual",
    15|    "cta",
    16|]
    17|
    18|
    19|def _split_text(text: str, limit: int = 74) -> str:
    20|    """Return a compact on-screen phrase, preserving strong hooks."""
    21|    cleaned = " ".join(text.split())
    22|    if len(cleaned) <= limit:
    23|        return cleaned
    24|    return cleaned[:limit].rsplit(" ", 1)[0].rstrip(".,;:")
    25|
    26|
    27|def _scene_windows(duration: int) -> list[tuple[int, int]]:
    28|    """Create five coarse timing windows for a short-form shot plan."""
    29|    if duration <= 45:
    30|        cuts = [0, 3, 8, max(16, duration - 16), max(24, duration - 7), duration]
    31|    else:
    32|        cuts = [0, 3, 9, max(22, duration - 28), max(42, duration - 10), duration]
    33|    windows: list[tuple[int, int]] = []
    34|    for start, end in zip(cuts, cuts[1:]):
    35|        windows.append((start, max(start + 2, end)))
    36|    return windows[:5]
    37|
    38|
    39|def _asset_sequence(final: dict[str, Any]) -> list[str]:
    40|    pillar = final["pillar"]
    41|    if pillar == "ai_life_systems":
    42|        return ["kinetic_text", "screen_recording", "card_animation", "screen_recording", "kinetic_text"]
    43|    if pillar == "attention_design":
    44|        return ["kinetic_text", "stock_broll", "card_animation", "screen_recording", "kinetic_text"]
    45|    return ["kinetic_text", "generated_background", "card_animation", "stock_broll", "kinetic_text"]
    46|
    47|
    48|def _visual_directions(final: dict[str, Any]) -> list[str]:
    49|    if final["pillar"] == "ai_life_systems":
    50|        return [
    51|            "Large kinetic hook text over a messy prompt window; immediate zoom-in on the contradiction.",
    52|            "Fast cut to a real prompt being rewritten from passive question to critic prompt.",
    53|            "Use simple cards for the key questions; one card changes at a time, no dense paragraphs.",
    54|            "Screen recording of the final copy-paste prompt with a visible human approval/decision moment.",
    55|            "End card with the comment keyword and one clear action; keep the CTA legible for one beat.",
    56|        ]
    57|    if final["pillar"] == "attention_design":
    58|        return [
    59|            "Large hook text over a phone/feed freeze-frame; make the first frame readable without audio.",
    60|            "Relatable stock/B-roll: thumb hovering over an endless feed, then hard cut to greyscale.",
    61|            "Card animation comparing your incentive vs. the feed incentive; two columns, very little text.",
    62|            "Screen-style visual showing home screen cleanup, app moved away, timer/exit rule set.",
    63|            "End on the phone home screen question; highlight the app moved off the home screen today.",
    64|        ]
    65|    return [
    66|        "Large kinetic hook text; visible movement in the first second.",
    67|        "Pattern interrupt with a fast before/after visual state change.",
    68|        "Use cards to explain the core idea one beat at a time.",
    69|        "Support the payoff with B-roll or a concrete screen/card visual.",
    70|        "End with the CTA as readable kinetic text.",
    71|    ]
    72|
    73|
    74|def _retention_goals() -> list[str]:
    75|    return [
    76|        "Stop the scroll with self-relevance and tension in the first two seconds.",
    77|        "Reset attention before the viewer predicts a generic slideshow.",
    78|        "Deliver the main mental model with low cognitive load.",
    79|        "Show the concrete payoff so the advice feels usable, not ornamental.",
    80|        "Convert attention into a comment/save action without adding a second idea.",
    81|    ]
    82|
    83|
    84|def build_shot_plan(final: dict[str, Any]) -> dict[str, Any]:
    85|    """Build a five-scene, retention-led shot plan from a finalized voiceover package."""
    86|    windows = _scene_windows(int(final["estimated_seconds"]))
    87|    asset_types = _asset_sequence(final)
    88|    directions = _visual_directions(final)
    89|    goals = _retention_goals()
    90|    beats = final.get("caption_beats", [])
    91|    hook_sentences = [part.strip() for part in final["voiceover"].split(".") if part.strip()]
    92|    hook_text = ". ".join(hook_sentences[:2]).strip()
    93|
    94|    scenes: list[dict[str, Any]] = []
    95|    for index, purpose in enumerate(SCENE_PURPOSES):
    96|        start, end = windows[index]
    97|        beat_text = beats[min(index, len(beats) - 1)]["text"] if beats else final["voiceover"]
    98|        on_screen = hook_text if index == 0 else _split_text(beat_text)
    99|        scenes.append(
   100|            {
   101|                "scene": index + 1,
   102|                "purpose": purpose,
   103|                "start": start,
   104|                "end": end,
   105|                "asset_type": asset_types[index],
   106|                "on_screen_text": on_screen,
   107|                "visual_direction": directions[index],
   108|                "retention_goal": goals[index],
   109|            }
   110|        )
   111|
   112|    return {
   113|        "id": final["id"],
   114|        "title": final["title"],
   115|        "pillar": final["pillar"],
   116|        "estimated_seconds": final["estimated_seconds"],
   117|        "caption_beats": final.get("caption_beats", []),
   118|        "scenes": scenes,
   119|        "production_notes": final.get("production_notes", []),
   120|    }
   121|
   122|
   123|def format_shot_plan(plan: dict[str, Any]) -> str:
   124|    """Format a shot plan as Markdown for human review and future renderer input."""
   125|    scene_blocks = []
   126|    for scene in plan["scenes"]:
   127|        scene_blocks.append(
   128|            f"### Scene {scene['scene']} — {scene['purpose']}\n\n"
   129|            f"- Timing: {scene['start']:02d}s–{scene['end']:02d}s\n"
   130|            f"- Asset type: {scene['asset_type']}\n"
   131|            f"- On-screen text: {scene['on_screen_text']}\n"
   132|            f"- Visual direction: {scene['visual_direction']}\n"
   133|            f"- Retention goal: {scene['retention_goal']}"
   134|        )
   135|    scenes = "\n\n".join(scene_blocks)
   136|    beats = "\n".join(f"- {beat['start']:02d}s: {beat['text']}" for beat in plan["caption_beats"])
   137|    notes = "\n".join(f"- {note}" for note in plan["production_notes"])
   138|    return f"""# {plan['title']}
   139|
   140|- ID: `{plan['id']}`
   141|- Pillar: `{plan['pillar']}`
   142|- Estimated duration: {plan['estimated_seconds']}s
   143|
   144|## Shot plan
   145|
   146|{scenes}
   147|
   148|## Caption beats
   149|
   150|{beats}
   151|
   152|## Production notes
   153|
   154|{notes}
   155|"""
   156|
   157|
   158|def write_shot_plans(finals: list[dict[str, Any]], output_dir: Path) -> list[Path]:
   159|    """Write one Markdown shot plan per finalized script."""
   160|    output_dir.mkdir(parents=True, exist_ok=True)
   161|    paths: list[Path] = []
   162|    for final in finals:
   163|        plan = build_shot_plan(final)
   164|        path = output_dir / f"{plan['id']}-{_slug(plan['title'])}.md"
   165|        path.write_text(format_shot_plan(plan), encoding="utf-8")
   166|        paths.append(path)
   167|    return paths
   168|


todo

Repo-Status prüfen und bestehende Manifest-/Shot-Plan-Struktur lesen

TDD: Asset-Plan-Tests für cinematic/screen/kinetic Modi schreiben und RED verifizieren


Asset-Plan-Builder v2 implementieren

Asset-Pläne für Top-3 generieren und prüfen

Full test suite, commit, push und remote verifizieren


import json
from pathlib import Path

from autoshorts.asset_plan import build_asset_plan, write_asset_plans

def _manifest(name: str = "ai-004-stop-asking-ai-to-think-for-you") -> dict:
    path = Path("data/render_manifests/testbatch_002_top3") / f"{name}.json"
    return json.loads(path.read_text(encoding="utf-8"))

def test_build_asset_plan_replaces_static_cards_with_moving_asset_modes():
    manifest = _manifest()

    plan = build_asset_plan(manifest)

    assert plan["schema_version"] == "asset_plan.v2"
    assert plan["video"]["id"] == "ai-004"
    assert len(plan["scenes"]) == 5
    modes = [scene["asset_mode"] for scene in plan["scenes"]]
    assert "cinematic_ai_video" in modes
    assert "screen_recording_simulation" in modes
    assert "kinetic_text_overlay" in modes
    assert "static_card" not in modes
    assert all(scene["motion_directive"] for scene in plan["scenes"])
    assert all(scene["overlay_text"] for scene in plan["scenes"])

def test_cinematic_ai_video_scenes_have_provider_ready_prompts_without_baked_text():
    manifest = _manifest()

    plan = build_asset_plan(manifest)

    cinematic = [scene for scene in plan["scenes"] if scene["asset_mode"] == "cinematic_ai_video"]
    assert cinematic
    for scene in cinematic:
        prompt = scene["generation_prompt"]
        assert scene["provider_family"] == "kling_hailuo_runway"
        assert scene["no_text_in_video"] is True
        assert "photorealistic" in prompt.lower()
        assert "vertical 9:16" in prompt.lower()
        assert "no readable text" in scene["negative_prompt"].lower()
        assert scene["duration_seconds"] >= 3
        assert scene["duration_seconds"] <= 6

def test_attention_design_asset_plan_uses_broll_and_screen_recording_not_text_slideshow():
    manifest = _manifest("attention-002-the-algorithm-does-not-need-you-happy")

    plan = build_asset_plan(manifest)

    modes = [scene["asset_mode"] for scene in plan["scenes"]]
    assert "cinematic_ai_video" in modes
    assert "screen_recording_simulation" in modes
    assert "animated_card" in modes
    assert modes.count("kinetic_text_overlay") <= 2
    assert plan["final_render_strategy"]["static_card_limit"] == 0

def test_write_asset_plans_outputs_json_files(tmp_path):
    manifests = [_manifest(), _manifest("attention-002-the-algorithm-does-not-need-you-happy")]

    paths = write_asset_plans(manifests, tmp_path)

    assert len(paths) == 2
    assert all(path.suffix == ".json" for path in paths)
    payload = json.loads(paths[0].read_text(encoding="utf-8"))
    assert payload["schema_version"] == "asset_plan.v2"
    assert payload["scenes"][0]["scene_id"] == "scene_01"


LSP diagnostics introduced by this edit:
<diagnostics file="/home/agent/jarvis_runtime/AutoShortsBot/tests/test_asset_plan.py">
ERROR [4:6] Import "autoshorts.asset_plan" could not be resolved [reportMissingImports] (Pyright)
</diagnostics>


terminal

python3 -m pytest tests/test_asset_plan.py -q


Output:
==================================== ERRORS ====================================
__________________ ERROR collecting tests/test_asset_plan.py ___________________
ImportError while importing test module '/home/agent/jarvis_runtime/AutoShortsBot/tests/test_asset_plan.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_asset_plan.py:4: in <module>
    from autoshorts.asset_plan import build_asset_plan, write_asset_plans
E   ModuleNotFoundError: No module named 'autoshorts.asset_plan'
=========================== short test summary info ============================
ERROR tests/test_asset_plan.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.12s


todo

Asset-Plan-Builder v2 implementieren


"""Asset planning for final, motion-led AutoShortsBot renders.

The preview renderer is only a storyboard. This module converts render manifests
into explicit asset-generation plans so the final videos can use cinematic AI
clips, screen-recording simulations, kinetic overlays, and animated cards instead
of static text slides.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from autoshorts.script_drafts import _slug

SCENE_MODE_BY_TYPE = {
    "kinetic_text": "kinetic_text_overlay",
    "screen_recording": "screen_recording_simulation",
    "card_animation": "animated_card",
    "stock_broll": "cinematic_ai_video",
    "generated_background": "cinematic_ai_video",
}

# Scene-level overrides keep AI/productivity videos from becoming five text cards.
SCENE_MODE_OVERRIDES = {
    "ai_life_systems": {
        "scene_01": "cinematic_ai_video",
        "scene_02": "screen_recording_simulation",
        "scene_03": "animated_card",
        "scene_04": "screen_recording_simulation",
        "scene_05": "kinetic_text_overlay",
    },
    "attention_design": {
        "scene_01": "cinematic_ai_video",
        "scene_02": "cinematic_ai_video",
        "scene_03": "animated_card",
        "scene_04": "screen_recording_simulation",
        "scene_05": "kinetic_text_overlay",
    },
}

def _visual_items(manifest: dict[str, Any]) -> list[dict[str, Any]]:
    return [item for item in manifest["timeline"] if item["track"] == "visual"]

def _duration_seconds(item: dict[str, Any]) -> int:
    return max(3, min(6, round(int(item["duration_ms"]) / 1000)))

def _asset_mode(item: dict[str, Any], pillar: str) -> str:
    override = SCENE_MODE_OVERRIDES.get(pillar, {}).get(item["id"])
    if override:
        return override
    return SCENE_MODE_BY_TYPE.get(item["type"], "cinematic_ai_video")

def _camera_motion(scene_id: str, mode: str) -> str:
    if mode == "cinematic_ai_video":
        return "slow handheld push-in, subtle parallax, natural micro-movements"
    if mode == "screen_recording_simulation":
        return "cursor movement, typing reveal, zoom to changed prompt, highlighted selection box"
    if mode == "animated_card":
        return "cards slide in one at a time, one concept per beat, slight scale pops"
    return "large kinetic caption words pop and slide with beat-synced emphasis"

def _cinematic_subject(pillar: str, text: str) -> str:
    if pillar == "attention_design":
        return (
            "a realistic person holding a phone in a dim modern room, thumb hovering over an endless feed, "
            "then a decisive pause as the screen glow reflects on their face"
        )
    return (
        "a realistic creator at a desk late evening, laptop open to an AI chat, thoughtful but in control, "
        "hands moving between keyboard and notebook"
    )

def _generation_prompt(item: dict[str, Any], manifest: dict[str, Any], mode: str) -> str:
    video = manifest["video"]
    text = item["text"]
    if mode == "cinematic_ai_video":
        subject = _cinematic_subject(video["pillar"], text)
        return (
            f"Photorealistic vertical 9:16 short video clip, {subject}. "
            f"Mood: focused, modern, high-retention social video, cinematic lighting, realistic motion. "
            f"Scene intent: {item['visual_direction']} Camera: {_camera_motion(item['id'], mode)}. "
            "Leave clean negative space for captions; do not include readable text in the generated video."
        )
    if mode == "screen_recording_simulation":
        return (
            "Vertical 9:16 UI/screen-recording simulation. Show an AI chat or phone interface being edited live: "
            f"{item['visual_direction']} Use typing, cursor movement, zoom highlights, and before/after prompt changes. "
            "All readable captions must be added by our renderer overlay, not baked into the clip."
        )
    if mode == "animated_card":
        return (
            "Vertical animated explainer card sequence. Minimal dark UI, one idea per beat, animated boxes and arrows. "
            f"Visual task: {item['visual_direction']} Keep it moving; no static slide holds longer than one second."
        )
    return (
        "Kinetic text overlay scene. Use large caption words, fast emphasis, scale pops, and clean background motion. "
        f"Overlay text: {text}"
    )

def _negative_prompt(mode: str) -> str:
    if mode == "cinematic_ai_video":
        return (
            "no readable text, no subtitles, no logos, no watermarks, no distorted hands, no fake UI text, "
            "no slideshow, no static image, no uncanny faces"
        )
    if mode == "screen_recording_simulation":
        return "no private data, no real credentials, no illegible tiny UI, no brand logos unless approved"
    if mode == "animated_card":
        return "no dense paragraphs, no static PowerPoint slide, no tiny text, no chart junk"
    return "no long paragraphs, no tiny footnotes, no static card"

def _scene_asset_plan(item: dict[str, Any], manifest: dict[str, Any]) -> dict[str, Any]:
    video = manifest["video"]
    mode = _asset_mode(item, video["pillar"])
    return {
        "scene_id": item["id"],
        "asset_ref": item["asset_ref"],
        "asset_mode": mode,
        "provider_family": "kling_hailuo_runway" if mode == "cinematic_ai_video" else "local_renderer",
        "duration_seconds": _duration_seconds(item),
        "overlay_text": item["text"],
        "caption_strategy": "renderer_overlay_only",
        "no_text_in_video": mode == "cinematic_ai_video",
        "motion_directive": _camera_motion(item["id"], mode),
        "generation_prompt": _generation_prompt(item, manifest, mode),
        "negative_prompt": _negative_prompt(mode),
        "retention_goal": item["retention_goal"],
        "source_visual_direction": item["visual_direction"],
    }

def build_asset_plan(manifest: dict[str, Any]) -> dict[str, Any]:
    """Build a final-render asset plan from a render manifest."""
    scenes = [_scene_asset_plan(item, manifest) for item in _visual_items(manifest)]
    modes = [scene["asset_mode"] for scene in scenes]
    return {
        "schema_version": "asset_plan.v2",
        "video": manifest["video"],
        "scenes": scenes,
        "final_render_strategy": {
            "preview_renderer_role": "storyboard_only",
            "static_card_limit": 0,
            "must_use_motion_assets": True,
            "preferred_mix": {
                "cinematic_ai_video": modes.count("cinematic_ai_video"),
                "screen_recording_simulation": modes.count("screen_recording_simulation"),
                "animated_card": modes.count("animated_card"),
                "kinetic_text_overlay": modes.count("kinetic_text_overlay"),
            },
            "captions": "large kinetic overlays generated by renderer; no baked text in AI clips",
            "approval_required_before_publish": True,
        },
    }

def write_asset_plans(manifests: list[dict[str, Any]], output_dir: Path) -> list[Path]:
    """Write asset plans as JSON files."""
    output_dir.mkdir(parents=True, exist_ok=True)
    paths: list[Path] = []
    for manifest in manifests:
        plan = build_asset_plan(manifest)
        path = output_dir / f"{plan['video']['id']}-{_slug(plan['video']['title'])}.json"
        path.write_text(json.dumps(plan, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
        paths.append(path)
    return paths


patch

__all__ = [
    "asset_plan",
    "growth_plan",


--- a//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/__init__.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/__init__.py
@@ -1,6 +1,7 @@
 """AutoShortsBot growth engine package."""

 __all__ = [
+    "asset_plan",
     "growth_plan",
     "preview_renderer",
     "render_manifest",


terminal

Output:
....                                                                     [100%]
4 passed in 0.04s


todo

Asset-Plan-Builder v2 implementieren


"""Generate final-render asset plans from AutoShortsBot render manifests."""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from autoshorts.asset_plan import write_asset_plans

def main() -> None:
    parser = argparse.ArgumentParser(description="Generate motion-led asset plans from render manifests.")
    parser.add_argument("--manifest-dir", default="data/render_manifests/testbatch_002_top3")
    parser.add_argument("--out", default="data/asset_plans/testbatch_002_top3")
    parser.add_argument("--ids", nargs="*", default=None)
    args = parser.parse_args()

    manifest_dir = Path(args.manifest_dir)
    manifests = []
    for path in sorted(manifest_dir.glob("*.json")):
        manifest = json.loads(path.read_text(encoding="utf-8"))
        if args.ids and manifest["video"]["id"] not in set(args.ids):
            continue
        manifests.append(manifest)

    paths = write_asset_plans(manifests, Path(args.out))
    print(f"Generated {len(paths)} asset plans in {args.out}")
    for path in paths:
        print(path)

if __name__ == "__main__":
    main()


terminal

rm -rf data/asset_plans/testbatch_002_top3
python3 scripts/generate_asset_plans.py --manifest-dir data/render_manifests/testbatch_002_top3 --out data/asset_plans/testbatch_002_top3
python3 - <<'PY'
import json
from pathlib import Path
for p in sorted(Path('data/asset_plans/testbatch_002_top3').glob('*.json')):
    data=json.loads(p.read_text())
    modes=[s['asset_mode'] for s in data['scenes']]
    print(p.name, modes, data['final_render_strategy']['preferred_mix'])
    print('scene1 prompt:', data['scenes'][0]['generation_prompt'][:220].replace('\n',' '))
PY


Output:
Generated 3 asset plans in data/asset_plans/testbatch_002_top3
data/asset_plans/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.json
data/asset_plans/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.json
data/asset_plans/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.json
ai-004-stop-asking-ai-to-think-for-you.json ['cinematic_ai_video', 'screen_recording_simulation', 'animated_card', 'screen_recording_simulation', 'kinetic_text_overlay'] {'cinematic_ai_video': 1, 'screen_recording_simulation': 2, 'animated_card': 1, 'kinetic_text_overlay': 1}
scene1 prompt: Photorealistic vertical 9:16 short video clip, a realistic creator at a desk late evening, laptop open to an AI chat, thoughtful but in control, hands moving between keyboard and notebook. Mood: focused, modern, high-ret
ai-005-the-4-box-prompt-for-mental-overload.json ['cinematic_ai_video', 'screen_recording_simulation', 'animated_card', 'screen_recording_simulation', 'kinetic_text_overlay'] {'cinematic_ai_video': 1, 'screen_recording_simulation': 2, 'animated_card': 1, 'kinetic_text_overlay': 1}
scene1 prompt: Photorealistic vertical 9:16 short video clip, a realistic creator at a desk late evening, laptop open to an AI chat, thoughtful but in control, hands moving between keyboard and notebook. Mood: focused, modern, high-ret
attention-002-the-algorithm-does-not-need-you-happy.json ['cinematic_ai_video', 'cinematic_ai_video', 'animated_card', 'screen_recording_simulation', 'kinetic_text_overlay'] {'cinematic_ai_video': 2, 'screen_recording_simulation': 1, 'animated_card': 1, 'kinetic_text_overlay': 1}
scene1 prompt: Photorealistic vertical 9:16 short video clip, a realistic person holding a phone in a dim modern room, thumb hovering over an endless feed, then a decisive pause as the screen glow reflects on their face. Mood: focused,


todo

Asset-Plan-Builder v2 implementieren


terminal

python3 -m pytest -q
git status --short
git diff --stat


Output:
................................                                         [100%]
32 passed in 1.01s
 M autoshorts/__init__.py
?? autoshorts/asset_plan.py
?? autoshorts/review_package.py
?? data/asset_plans/
?? data/telegram_reviews/
?? scripts/generate_asset_plans.py
?? scripts/generate_telegram_review_packages.py
?? tests/test_asset_plan.py
?? tests/test_review_package.py
 autoshorts/__init__.py | 1 +
 1 file changed, 1 insertion(+)


Explanation:
**Deciding on untracked files**

I’m facing a decision about an unknown untracked review package. There are 32 tests that passed, but the untracked ones are a bit strange. If I only commit our changes, the full tests afterward may still include those untracked tests. That's a complicated situation since "clean" refers to existing untracked files. I wonder if these untracked files were previous changes from the user. Ultimately, I need to ensure we only commit our own changes and then push.
**Updating skill references**

I need to make sure there are no large media files, but JSON is fine. There's also the option to update the skill after discovering that the workflow asset plan v2 should patch the skill to prevent forgetting. Maybe this is required after a complex task? I can patch the existing skill since it lacks the final AI video asset pipeline. I might also create a supporting reference file and link it to the skill update. First, though, I need to commit.

social-media-shorts-automation

# AI-video asset planning for motion-led Shorts

Use this when a shorts pipeline has scripts, shot plans, and render manifests but the user rejects static card/slideshow output.

## Durable pattern

Treat the first Pillow/FFmpeg preview as a storyboard only. The final render needs an explicit asset plan:

```text
Render manifest -> asset plan v2 -> AI/video asset generation + screen simulations -> final renderer
```

## Asset modes

Recommended per-scene `asset_mode` values:

- `cinematic_ai_video` — photorealistic 3–6s clips from Kling/Hailuo/Runway/Pika-style providers.
- `screen_recording_simulation` — real or simulated UI capture, cursor movement, typing reveal, prompt edits, zoom highlights.
- `animated_card` — moving cards/arrows/boxes for frameworks; never static paragraph cards.
- `kinetic_text_overlay` — large renderer-generated captions, hook words, CTA beats.

Avoid `static_card` in final renders. If static cards are needed for debugging, keep them in preview/storyboard only.

## Prompt rules for AI-video clips

- Always include: `photorealistic`, `vertical 9:16`, scene intent, camera motion, natural micro-movement, negative space for captions.
- Always exclude baked text: use `no readable text`, `no subtitles`, `no logos`, `no watermarks`, `no fake UI text`.
- Keep clip durations short, typically 3–6 seconds, and let the final renderer add captions separately.

## Testing checks

Add tests that assert:

- asset plans use `asset_plan.v2`
- no scene uses `static_card`
- at least one `cinematic_ai_video` scene exists for final visual energy
- screen/tutorial content uses `screen_recording_simulation`
- cinematic scenes have provider-ready prompts and `no_text_in_video=true`
- final strategy sets `static_card_limit=0`

## Pitfall

Do not mistake a storyboard preview for the final creative format. If the user expects Kling/Hailuo-quality motion, build the asset plan before polishing the local preview renderer.


File 'references/ai-video-asset-planning.md' written to skill 'social-media-shorts-automation'.


   - For the first video output, build a manifest-driven preview renderer before any AI-video or polished asset pipeline: render vertical scene/card frames from manifest fields, assemble them with FFmpeg, then use the MP4 for timing/approval review. See `references/manifest-preview-renderer.md`.
   - When the user rejects static/slideshow output or expects Kling/Hailuo-quality motion, add an `asset_plan.v2` layer that maps each scene to `cinematic_ai_video`, `screen_recording_simulation`, `animated_card`, or `kinetic_text_overlay` with provider-ready prompts and no baked text. See `references/ai-video-asset-planning.md`.


Patched SKILL.md in skill 'social-media-shorts-automation' (1 replacement).


See `references/monetization-and-approval-notes.md` for current working notes captured from official YouTube/TikTok docs and the recommended approval/posting setup. See `references/retention-led-shorts.md` for retention scoring signals, no-faith/adjacent-pillar handling, and voiceover finalization rules. See `references/render-manifest-pipeline.md` for the script → shot plan → render manifest → renderer sequence that prevents generic AI slideshow output. See `references/manifest-preview-renderer.md` for the first deterministic Pillow/FFmpeg preview-renderer pattern. See `references/ai-video-asset-planning.md` for motion-led final-render asset planning with Kling/Hailuo/Runway-style clips and screen simulations. Re-check official docs before making business-critical decisions because platform programs change often.

See `references/monetization-and-approval-notes.md` for current working notes captured from official YouTube/TikTok docs and the recommended approval/posting setup. See `references/retention-led-shorts.md` for retention scoring signals, no-faith/adjacent-pillar handling, and voiceover finalization rules. See `references/render-manifest-pipeline.md` for the script → shot plan → render manifest → renderer sequence that prevents generic AI slideshow output. See `references/manifest-preview-renderer.md` for the first deterministic Pillow/FFmpeg preview-renderer pattern. Re-check official docs before making business-critical decisions because platform programs change often.


terminal

set -euo pipefail
git add autoshorts/asset_plan.py autoshorts/__init__.py scripts/generate_asset_plans.py tests/test_asset_plan.py data/asset_plans/testbatch_002_top3
git commit -m "feat: add motion-led asset planning"


Output:
[feat/initial-growth-engine b60981b] feat: add motion-led asset planning
 7 files changed, 600 insertions(+)
 create mode 100644 autoshorts/asset_plan.py
 create mode 100644 data/asset_plans/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.json
 create mode 100644 data/asset_plans/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.json
 create mode 100644 data/asset_plans/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.json
 create mode 100644 scripts/generate_asset_plans.py
 create mode 100644 tests/test_asset_plan.py


set -euo pipefail
export GITHUB_TOKEN=$(tr -d '\n\r' < /home/agent/.hermes/secrets/github_autoshortsbot_token)
ASKPASS=$(mktemp)
cat > "$ASKPASS" <<'SH'
#!/bin/sh
case "$1" in
  *Username*) printf '%s\n' x-access-token ;;
  *Password*) printf '%s\n' "$GITHUB_TOKEN" ;;
  *) printf '\n' ;;
esac
SH
chmod 700 "$ASKPASS"
trap 'rm -f "$ASKPASS"' EXIT
GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 git push >/tmp/autoshorts_push9.log 2>&1 || { sed -E 's/(github_pat_|ghp_)[A-Za-z0-9_]+/[REDACTED]/g' /tmp/autoshorts_push9.log; exit 1; }
BRANCH=$(git branch --show-current)
LOCAL=$(git rev-parse HEAD)
REMOTE=$(GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 git ls-remote origin "refs/heads/$BRANCH" | awk '{print $1}')
printf 'branch=%s\nlocal=%s\nremote=%s\n' "$BRANCH" "$LOCAL" "$REMOTE"
test "$LOCAL" = "$REMOTE"
sed -E 's/(github_pat_|ghp_)[A-Za-z0-9_]+/[REDACTED]/g' /tmp/autoshorts_push9.log


Output:
branch=feat/initial-growth-engine
local=b60981bd2383f508676993191afb4c04c636c43f
remote=b60981bd2383f508676993191afb4c04c636c43f
To https://github.com/Gamexgit/AutoShortsBot.git
   3cc201b..b60981b  feat/initial-growth-engine -> feat/initial-growth-engine


git status --short --branch && python3 -m pytest -q
python3 - <<'PY'
import json
from pathlib import Path
p=Path('data/asset_plans/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.json')
data=json.loads(p.read_text())
print('asset_plan', p)
for s in data['scenes']:
    print(s['scene_id'], s['asset_mode'], s['duration_seconds'], s['provider_family'])
PY


Output:
## feat/initial-growth-engine...origin/feat/initial-growth-engine
?? autoshorts/review_package.py
?? data/telegram_reviews/
?? scripts/generate_telegram_review_packages.py
?? tests/test_review_package.py
................................                                         [100%]
32 passed in 0.96s
asset_plan data/asset_plans/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.json
scene_01 cinematic_ai_video 3 kling_hailuo_runway
scene_02 screen_recording_simulation 5 local_renderer
scene_03 animated_card 6 local_renderer
scene_04 screen_recording_simulation 6 local_renderer
scene_05 kinetic_text_overlay 6 local_renderer


todo

Asset-Plan-Builder v2 implementieren


Erledigt, Sir.

Ich habe die Richtung jetzt bewusst geändert: **weg vom Card-/Storyboard-Renderer als vermeintliches Endprodukt**, hin zu einer **motion-led Asset Pipeline v2**.

## Gebaut: Asset Plan v2

Neue Komponente:

```text
autoshorts/asset_plan.py
```

Neues CLI:

```text
scripts/generate_asset_plans.py
```

Neue Tests:

```text
tests/test_asset_plan.py
```

Generierte Asset-Pläne:

```text
data/asset_plans/testbatch_002_top3/
  ai-004-stop-asking-ai-to-think-for-you.json
  ai-005-the-4-box-prompt-for-mental-overload.json
  attention-002-the-algorithm-does-not-need-you-happy.json
```

## Was sich dadurch ändert

Der Preview-Renderer bleibt jetzt explizit nur:

```text
Storyboard / Timing Preview
```

Nicht finaler Content.

Der neue Asset Plan entscheidet pro Szene:

```text
cinematic_ai_video
screen_recording_simulation
animated_card
kinetic_text_overlay
```

Und setzt:

```text
static_card_limit = 0
```

Also: **keine finalen statischen Textkarten**.

## Beispiel `ai-004`

Der finale Render-Plan für `ai-004` ist jetzt:

```text
scene_01 → cinematic_ai_video          3s  Kling/Hailuo/Runway-style
scene_02 → screen_recording_simulation 5s  local renderer
scene_03 → animated_card               6s  local renderer
scene_04 → screen_recording_simulation 6s  local renderer
scene_05 → kinetic_text_overlay        6s  local renderer
```

Damit wäre das Endprodukt nicht mehr:

```text
statische Bilder + Text
```

sondern:

```text
photorealistische Clip-Sequenz
+ UI-/Screen-Recording-Simulation
+ animierte Cards
+ große kinetic captions
+ finaler Schnitt
```

## Provider-ready Prompting

Für cinematic Szenen erzeugt der Plan jetzt direkt Prompt-Felder wie:

```text
Photorealistic vertical 9:16 short video clip...
cinematic lighting...
realistic motion...
Leave clean negative space for captions...
do not include readable text in the generated video.
```

Plus Negative Prompt:

```text
no readable text
no subtitles
no logos
no watermarks
no fake UI text
no slideshow
no static image
no uncanny faces
```

Das ist genau wichtig, weil Kling/Hailuo/Runway sonst gerne Text, Logos oder kaputte UI in den Clip backen — sehr nett von ihnen, aber leider meistens Müll.

## Für `attention-002`

Der Plan nutzt mehr visuelle Bewegung:

```text
cinematic_ai_video
cinematic_ai_video
animated_card
screen_recording_simulation
kinetic_text_overlay
```

Also eher:

- Person mit Phone / Feed / Licht auf Gesicht
- Scroll-/Pause-Moment
- Incentive-Vergleich als animierte Card
- Home-screen cleanup
- CTA als kinetic caption

## Tests / Push

TDD durchgeführt:

- RED: fehlendes `autoshorts.asset_plan`
- GREEN: Asset Plan Builder
- Full suite:

```text
32 passed in 0.96s
```

Neuer Commit:

```text
b60981b feat: add motion-led asset planning
```

Branch:

```text
feat/initial-growth-engine
```

Remote verifiziert:

```text
local == remote
```

PR-Link bleibt:

https://github.com/Gamexgit/AutoShortsBot/pull/new/feat/initial-growth-engine

## Hinweis

Im Working Tree liegen noch untracked Dateien zu einem separaten Telegram-Review-Package:

```text
autoshorts/review_package.py
data/telegram_reviews/
scripts/generate_telegram_review_packages.py
tests/test_review_package.py
```

Die habe ich nicht angefasst und nicht in diesen Commit aufgenommen, damit wir keine fremden/parallel entstandenen Änderungen vermischen. Saubere Buchführung, wie langweilig — und deshalb nützlich.

## Nächster sinnvoller Schritt

Jetzt als nächstes:

```text
Asset Plan v2
→ AI-video prompt export package
→ Clip placeholders / provider job specs
→ final renderer liest echte Clip-Dateien statt Cards
```

Praktisch: Ich baue als Nächstes einen **provider job exporter**, der aus dem Asset Plan für jede `cinematic_ai_video`-Szene saubere Kling/Hailuo/Runway-Job-Specs erzeugt. Danach können wir entweder manuell Clips erzeugen oder später API-Integration anschließen.


[M G] ok bau weiter. den separaten Working tree kannst du ignorieren, ich habe ausversehen in den Falschen telegram-kanal geschrieben.  Hier noch einige ideen, wie unser content aussehen könnte:. Wir sollten nicht “KI-Video-Content” bauen, sondern ein halbautomatisches “Retention-Studio” für Shorts. Der Unterschied ist wichtig: Nicht der KI-Look zählt, sondern ob die ersten 1–2 Sekunden einen offenen Loop erzeugen, ob alle 1–2 Sekunden visuell etwas passiert und ob der Zuschauer am Ende denkt: „Davon will ich mehr.“
Deine Beobachtung ist absolut richtig: statische KI-Bilder + KI-Voiceover + “Was wäre wenn…” ist bereits KI-Müll-Massenware. Damit gewinnt man heute nur noch zufällig oder kurzfristig.
Meine klare Empfehlung
Der beste Ansatz für Hermes wäre:
KI-generierte, animierte Erklärvideos im Stil von “mechanism reveal / visual explanation / mini-investigation” – aber mit eigener Nische, eigener Dramaturgie und eigener Datenbasis.
Also nicht:
“Was wäre, wenn ein Römer heute ein iPhone sieht?”
Sondern eher:
“Warum du beim Autokauf fast immer gegen einen versteckten Algorithmus verlierst.”
“Warum ein kleines Bauprojekt plötzlich 1 Million teurer wird.”
“Warum Shorts dich nicht wegen dem Inhalt halten, sondern wegen offenen Loops.”
“Wie ein Betrug funktioniert – Frame für Frame erklärt.”
“Warum manche KI-Agenten Geld verbrennen, bevor sie produktiv werden.”
Das wäre näher an @zackdfilms, aber nicht als Kopie. Zack D. Films hat laut YouTube aktuell rund 27 Mio. Abonnenten und macht kurze animierte Erklärvideos mit sehr klarem visuellen Payoff.   Genau daran ist spannend: Es ist nicht einfach Voiceover. Es ist visuelles Verständnis.
Was wir von schnell wachsenden Kanälen adaptieren sollten
Nicht die Themen kopieren. Sondern die Mechaniken.
1. Zack-D-Films-Mechanik: “Zeig den unsichtbaren Mechanismus”
Das Erfolgsprinzip ist:
Hook → Objekt/Problem → Schnitt ins Innere → Gefahr/Überraschung → Auflösung
Beispiel für deinen Kanal:
Hook:
“Dieser Occasion-Käufer denkt, er spart 4’000 Franken. In Wirklichkeit verliert er gerade Geld.”
Visual:
3D-Auto, Preisschild, Käufer klickt, dann “Röntgenblick” ins Auto: Wertverlust, Reparaturrisiko, Standtage, Händler-Marge, Finanzierung.
Payoff:
“Der teuerste Fehler war nicht der Kaufpreis. Es war der Zeitpunkt.”
Das ist viel stärker als ein Avatar, der in die Kamera redet.
2. MrBeast-/Jenny-Hoyos-Mechanik: “Sofortiges Versprechen”
Jedes Video braucht in den ersten 1–2 Sekunden ein klares Versprechen:
“In 20 Sekunden verstehst du, warum…”
“Das sieht harmlos aus, aber…”
“Die meisten sehen nur X. Die Wahrheit ist Y.”
“Dieser eine Fehler kostet…”
Nicht: “Heute erklären wir…”
3. TikTok-Creative-Code-Mechanik: Struktur, Stimulation, Sound
TikTok selbst empfiehlt für performante Videos eine klare Struktur aus Hook, Body, Close, dynamisches Editing, Bewegung, Text-Overlays und Sound/Voice/SFX als Aufmerksamkeitstreiber.   Das ist genau das Gegenteil von statischen KI-Bildern.
Avatare: Ja, aber nicht als Hauptformat
Realistische KI-Avatare, die sprechen, sind verführerisch. Aber ich würde sie nicht als Kernformat nehmen.
Warum?
Erstens wirken viele Avatare nach 3 Sekunden generisch. Zweitens erzeugt ein sprechender Avatar selten hohe Retention, wenn er nur erklärt. Drittens gibt es Policy- und Vertrauensrisiken, wenn realistische KI-Menschen wie echte Experten wirken. YouTube verlangt Disclosure, wenn realistische Inhalte mit synthetischen Medien erstellt wurden; bei sensiblen Themen wie Finanzen, Gesundheit, News oder Wahlen kann ein Label auch direkt im Video erscheinen.
Besser: Avatar nur als wiederkehrende Nebenfigur. Zum Beispiel:
“JARVIS scannt den Fall.”
Dann sieht man kurz einen KI-Analysten, aber der Hauptteil ist animierte Visualisierung: Diagramme, Objekte, Simulationen, Screenshots, Daten, 3D-Szenen.
Re-Kuratieren erfolgreicher Videos: Ja, aber vorsichtig
Das ist strategisch stark, aber es darf nicht “kopieren und neu vertonen” werden.
YouTube sagt klar: Reused Content ist problematisch, wenn man vorhandenes Material ohne deutliche eigene Kommentare, substanzielle Änderung oder eigenen Bildungs-/Unterhaltungswert wiederverwendet. Erlaubt ist Wiederverwendung eher dann, wenn ein klarer eigener Spin, Storyline, Kommentar oder substanzielle Bearbeitung dazukommt.
Für Hermes heisst das:
Erlaubt / sinnvoll:
erfolgreiche Videos analysieren
Hook-Struktur extrahieren
Thema neu recherchieren
eigene Daten/Quellen verwenden
eigene Animation generieren
eigene These / eigene Auflösung bauen
Nicht sinnvoll:
Video herunterladen
mit KI nachbauen
leicht umschreiben
gleiche Szenenfolge
gleiche Pointe
Das wäre riskant, austauschbar und langfristig nicht monetarisierungssicher. YouTube betont ausserdem, dass Content original/authentisch sein soll und nicht massenproduziert oder repetitiv wirken darf.
Das Format, das ich für dich bauen würde
Ich würde einen Kanal nicht als “KI erklärt…” positionieren, sondern als:
“Hidden Systems” / “Unsichtbare Mechanismen”
Thema: Dinge, die Menschen täglich betreffen, aber nicht verstehen.
Visuell: 3D-Animation, Daten, Simulationen, Röntgenblick, UI-Animationen, Mini-Szenen.
Stimme: hochwertige menschlich klingende Voice, aber nicht lahmer AI-Narrator.
Länge: 22–38 Sekunden.
Frequenz: 2–4 Shorts pro Tag testen, aber nur mit klaren Qualitätsregeln.
Die besten Content-Säulen für dich
1.
Geldfallen & Alltagsökonomie
Sehr gut monetarisierbar, stark für Follow.
Beispiele:
“Warum du bei Leasing fast immer die falsche Zahl vergleichst”
“Warum 0%-Finanzierung nicht gratis ist”
“Warum der Autohändler deinen alten Wagen anders bewertet als du”
“Wie 100 Franken pro Monat heimlich zu 30’000 Franken werden”
“Warum Krankenkassenprämien psychologisch perfekt weh tun”
2.
Auto- & Handels-Mechaniken
Passt zu deinem Autotrading-Script und kann später ein Produkt/Tool stützen.
Beispiele:
“Der Moment, in dem ein Auto unverkäuflich wird”
“Warum manche Autos 3 Tage online sind und andere 90”
“So erkennt ein Algorithmus einen schlechten Occasion-Deal”
“Warum ein tiefer Preis manchmal ein Warnsignal ist”
“Der unsichtbare Kampf zwischen Käufer, Händler und Plattform”
3.
KI-Agenten realistisch erklärt
Hier kannst du dich positiv vom Hype unterscheiden.
Beispiele:
“Warum KI-Agenten oft scheitern, obwohl sie schlau wirken”
“Was passiert, wenn ein Agent ohne Kontrolle tradet”
“Der Unterschied zwischen Chatbot und echtem Agenten”
“Warum dein KI-Agent zuerst langweilige Arbeit lernen muss”
“Wie ein Agent dein Leben organisieren könnte – aber nur mit Regeln”
4.
Schweiz erklärt als System
Das wäre deine lokale Differenzierung. Weniger Konkurrenz, höhere Glaubwürdigkeit.
Beispiele:
“Warum ein Gemeindekredit plötzlich Millionen kostet”
“Wie ein Einwohnerrat wirklich entscheidet”
“Warum Bauen in der Schweiz so teuer ist”
“Warum ein Bahnhofprojekt 10 Jahre dauern kann”
“Wie Steuergeld durch politische Prozesse fliesst”
5.
Gefährliche Denkfehler
Sehr stark für Retention, weil es persönlich relevant ist.
Beispiele:
“Warum dein Gehirn schlechte Deals schönrechnet”
“Warum du Verlusten hinterherrennst”
“Warum Menschen schlechten News mehr glauben”
“Warum ‘ich weiss es besser’ teuer wird”
“Warum Charts dich zum falschen Zeitpunkt überzeugen”
Mein Favorit: “Mechanism Explainer Shorts”
Die Formel:
0.0–1.5 Sek.: Pattern Interrupt
Ein Objekt explodiert, fällt, wird durchleuchtet, friert ein oder verwandelt sich.
1.5–5 Sek.: Problem
“Das sieht nach einem guten Deal aus.”
5–18 Sek.: Mechanismus
Röntgenblick / Animation / Datenlayer / Mini-Simulation.
18–28 Sek.: Twist
“Aber der Preis ist nicht das Problem. Die Liquidität ist das Problem.”
28–35 Sek.: Follow-Trigger
“Folge, wenn du versteckte Systeme sehen willst, bevor sie dich Geld kosten.”
Das gibt Hermes eine klare Produktionslogik.
Technischer Hermes-Workflow
Hermes sollte nicht einfach “poste jeden Tag 10 Videos” machen. Das endet in KI-Müll. Besser:
Phase 1: Trend- und Format-Mining
Hermes sammelt täglich:
50 erfolgreiche Shorts aus 5–10 Zielnischen
Hook-Typ
Videolänge
Schnittrhythmus
visueller Stil
Kommentar-Trigger
Like/View-Verhältnis, soweit sichtbar
Wiederholbare Formate
Dann klassifiziert er:
Mechanism Reveal
Mistake Breakdown
Hidden Cost
Before/After
Myth vs Reality
Simulation
“Nobody notices this…”
“This tiny detail changes everything…”
Phase 2: Ideen-Scoring
Jede Idee bekommt einen Score:
persönlicher Nutzen
visueller Mechanismus
Überraschung
Datenbezug
Kommentarpotenzial
Serienfähigkeit
Monetarisierungspotenzial
Produktionsaufwand
Nur Ideen mit hohem Score werden produziert.
Phase 3: Skript mit Retention-Logik
Hermes erstellt nicht einfach Text, sondern ein Shot-by-Shot-Skript:
Zeit
Bild
Text
Bewegung
Sound
0–1.5s
Auto mit rotem Warnsignal
“Dieser Deal sieht gut aus…”
Kamera-Zoom
Bass-Hit
1.5–4s
Preis fällt
“…bis du den versteckten Fehler siehst.”
Freeze + Pfeil
Whoosh
4–12s
Röntgenblick Auto
“Der Preis ist nur eine Schicht.”
Layers erscheinen
Clicks
12–24s
Markt-/Standtage-Simulation
“Das Risiko steckt hier.”
Graph bewegt sich
Tension
24–32s
Auflösung
“Nicht kaufen, wenn…”
Snap to conclusion
Drop
Phase 4: Video-Generierung
Nicht mit einem einzigen Tool, sondern modular:
Blender / Runway / Kling / Veo / Hailuo für Szenen
After Effects / CapCut Templates / Remotion für dynamische Overlays
ElevenLabs / hochwertiges TTS für Voice
SFX-Library für Pattern Interrupts
Hermes für Schnittlogik, Titel, Beschreibung, Posting
Der eigentliche Qualitätshebel ist nicht das KI-Video-Tool. Es ist die visuelle Regie.
Ein konkretes Pilotformat
Ich würde mit dieser Serie starten:
“Unsichtbare Systeme in 30 Sekunden”
Claim:
“Wir zeigen Dinge, die dich Geld, Zeit oder Kontrolle kosten – bevor du sie bemerkst.”
Erste 10 Videos:
“Warum ein billiges Auto teuer sein kann”
“Warum 0%-Finanzierung nicht kostenlos ist”
“Warum KI-Agenten ohne Regeln gefährlich werden”
“Warum ein Bauprojekt fast nie beim ersten Preis bleibt”
“Warum Shorts dich nach 1 Sekunde verlieren”
“Warum Menschen schlechten Deals zustimmen”
“Warum Banken Gebühren verstecken können, ohne sie zu verstecken”
“Warum ein Occasion-Preis manchmal zu tief ist”
“Warum politische Projekte so langsam wirken”
“Warum dein Gehirn Verluste schlimmer bewertet als Gewinne”
Das ist hochwertiger als “Was wäre wenn…”, weil es echte Probleme erklärt.
Follow-Trigger, der funktionieren kann
Nicht am Ende sagen:
“Like und Follow für mehr.”
Sondern:
“Folge, wenn du versteckte Kosten sehen willst, bevor sie dich treffen.”
“Ich zeige jeden Tag ein System, das Menschen falsch verstehen.”
“Morgen: warum Leasing psychologisch perfekt gebaut ist.”
“Teil 2 zeigt den Trick, den fast niemand berechnet.”
Der Follow entsteht durch Serienversprechen, nicht durch Bitte.
Was ich nicht machen würde
Ich würde keinen reinen KI-Avatar-Kanal bauen.
Ich würde keinen Zack-D-Films-Klon bauen.
Ich würde keine fremden Videos recyceln.
Ich würde keinen generischen Wissenskanal machen.
Ich würde auch nicht “maximal automatisieren” als Hauptziel setzen. Besser:
80 % automatisiert, 20 % menschliche Qualitätskontrolle.
Diese 20 % entscheiden, ob es nach wertvoller Marke aussieht oder nach KI-Fabrik.
Beste strategische Positionierung
Für dich sehe ich drei starke Richtungen:
Richtung
Reichweitenpotenzial
Monetarisierung
Differenzierung
Hidden Money Systems
hoch
sehr hoch
gut
AI Agents Explained
mittel–hoch
sehr hoch
sehr gut
Swiss Systems Explained
mittel
mittel–hoch
sehr stark lokal
General Mystery/History
hoch
mittel
schwach, viel Konkurrenz
KI-Avatar-News
mittel
mittel
riskant/generisch
Meine Wahl wäre:
Hidden Money & AI Systems
Warum?
Es verbindet deine bestehenden Projekte: Hermes, Autotrading, Finanzen, KI-Agenten, Politik/Systemverständnis. Es ist nicht nur Reichweiten-Content, sondern kann später in Newsletter, Tools, Beratung, Affiliate, Software oder Kurse führen.
Minimaler Startplan für Hermes
Woche 1:
30 erfolgreiche Shorts analysieren, davon 10 von Zack-D-Films-artigen Explainern, 10 Money-/Business-Shorts, 10 AI-/Tech-Shorts.
Woche 2:
20 eigene Skripte erstellen, aber nur 5 produzieren.
Woche 3:
Täglich 2 Shorts posten, jeweils mit unterschiedlichen Hook-Typen.
Woche 4:
Nur nach Daten entscheiden:
Retention nach 1 Sekunde
Retention nach 3 Sekunden
durchschnittliche Wiedergabedauer
Wiederholungen
Kommentare pro 1’000 Views
Follows pro 1’000 Views
Dann skaliert Hermes nur die Gewinnerformate.
Mein Urteil
Der stärkste Weg ist:
Nicht KI-Avatar. Nicht statische Bilder. Nicht “Was wäre wenn”. Sondern animierte, datenbasierte Mechanismus-Erklärvideos mit starkem Hook, klarer visueller Bewegung und echtem Nutzen.
Also: Zack-D-Films-Prinzip + Money/System/AI-Nische + Hermes-Retention-Analyse.
Das kann sich deutlich von KI-Müll unterscheiden, weil jedes Video eine echte Frage beantwortet und visuell etwas zeigt, das man vorher nicht verstanden hat. / evtl. kann man das konzept auch zusätzlich auf andere bereiche anwenden…


Explanation:
**Building a strategy for retention studio**

I’m thinking we need to act here and possibly build the concept into the repository. First, I should check what's untracked since the user mentioned ignoring a separate working tree, which I can leave. I also need to implement the retention studio concept, including strategy configuration, format taxonomy, scoring, and an explainer spec. I definitely want to use Test-Driven Development (TDD), creating tests for the mechanism's format and content pillars within the repo.

Neue Retention-Studio-Anforderungen in Projektstruktur einordnen

TDD: Tests für Mechanism-Explainer-Format, Pillars und Scoring schreiben

Retention-Studio-Strategiemodul implementieren

Pilot-Ideen/Format-Config generieren und prüfen

Tests, Commit, Push und Remote-Verifikation


terminal

git status --short --branch && python3 -m pytest -q


Output:
## feat/initial-growth-engine...origin/feat/initial-growth-engine
?? autoshorts/review_package.py
?? data/telegram_reviews/
?? scripts/generate_telegram_review_packages.py
?? tests/test_review_package.py
................................                                         [100%]
32 passed in 1.05s


from autoshorts.retention_studio import (
    PILOT_IDEAS,
    build_mechanism_explainer,
    build_retention_studio_strategy,
    score_idea,
)

def test_strategy_positions_channel_as_retention_studio_not_ai_video_factory():
    strategy = build_retention_studio_strategy()

    assert strategy["positioning"] == "Hidden Money & AI Systems"
    assert strategy["operating_model"] == "semi_automatic_retention_studio"
    assert strategy["not_a"] == ["ki_avatar_news", "static_ai_slideshow", "reused_content_clone"]
    assert "mechanism_reveal" in strategy["format_taxonomy"]
    assert strategy["quality_gates"]["static_card_limit"] == 0
    assert strategy["quality_gates"]["human_review_percent"] == 20

def test_mechanism_explainer_has_required_retention_beats_and_visual_motion():
    idea = PILOT_IDEAS[0]

    explainer = build_mechanism_explainer(idea)

    assert explainer["series"] == "Unsichtbare Systeme in 30 Sekunden"
    assert [beat["beat"] for beat in explainer["beats"]] == [
        "pattern_interrupt",
        "problem",
        "mechanism",
        "twist",
        "follow_trigger",
    ]
    assert explainer["beats"][0]["start"] == 0.0
    assert explainer["beats"][0]["end"] <= 1.5
    assert all(beat["visual_motion"] for beat in explainer["beats"])
    assert all(beat["sound_cue"] for beat in explainer["beats"])
    assert "Follow" not in explainer["follow_trigger"]
    assert "versteckte" in explainer["follow_trigger"].lower()

def test_idea_scoring_rewards_visual_mechanism_and_monetization():
    strong = score_idea(PILOT_IDEAS[0])
    weak = score_idea(
        {
            "title": "What if AI ruled the world",
            "pillar": "general_mystery_history",
            "visual_mechanism": "generic montage",
            "surprise": 4,
            "data_basis": 1,
            "comment_potential": 3,
            "series_potential": 2,
            "monetization_potential": 2,
            "production_effort": 8,
        }
    )

    assert strong["total"] >= 80
    assert strong["decision"] == "produce"
    assert weak["total"] < 60
    assert weak["decision"] == "reject"

def test_pilot_ideas_cover_money_ai_swiss_and_cognitive_systems():
    pillars = {idea["pillar"] for idea in PILOT_IDEAS}

    assert "hidden_money_systems" in pillars
    assert "ai_agents_explained" in pillars
    assert "swiss_systems_explained" in pillars
    assert "dangerous_thinking_errors" in pillars
    assert len(PILOT_IDEAS) == 10


LSP diagnostics introduced by this edit:
<diagnostics file="/home/agent/jarvis_runtime/AutoShortsBot/tests/test_retention_studio.py">
ERROR [1:6] Import "autoshorts.retention_studio" could not be resolved [reportMissingImports] (Pyright)
</diagnostics>


python3 -m pytest tests/test_retention_studio.py -q


Output:
==================================== ERRORS ====================================
_______________ ERROR collecting tests/test_retention_studio.py ________________
ImportError while importing test module '/home/agent/jarvis_runtime/AutoShortsBot/tests/test_retention_studio.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_retention_studio.py:1: in <module>
    from autoshorts.retention_studio import (
E   ModuleNotFoundError: No module named 'autoshorts.retention_studio'
=========================== short test summary info ============================
ERROR tests/test_retention_studio.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.12s


"""Retention Studio strategy for mechanism-explainer Shorts.

This module captures the strategic shift from generic AI-video output to a
semi-automatic retention studio: format mining, idea scoring, mechanism-reveal
scripts, motion-led visuals, and human quality gates.
"""

from __future__ import annotations

from typing import Any

PILOT_IDEAS: list[dict[str, Any]] = [
    {
        "title": "Warum ein billiges Auto teuer sein kann",
        "pillar": "hidden_money_systems",
        "hook": "Dieser Occasion-Käufer denkt, er spart 4'000 Franken.",
        "mechanism": "Röntgenblick durch Preis, Reparaturrisiko, Standtage, Händler-Marge und Liquidität.",
        "twist": "Der teuerste Fehler ist nicht der Kaufpreis, sondern der Zeitpunkt.",
        "visual_mechanism": "3D car x-ray with price tag layers and risk meters",
        "surprise": 9,
        "data_basis": 8,
        "comment_potential": 8,
        "series_potential": 9,
        "monetization_potential": 10,
        "production_effort": 6,
    },
    {
        "title": "Warum 0%-Finanzierung nicht kostenlos ist",
        "pillar": "hidden_money_systems",
        "hook": "0 Prozent klingt gratis. Genau deshalb funktioniert es.",
        "mechanism": "Cash price, margin, fees, lock-in and opportunity cost split into layers.",
        "twist": "Die Zinsen verschwinden nicht. Sie verstecken sich nur im System.",
        "visual_mechanism": "contract transforms into hidden cost layers",
        "surprise": 8,
        "data_basis": 7,
        "comment_potential": 8,
        "series_potential": 9,
        "monetization_potential": 10,
        "production_effort": 5,
    },
    {
        "title": "Warum KI-Agenten ohne Regeln gefährlich werden",
        "pillar": "ai_agents_explained",
        "hook": "Dieser Agent wirkt produktiv. Bis er ohne Kontrolle Geld verbrennt.",
        "mechanism": "Goal, tool access, missing approval gates, runaway loops and audit trail visualized as a circuit.",
        "twist": "Der Fehler ist nicht Intelligenz. Der Fehler ist fehlende Begrenzung.",
        "visual_mechanism": "agent control loop with approval gate and red runaway path",
        "surprise": 8,
        "data_basis": 8,
        "comment_potential": 9,
        "series_potential": 10,
        "monetization_potential": 10,
        "production_effort": 5,
    },
    {
        "title": "Der Unterschied zwischen Chatbot und echtem Agenten",
        "pillar": "ai_agents_explained",
        "hook": "Ein Chatbot antwortet. Ein Agent verändert Dinge.",
        "mechanism": "Intent, tools, memory, approvals and execution are revealed as separate layers.",
        "twist": "Autonomie beginnt nicht beim Modell, sondern beim Zugriff.",
        "visual_mechanism": "split-screen chatbot bubble vs agent toolchain",
        "surprise": 7,
        "data_basis": 8,
        "comment_potential": 8,
        "series_potential": 10,
        "monetization_potential": 9,
        "production_effort": 4,
    },
    {
        "title": "Warum ein Bauprojekt fast nie beim ersten Preis bleibt",
        "pillar": "swiss_systems_explained",
        "hook": "Dieser Baukredit sieht fix aus. Dann beginnt die Schweiz.",
        "mechanism": "Permits, objections, procurement, inflation, scope creep and political process stack over time.",
        "twist": "Die Überraschung ist nicht der Mehrpreis. Es ist die Anzahl unsichtbarer Schleifen.",
        "visual_mechanism": "construction budget timeline with process gates and cost bubbles",
        "surprise": 8,
        "data_basis": 8,
        "comment_potential": 7,
        "series_potential": 9,
        "monetization_potential": 7,
        "production_effort": 7,
    },
    {
        "title": "Wie ein Einwohnerrat wirklich entscheidet",
        "pillar": "swiss_systems_explained",
        "hook": "Das sieht wie eine Abstimmung aus. Eigentlich ist es ein Filtersystem.",
        "mechanism": "Motion, committee, budget, party positions, amendments and final vote visualized as a flow.",
        "twist": "Die Entscheidung fällt oft, bevor die Schlussabstimmung beginnt.",
        "visual_mechanism": "council chamber turns into decision flowchart",
        "surprise": 7,
        "data_basis": 8,
        "comment_potential": 7,
        "series_potential": 8,
        "monetization_potential": 6,
        "production_effort": 6,
    },
    {
        "title": "Warum dein Gehirn schlechte Deals schönrechnet",
        "pillar": "dangerous_thinking_errors",
        "hook": "Dein Gehirn merkt diesen schlechten Deal. Dann verteidigt es ihn.",
        "mechanism": "Sunk cost, confirmation bias and loss aversion appear as competing brain layers.",
        "twist": "Du kaufst nicht den Deal. Du kaufst die Rechtfertigung.",
        "visual_mechanism": "brain courtroom where biases argue over a bad deal",
        "surprise": 9,
        "data_basis": 7,
        "comment_potential": 9,
        "series_potential": 10,
        "monetization_potential": 8,
        "production_effort": 5,
    },
    {
        "title": "Warum du Verlusten hinterherrennst",
        "pillar": "dangerous_thinking_errors",
        "hook": "Der Verlust ist vorbei. Trotzdem zahlst du weiter.",
        "mechanism": "Loss aversion, sunk cost and identity protection visualized as a trap door.",
        "twist": "Du rettest nicht dein Geld. Du rettest dein altes Ich.",
        "visual_mechanism": "money trail into trap with brain pulling it back",
        "surprise": 8,
        "data_basis": 7,
        "comment_potential": 8,
        "series_potential": 9,
        "monetization_potential": 8,
        "production_effort": 5,
    },
    {
        "title": "Warum Shorts dich nicht wegen dem Inhalt halten",
        "pillar": "hidden_attention_systems",
        "hook": "Du bleibst nicht wegen dem Thema. Du bleibst wegen der offenen Schleife.",
        "mechanism": "Hook, open loop, pattern interrupt, payoff delay and reward pulse shown as timeline layers.",
        "twist": "Retention ist oft Architektur, nicht Interesse.",
        "visual_mechanism": "shorts timeline with dopamine gates and loop markers",
        "surprise": 9,
        "data_basis": 8,
        "comment_potential": 9,
        "series_potential": 10,
        "monetization_potential": 8,
        "production_effort": 5,
    },
    {
        "title": "Warum Banken Gebühren verstecken können, ohne sie zu verstecken",
        "pillar": "hidden_money_systems",
        "hook": "Diese Gebühr ist sichtbar. Genau deshalb übersiehst du sie.",
        "mechanism": "Statement line items, percentage framing, monthly smoothing and comparison blindness.",
        "twist": "Das Versteck ist nicht Geheimhaltung. Es ist Normalisierung.",
        "visual_mechanism": "bank statement zooms into visible-but-ignored fee layers",
        "surprise": 8,
        "data_basis": 7,
        "comment_potential": 8,
        "series_potential": 9,
        "monetization_potential": 9,
        "production_effort": 5,
    },
]

def build_retention_studio_strategy() -> dict[str, Any]:
    """Return the strategic operating model for the channel."""
    return {
        "positioning": "Hidden Money & AI Systems",
        "series": "Unsichtbare Systeme in 30 Sekunden",
        "claim": "Wir zeigen Dinge, die dich Geld, Zeit oder Kontrolle kosten – bevor du sie bemerkst.",
        "operating_model": "semi_automatic_retention_studio",
        "not_a": ["ki_avatar_news", "static_ai_slideshow", "reused_content_clone"],
        "core_formula": [
            "0.0-1.5s pattern interrupt",
            "1.5-5s problem",
            "5-18s mechanism reveal",
            "18-28s twist",
            "28-35s follow trigger",
        ],
        "format_taxonomy": [
            "mechanism_reveal",
            "mistake_breakdown",
            "hidden_cost",
            "before_after",
            "myth_vs_reality",
            "mini_simulation",
            "nobody_notices_this",
        ],
        "content_pillars": [
            "hidden_money_systems",
            "auto_trade_mechanics",
            "ai_agents_explained",
            "swiss_systems_explained",
            "dangerous_thinking_errors",
        ],
        "quality_gates": {
            "static_card_limit": 0,
            "first_two_seconds_must_open_loop": True,
            "visual_change_every_seconds": 2,
            "human_review_percent": 20,
            "reused_content_allowed": False,
            "synthetic_media_disclosure_required_when_realistic": True,
        },
        "metrics": [
            "retention_1s",
            "retention_3s",
            "average_view_duration",
            "rewatches",
            "comments_per_1000_views",
            "follows_per_1000_views",
        ],
    }

def score_idea(idea: dict[str, Any]) -> dict[str, Any]:
    """Score an idea for production priority."""
    weights = {
        "surprise": 1.6,
        "data_basis": 1.2,
        "comment_potential": 1.2,
        "series_potential": 1.4,
        "monetization_potential": 1.4,
    }
    weighted = sum(float(idea.get(key, 0)) * weight for key, weight in weights.items())
    visual_bonus = 12 if _has_specific_visual_mechanism(str(idea.get("visual_mechanism", ""))) else 0
    effort_penalty = max(0, float(idea.get("production_effort", 5)) - 5) * 2
    total = round(min(100, weighted / sum(weights.values()) * 8.8 + visual_bonus - effort_penalty))
    if total >= 80:
        decision = "produce"
    elif total >= 65:
        decision = "develop"
    else:
        decision = "reject"
    return {"total": total, "decision": decision}

def _has_specific_visual_mechanism(visual_mechanism: str) -> bool:
    generic_terms = {"generic montage", "talking avatar", "static image", "slideshow"}
    if visual_mechanism.lower().strip() in generic_terms:
        return False
    return any(
        token in visual_mechanism.lower()
        for token in ["x-ray", "röntgen", "layer", "simulation", "timeline", "flow", "split", "3d", "circuit"]
    )

def build_mechanism_explainer(idea: dict[str, Any]) -> dict[str, Any]:
    """Build a shot-by-shot mechanism explainer skeleton from a scored idea."""
    follow_trigger = "Morgen zeige ich das nächste versteckte System, bevor es dich Geld kostet."
    return {
        "series": "Unsichtbare Systeme in 30 Sekunden",
        "title": idea["title"],
        "pillar": idea["pillar"],
        "target_seconds": 34,
        "follow_trigger": follow_trigger,
        "beats": [
            {
                "beat": "pattern_interrupt",
                "start": 0.0,
                "end": 1.4,
                "voiceover": idea["hook"],
                "visual": f"{idea['visual_mechanism']} freezes mid-action with warning signal.",
                "visual_motion": "hard zoom, freeze-frame, red signal pulse",
                "sound_cue": "bass hit + camera snap",
            },
            {
                "beat": "problem",
                "start": 1.4,
                "end": 5.0,
                "voiceover": "Das sieht harmlos aus, bis du den Mechanismus dahinter siehst.",
                "visual": "surface layer peels away to reveal the hidden system underneath.",
                "visual_motion": "layer peel + parallax push",
                "sound_cue": "whoosh + low tension tick",
            },
            {
                "beat": "mechanism",
                "start": 5.0,
                "end": 18.0,
                "voiceover": idea["mechanism"],
                "visual": idea["visual_mechanism"],
                "visual_motion": "sequential layer reveal every 1-2 seconds",
                "sound_cue": "clicks per revealed layer",
            },
            {
                "beat": "twist",
                "start": 18.0,
                "end": 28.0,
                "voiceover": idea["twist"],
                "visual": "camera snaps from obvious cause to real cause; wrong layer fades out.",
                "visual_motion": "snap zoom + wrong answer collapses",
                "sound_cue": "drop + silence beat",
            },
            {
                "beat": "follow_trigger",
                "start": 28.0,
                "end": 34.0,
                "voiceover": follow_trigger,
                "visual": "series tag appears over a clean animated system map.",
                "visual_motion": "map draws itself, final node glows",
                "sound_cue": "resolve hit + soft riser",
            },
        ],
    }


patch

    "render_manifest",
    "retention",
    "retention_studio",


--- a//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/__init__.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/__init__.py
@@ -6,6 +6,7 @@
     "preview_renderer",
     "render_manifest",
     "retention",
+    "retention_studio",
     "script_drafts",
     "shot_planner",
     "voiceover_finalizer",


terminal

Output:
....                                                                     [100%]
4 passed in 0.04s


"""Generate Retention Studio strategy artifacts and pilot mechanism explainers."""

from __future__ import annotations

import json
import sys
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from autoshorts.retention_studio import (
    PILOT_IDEAS,
    build_mechanism_explainer,
    build_retention_studio_strategy,
    score_idea,
)

def main() -> None:
    output_dir = Path("data/retention_studio")
    output_dir.mkdir(parents=True, exist_ok=True)

    strategy = build_retention_studio_strategy()
    (output_dir / "strategy.json").write_text(
        json.dumps(strategy, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )

    scored_ideas = []
    explainers = []
    for idea in PILOT_IDEAS:
        scored = {**idea, "score": score_idea(idea)}
        scored_ideas.append(scored)
        explainers.append(build_mechanism_explainer(idea))

    (output_dir / "pilot_ideas.json").write_text(
        json.dumps(scored_ideas, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )
    (output_dir / "mechanism_explainers.json").write_text(
        json.dumps(explainers, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )

    print(f"Wrote strategy and {len(scored_ideas)} pilot ideas to {output_dir}")
    for idea in scored_ideas:
        print(f"{idea['score']['total']:03d} {idea['score']['decision']:7s} {idea['pillar']}: {idea['title']}")

if __name__ == "__main__":
    main()


rm -rf data/retention_studio
python3 scripts/generate_retention_studio.py
python3 - <<'PY'
import json
from pathlib import Path
root=Path('data/retention_studio')
for p in sorted(root.glob('*.json')):
    data=json.loads(p.read_text())
    print(p.name, type(data).__name__, len(data) if isinstance(data, list) else sorted(data.keys())[:5])
ideas=json.loads((root/'pilot_ideas.json').read_text())
print('produce_count', sum(1 for i in ideas if i['score']['decision']=='produce'))
print('top3', [(i['score']['total'], i['title']) for i in sorted(ideas, key=lambda x: x['score']['total'], reverse=True)[:3]])
PY


Output:
Wrote strategy and 10 pilot ideas to data/retention_studio
088 produce hidden_money_systems: Warum ein billiges Auto teuer sein kann
086 produce hidden_money_systems: Warum 0%-Finanzierung nicht kostenlos ist
079 develop ai_agents_explained: Warum KI-Agenten ohne Regeln gefährlich werden
086 produce ai_agents_explained: Der Unterschied zwischen Chatbot und echtem Agenten
077 develop swiss_systems_explained: Warum ein Bauprojekt fast nie beim ersten Preis bleibt
073 develop swiss_systems_explained: Wie ein Einwohnerrat wirklich entscheidet
076 develop dangerous_thinking_errors: Warum dein Gehirn schlechte Deals schönrechnet
071 develop dangerous_thinking_errors: Warum du Verlusten hinterherrennst
090 produce hidden_attention_systems: Warum Shorts dich nicht wegen dem Inhalt halten
084 produce hidden_money_systems: Warum Banken Gebühren verstecken können, ohne sie zu verstecken
mechanism_explainers.json list 10
pilot_ideas.json list 10
strategy.json dict ['claim', 'content_pillars', 'core_formula', 'format_taxonomy', 'metrics']
produce_count 5
top3 [(90, 'Warum Shorts dich nicht wegen dem Inhalt halten'), (88, 'Warum ein billiges Auto teuer sein kann'), (86, 'Warum 0%-Finanzierung nicht kostenlos ist')]


Output:
....................................                                     [100%]
36 passed in 1.08s
 M autoshorts/__init__.py
?? autoshorts/retention_studio.py
?? autoshorts/review_package.py
?? data/retention_studio/
?? data/telegram_reviews/
?? scripts/generate_retention_studio.py
?? scripts/generate_telegram_review_packages.py
?? tests/test_retention_studio.py
?? tests/test_review_package.py
 autoshorts/__init__.py | 1 +
 1 file changed, 1 insertion(+)


social-media-shorts-automation

# Retention Studio strategy for mechanism-explainer Shorts

Use this when a shorts project should move beyond generic AI-video output into a semi-automatic retention studio.

## Core distinction

Do not optimize for "KI-Look". Optimize for retention mechanics:

- first 1-2 seconds open a loop
- visual change every 1-2 seconds
- every scene reveals an invisible mechanism
- the viewer ends with: "I want to see more of these systems"

## Recommended positioning

Best fit for this user's projects:

```text
Hidden Money & AI Systems
Series: Unsichtbare Systeme in 30 Sekunden
Claim: Wir zeigen Dinge, die dich Geld, Zeit oder Kontrolle kosten – bevor du sie bemerkst.
```

Avoid pure KI-avatar news, static AI slideshows, reused-content clones, or generic "what if" videos.

## Mechanism Explainer formula

```text
0.0-1.5s  Pattern interrupt
1.5-5s    Problem
5-18s     Mechanism reveal
18-28s    Twist
28-35s    Follow trigger / series promise
```

The key visual mechanic is: **show the invisible mechanism**.

Examples:

- car price -> x-ray layers: repair risk, stand days, margin, financing
- AI agent -> control loop: goal, tools, approval gate, runaway path
- building project -> timeline: permits, objections, procurement, inflation, scope creep
- bad deal -> brain/courtroom: sunk cost, confirmation bias, loss aversion

## Idea scoring dimensions

Score ideas for:

- surprise
- data basis
- comment potential
- series potential
- monetization potential
- production effort penalty
- specific visual mechanism bonus

Produce only high-scoring ideas. Develop medium ideas. Reject generic montage ideas.

## Production principle

Build artifacts in this order:

```text
strategy -> scored ideas -> mechanism explainer -> shot plan -> render manifest -> asset plan -> final renderer
```

For final render, use motion-led asset modes:

- cinematic AI video
- screen-recording simulation
- animated card
- kinetic text overlay

Keep static cards at `0` for final output.

## Testing checks

Tests should assert:

- strategy is not positioned as AI-video factory
- `static_card_limit == 0`
- mechanism explainer has all five beats
- pattern interrupt ends within 1.5 seconds
- every beat has visual motion and sound cue
- weak generic ideas are rejected
- pilot ideas cover money, AI, Swiss systems, and thinking-error pillars


File 'references/retention-studio-strategy.md' written to skill 'social-media-shorts-automation'.


Treat the system as a **Shorts Growth Engine**, not merely a renderer. A working video generator can still fail if it produces generic, low-retention, AI-slop content. Optimize for audience identity, hook quality, retention, platform fit, and analytics feedback.

Treat the system as a **Shorts Growth Engine** / **semi-automatic Retention Studio**, not merely a renderer. A working video generator can still fail if it produces generic, low-retention, AI-slop content. Optimize for audience identity, hook quality, retention mechanics, visual mechanism reveals, platform fit, and analytics feedback. For the user's strongest strategic direction, see `references/retention-studio-strategy.md`.


terminal

set -euo pipefail
git add autoshorts/retention_studio.py autoshorts/__init__.py scripts/generate_retention_studio.py tests/test_retention_studio.py data/retention_studio
git commit -m "feat: add retention studio strategy"


Output:
[feat/initial-growth-engine a9fe897] feat: add retention studio strategy
 7 files changed, 1192 insertions(+)
 create mode 100644 autoshorts/retention_studio.py
 create mode 100644 data/retention_studio/mechanism_explainers.json
 create mode 100644 data/retention_studio/pilot_ideas.json
 create mode 100644 data/retention_studio/strategy.json
 create mode 100644 scripts/generate_retention_studio.py
 create mode 100644 tests/test_retention_studio.py


set -euo pipefail
export GITHUB_TOKEN=$(tr -d '\n\r' < /home/agent/.hermes/secrets/github_autoshortsbot_token)
ASKPASS=$(mktemp)
cat > "$ASKPASS" <<'SH'
#!/bin/sh
case "$1" in
  *Username*) printf '%s\n' x-access-token ;;
  *Password*) printf '%s\n' "$GITHUB_TOKEN" ;;
  *) printf '\n' ;;
esac
SH
chmod 700 "$ASKPASS"
trap 'rm -f "$ASKPASS"' EXIT
GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 git push >/tmp/autoshorts_push10.log 2>&1 || { sed -E 's/(github_pat_|ghp_)[A-Za-z0-9_]+/[REDACTED]/g' /tmp/autoshorts_push10.log; exit 1; }
BRANCH=$(git branch --show-current)
LOCAL=$(git rev-parse HEAD)
REMOTE=$(GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 git ls-remote origin "refs/heads/$BRANCH" | awk '{print $1}')
printf 'branch=%s\nlocal=%s\nremote=%s\n' "$BRANCH" "$LOCAL" "$REMOTE"
test "$LOCAL" = "$REMOTE"
sed -E 's/(github_pat_|ghp_)[A-Za-z0-9_]+/[REDACTED]/g' /tmp/autoshorts_push10.log


Output:
branch=feat/initial-growth-engine
local=a9fe897616a9c8e8a65160460d70dc8be039c769
remote=a9fe897616a9c8e8a65160460d70dc8be039c769
To https://github.com/Gamexgit/AutoShortsBot.git
   b60981b..a9fe897  feat/initial-growth-engine -> feat/initial-growth-engine


git status --short --branch && python3 -m pytest -q
python3 - <<'PY'
import json
from pathlib import Path
ideas=json.loads(Path('data/retention_studio/pilot_ideas.json').read_text())
print('pilot_ideas', len(ideas))
print('produce_count', sum(1 for i in ideas if i['score']['decision']=='produce'))
print('top3')
for i in sorted(ideas, key=lambda x: x['score']['total'], reverse=True)[:3]:
    print(i['score']['total'], i['pillar'], i['title'])
PY


Output:
## feat/initial-growth-engine...origin/feat/initial-growth-engine
?? autoshorts/review_package.py
?? data/telegram_reviews/
?? scripts/generate_telegram_review_packages.py
?? tests/test_review_package.py
....................................                                     [100%]
36 passed in 0.99s
pilot_ideas 10
produce_count 5
top3
90 hidden_attention_systems Warum Shorts dich nicht wegen dem Inhalt halten
88 hidden_money_systems Warum ein billiges Auto teuer sein kann
86 hidden_money_systems Warum 0%-Finanzierung nicht kostenlos ist


Erledigt, Sir.

Ich habe deine Richtung jetzt nicht nur „notiert“, sondern als **produktive Systemlogik** in AutoShortsBot eingebaut.

## Gebaut: Retention Studio Strategie

Neue Komponente:

```text
autoshorts/retention_studio.py
```

Neues CLI:

```text
scripts/generate_retention_studio.py
```

Neue Tests:

```text
tests/test_retention_studio.py
```

Generierte Strategie-Artefakte:

```text
data/retention_studio/
  strategy.json
  pilot_ideas.json
  mechanism_explainers.json
```

## Was jetzt im System verankert ist

Der Bot ist nicht mehr konzeptionell:

```text
KI-Video-Fabrik
```

sondern:

```text
semi_automatic_retention_studio
```

Positionierung:

```text
Hidden Money & AI Systems
```

Serie:

```text
Unsichtbare Systeme in 30 Sekunden
```

Claim:

```text
Wir zeigen Dinge, die dich Geld, Zeit oder Kontrolle kosten – bevor du sie bemerkst.
```

## Explizit ausgeschlossen

Im Strategie-Modul steht jetzt klar:

```text
not_a:
  - ki_avatar_news
  - static_ai_slideshow
  - reused_content_clone
```

Und als Quality Gate:

```text
static_card_limit = 0
```

Damit ist die Richtung sauber: **keine finalen statischen KI-Bilder-/Textkarten-Videos**.

## Mechanism-Explainer-Formel eingebaut

Jedes Format folgt jetzt dieser Dramaturgie:

```text
0.0–1.5s   Pattern Interrupt
1.5–5s     Problem
5–18s      Mechanism Reveal
18–28s     Twist
28–35s     Follow Trigger
```

Tests prüfen u.a.:

- Pattern Interrupt endet ≤ 1.5 Sekunden
- jeder Beat hat visuelle Bewegung
- jeder Beat hat Sound Cue
- kein generisches „Follow for more“
- Follow Trigger ist Serienversprechen statt Betteln

## Format-Taxonomie

Eingebaut:

```text
mechanism_reveal
mistake_breakdown
hidden_cost
before_after
myth_vs_reality
mini_simulation
nobody_notices_this
```

## Content-Pillars

Eingebaut:

```text
hidden_money_systems
auto_trade_mechanics
ai_agents_explained
swiss_systems_explained
dangerous_thinking_errors
```

Dazu zusätzlich aus deinen Ideen:

```text
hidden_attention_systems
```

## Pilot-Ideen eingebaut

10 Pilot-Ideen wurden als strukturierte Daten erzeugt und gescored.

Top 3 nach Score:

```text
90 — hidden_attention_systems
Warum Shorts dich nicht wegen dem Inhalt halten

88 — hidden_money_systems
Warum ein billiges Auto teuer sein kann

86 — hidden_money_systems
Warum 0%-Finanzierung nicht kostenlos ist
```

Produktionsempfehlung:

```text
produce_count = 5 von 10
```

## Beispiel: Mechanism Explainer

Für „Warum ein billiges Auto teuer sein kann“ erzeugt das System jetzt sinngemäß:

```text
Pattern interrupt:
Dieser Occasion-Käufer denkt, er spart 4'000 Franken.
Visual: 3D car x-ray, warning pulse
Sound: bass hit

Problem:
Das sieht harmlos aus, bis du den Mechanismus dahinter siehst.
Visual: surface layer peels away

Mechanism:
Röntgenblick durch Preis, Reparaturrisiko, Standtage, Händler-Marge und Liquidität.
Visual: sequential layer reveal every 1–2 seconds

Twist:
Der teuerste Fehler ist nicht der Kaufpreis, sondern der Zeitpunkt.
Visual: wrong layer collapses

Follow trigger:
Morgen zeige ich das nächste versteckte System, bevor es dich Geld kostet.
Visual: system map draws itself
```

Das ist genau die Richtung: **visuelles Verständnis**, nicht Avatar-Gelaber. Endlich weniger „KI erklärt Dinge vor Hintergrundbild“, mehr „hier ist der Mechanismus, Sir“.

## Tests / Git

TDD durchgeführt:

- RED: fehlendes `autoshorts.retention_studio`
- GREEN: Strategiemodul implementiert
- Full suite:

```text
36 passed in 0.99s
```

Neuer Commit:

```text
a9fe897 feat: add retention studio strategy
```

Branch:

```text
feat/initial-growth-engine
```

Remote verifiziert:

```text
local == remote
```

PR-Link bleibt:

https://github.com/Gamexgit/AutoShortsBot/pull/new/feat/initial-growth-engine

## Skill aktualisiert

Ich habe die Social-Shorts-Skill-Notiz erweitert um:

```text
Retention Studio strategy
Mechanism Explainer Shorts
Hidden Money & AI Systems
static_card_limit = 0
```

Damit bleibt diese neue Richtung dauerhaft abrufbar.

## Zu den untracked Telegram-Review-Dateien

Ich habe sie, wie gewünscht, ignoriert und **nicht** committet:

```text
autoshorts/review_package.py
data/telegram_reviews/
scripts/generate_telegram_review_packages.py
tests/test_review_package.py
```

## Nächster sinnvoller Schritt

Jetzt sollten wir aus dem besten Pilot eine echte Produktionskette bauen:

```text
Retention Studio idea
→ Mechanism Explainer
→ Shot Plan
→ Render Manifest
→ Asset Plan
→ Provider Job Specs
→ Final Motion Render
```

Ich würde als ersten echten Pilot nehmen:

```text
Warum ein billiges Auto teuer sein kann
```

Warum? Es passt stark zu Money/Auto, hat klares visuelles Objekt, hohe Monetarisierung und kann mit 3D-/X-Ray-/Layer-Animation sehr gut funktionieren.

Nächster Build-Schritt wäre also:

```text
Mechanism Explainer → Shot Plan Adapter
```

Damit nicht nur alte AI-Life-Ideen durch die Pipeline laufen, sondern dieses neue Retention-Studio-Format.


[M G] ok, baue weiter.


Explanation:
**Planning skill adaptation**

I need to take action and consider loading relevant skills. I already have some, but we might need to scan and load new ones. Using Test-Driven Development (TDD) could be helpful here, but I need to be careful about anything untracked. I should probably build an adapter mechanism and create an explainer for it. I’m wondering how to structure this plan effectively!


test-driven-development

---
name: test-driven-development
description: "TDD: enforce RED-GREEN-REFACTOR, tests before code."
version: 1.1.0
author: Hermes Agent (adapted from obra/superpowers)
license: MIT
platforms: [linux, macos, windows]
metadata:
  hermes:
    tags: [testing, tdd, development, quality, red-green-refactor]
    related_skills: [systematic-debugging, writing-plans, subagent-driven-development]
---

# Test-Driven Development (TDD)

## Overview

Write the test first. Watch it fail. Write minimal code to pass.

**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.

**Violating the letter of the rules is violating the spirit of the rules.**

## When to Use

**Always:**
- New features
- Bug fixes
- Refactoring
- Behavior changes

**Exceptions (ask the user first):**
- Throwaway prototypes
- Generated code
- Configuration files

Thinking "skip TDD just this once"? Stop. That's rationalization.

## The Iron Law

```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```

Write code before the test? Delete it. Start over.

**No exceptions:**
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete

Implement fresh from tests. Period.

## Red-Green-Refactor Cycle

### RED — Write Failing Test

Write one minimal test showing what should happen.

**Good test:**
```python
def test_retries_failed_operations_3_times():
    attempts = 0
    def operation():
        nonlocal attempts
        attempts += 1
        if attempts < 3:
            raise Exception('fail')
        return 'success'

    result = retry_operation(operation)

    assert result == 'success'
    assert attempts == 3
```
Clear name, tests real behavior, one thing.

**Bad test:**
```python
def test_retry_works():
    mock = MagicMock()
    mock.side_effect = [Exception(), Exception(), 'success']
    result = retry_operation(mock)
    assert result == 'success'  # What about retry count? Timing?
```
Vague name, tests mock not real code.

**Requirements:**
- One behavior per test
- Clear descriptive name ("and" in name? Split it)
- Real code, not mocks (unless truly unavoidable)
- Name describes behavior, not implementation

### Verify RED — Watch It Fail

**MANDATORY. Never skip.**

```bash
# Use terminal tool to run the specific test
pytest tests/test_feature.py::test_specific_behavior -v
```

Confirm:
- Test fails (not errors from typos)
- Failure message is expected
- Fails because the feature is missing

**Test passes immediately?** You're testing existing behavior. Fix the test.

**Test errors?** Fix the error, re-run until it fails correctly.

### GREEN — Minimal Code

Write the simplest code to pass the test. Nothing more.

**Good:**
```python
def add(a, b):
    return a + b  # Nothing extra
```

**Bad:**
```python
def add(a, b):
    result = a + b
    logging.info(f"Adding {a} + {b} = {result}")  # Extra!
    return result
```

Don't add features, refactor other code, or "improve" beyond the test.

**Cheating is OK in GREEN:**
- Hardcode return values
- Copy-paste
- Duplicate code
- Skip edge cases

We'll fix it in REFACTOR.

### Verify GREEN — Watch It Pass

**MANDATORY.**

```bash
# Run the specific test
pytest tests/test_feature.py::test_specific_behavior -v

# Then run ALL tests to check for regressions
pytest tests/ -q
```

Confirm:
- Test passes
- Other tests still pass
- Output pristine (no errors, warnings)

**Test fails?** Fix the code, not the test.

**Other tests fail?** Fix regressions now.

### REFACTOR — Clean Up

After green only:
- Remove duplication
- Improve names
- Extract helpers
- Simplify expressions

Keep tests green throughout. Don't add behavior.

**If tests fail during refactor:** Undo immediately. Take smaller steps.

### Repeat

Next failing test for next behavior. One cycle at a time.

## Why Order Matters

**"I'll write tests after to verify it works"**

Tests written after code pass immediately. Passing immediately proves nothing:
- Might test the wrong thing
- Might test implementation, not behavior
- Might miss edge cases you forgot
- You never saw it catch the bug

Test-first forces you to see the test fail, proving it actually tests something.

**"I already manually tested all the edge cases"**

Manual testing is ad-hoc. You think you tested everything but:
- No record of what you tested
- Can't re-run when code changes
- Easy to forget cases under pressure
- "It worked when I tried it" ≠ comprehensive

Automated tests are systematic. They run the same way every time.

**"Deleting X hours of work is wasteful"**

Sunk cost fallacy. The time is already gone. Your choice now:
- Delete and rewrite with TDD (high confidence)
- Keep it and add tests after (low confidence, likely bugs)

The "waste" is keeping code you can't trust.

**"TDD is dogmatic, being pragmatic means adapting"**

TDD IS pragmatic:
- Finds bugs before commit (faster than debugging after)
- Prevents regressions (tests catch breaks immediately)
- Documents behavior (tests show how to use code)
- Enables refactoring (change freely, tests catch breaks)

"Pragmatic" shortcuts = debugging in production = slower.

**"Tests after achieve the same goals — it's spirit not ritual"**

No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"

Tests-after are biased by your implementation. You test what you built, not what's required. Tests-first force edge case discovery before implementing.

## Common Rationalizations

| Excuse | Reality |
|--------|---------|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to the test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for the code you touch. |

## Red Flags — STOP and Start Over

If you catch yourself doing any of these, delete the code and restart with TDD:

- Code before test
- Test after implementation
- Test passes immediately on first run
- Can't explain why test failed
- Tests added "later"
- Rationalizing "just this once"
- "I already manually tested it"
- "Tests after achieve the same purpose"
- "Keep as reference" or "adapt existing code"
- "Already spent X hours, deleting is wasteful"
- "TDD is dogmatic, I'm being pragmatic"
- "This is different because..."

**All of these mean: Delete code. Start over with TDD.**

## Verification Checklist

Before marking work complete:

- [ ] Every new function/method has a test
- [ ] Watched each test fail before implementing
- [ ] Each test failed for expected reason (feature missing, not typo)
- [ ] Wrote minimal code to pass each test
- [ ] All tests pass
- [ ] Output pristine (no errors, warnings)
- [ ] Tests use real code (mocks only if unavoidable)
- [ ] Edge cases and errors covered

Can't check all boxes? You skipped TDD. Start over.

## When Stuck

| Problem | Solution |
|---------|----------|
| Don't know how to test | Write the wished-for API. Write the assertion first. Ask the user. |
| Test too complicated | Design too complicated. Simplify the interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify the design. |

## Hermes Agent Integration

### Running Tests

Use the `terminal` tool to run tests at each step:

```python
# RED — verify failure
terminal("pytest tests/test_feature.py::test_name -v")

# GREEN — verify pass
terminal("pytest tests/test_feature.py::test_name -v")

# Full suite — verify no regressions
terminal("pytest tests/ -q")
```

### With delegate_task

When dispatching subagents for implementation, enforce TDD in the goal:

```python
delegate_task(
    goal="Implement [feature] using strict TDD",
    context="""
    Follow test-driven-development skill:
    1. Write failing test FIRST
    2. Run test to verify it fails
    3. Write minimal code to pass
    4. Run test to verify it passes
    5. Refactor if needed
    6. Commit

    Project test command: pytest tests/ -q
    Project structure: [describe relevant files]
    """,
    toolsets=['terminal', 'file']
)
```

### With systematic-debugging

Bug found? Write failing test reproducing it. Follow TDD cycle. The test proves the fix and prevents regression.

Never fix bugs without a test.

## Testing Anti-Patterns

- **Testing mock behavior instead of real behavior** — mocks should verify interactions, not replace the system under test
- **Testing implementation details** — test behavior/results, not internal method calls
- **Happy path only** — always test edge cases, errors, and boundaries
- **Brittle tests** — tests should verify behavior, not structure; refactoring shouldn't break them
- **Substring sentinels that collide with generated IDs** — when asserting that amounts or fixture values are excluded from a result, assert the full formatted token (e.g. `999.00`) or a typed field, not a short substring like `999` that can appear inside UUIDs/hash-like IDs.

## Final Rule

```
Production code → test exists and failed first
Otherwise → not TDD
```

No exceptions without the user's explicit permission.