[M G] JARVIS, sehr guter Sprint 1.5. Jetzt gehen wir gezielt Richtung erstes echtes Video, aber kontrolliert.

Wichtige Präzisierung:
Du musst nicht generell Token sparen. Du darfst KI frei und intensiv nutzen für:

* Denken
* Research
* Hook-Varianten
* Skriptvarianten
* Kritik
* Score-Kalibrierung
* Review Packages
* ShotPlan-Entwürfe
* RenderManifest-Entwürfe
* Wan-2.2-Promptplanung
* Social-Copy-Varianten

Aber:
Bitte keine unnötige Medienproduktion.

Wir unterscheiden ab jetzt klar:

Resource Tier 0 — Text/Reasoning:
frei erlaubt.

Resource Tier 1 — deterministic Preview / JSON / Review Package:
erlaubt nach gültigem Candidate + ReviewPackage.

Resource Tier 2 — AI Image / Styleframes:
nur limitiert und nur wenn es für die Review wirklich nötig ist. Keine 20 Bildvarianten, keine sinnlosen Styleframe-Serien.

Resource Tier 3 — Wan 2.2 Final Render:
nur nach expliziter Freigabe mit Status APPROVED_FOR_FINAL_RENDER.

Resource Tier 4 — Publishing:
weiterhin deaktiviert.

Bitte setze jetzt Sprint 1.6 um:

Sprint 1.6: Pilot Production Readiness & First Controlled Render

Ziel:
Wir wollen zum ersten echten Video kommen, aber ohne Massenproduktion und ohne vorschnelles Wan-Rendering.

1. Sprint 1.5 sichern

* git status prüfen
* pytest ausführen
* Sprint-1.5-Dateien committen, sofern sauber

Commit Message:
Operationalize strategy spine v2 review workflow

Wenn Tests fehlschlagen oder Konflikte bestehen:
nicht committen, sondern Status melden.

2. Resource-Gating ergänzen

Bitte ergänze ein kleines Resource-Gating-Modell, z. B.:

* ResourceCostTier

  * TEXT_REASONING
  * DETERMINISTIC_PREVIEW
  * AI_STYLEFRAME
  * WAN_FINAL_RENDER
  * PUBLISHING

* RenderApprovalState

  * DRAFT
  * READY_FOR_REVIEW
  * APPROVED_FOR_ASSET_PLANNING
  * APPROVED_FOR_STYLEFRAME
  * APPROVED_FOR_FINAL_RENDER
  * RENDERED_FOR_REVIEW
  * APPROVED_FOR_MANUAL_POSTING
  * BLOCKED

* MediaGenerationGate

  * candidate_id
  * version
  * script_hash
  * requested_resource_tier
  * approval_state
  * allowed: yes/no
  * reason

Regeln:

* TEXT_REASONING immer erlaubt.
* DETERMINISTIC_PREVIEW erlaubt ab ready_for_review.
* AI_STYLEFRAME nur erlaubt ab APPROVED_FOR_STYLEFRAME.
* WAN_FINAL_RENDER nur erlaubt bei APPROVED_FOR_FINAL_RENDER.
* PUBLISHING bleibt immer blockiert.
* Wenn script_hash nicht zur Freigabe passt: blockieren.
* Wenn candidate_id/version nicht passen: blockieren.
* Wenn AI Disclosure unclear: blockieren.
* Wenn Claim Gate blockiert: blockieren.

Wichtig:
Das ist eine Gate-Logik. Noch kein finales Rendern.

3. Nicht 30 Kandidaten erzeugen

Bitte keinen 30-Tage-Testbatch bauen.

Arbeite nur mit maximal 3 bestehenden Demo-Kandidaten:

* AI Output Autopsy: Meeting summary misses decision target
* Digital Red Flags: Fake invoice payment route changed
* Workflow Teardown: Automation fails because missing exception path

Wähle daraus:

* 1 Hauptpilot
* 1 Backup
* 1 zurückgestellten Kandidaten

Meine Präferenz:
Hauptpilot = AI Output Autopsy / Meeting Summary Decision Target
Backup = Digital Red Flags / Fake Invoice
Zurückstellen = Workflow Teardown / Exception Path

Bitte begründe kurz, falls du anders priorisierst.

4. Produktionspaket für genau 1 Hauptpilot bauen

Für den Hauptpilot bitte ein vollständiges PilotProductionPackage erstellen.

Enthalten:

* Candidate ID
* Version
* Script Hash
* Approval State
* finaler englischer Voiceover-Text
* deutsches Review Summary
* Hook
* First Frame Description
* Visible Fail
* Anti Example / Plausible Nonsense Example
* Mechanism
* Fix
* Before/After
* Takeaway
* Save Reason
* Share Reason
* Follow Reason
* Claim Gate Result
* AI Disclosure Result
* Resource Gate Result
* Platform Fit:

  * YouTube Shorts
  * TikTok
  * Instagram Reels
  * LinkedIn Manual
* Social Copy:

  * YouTube Title
  * YouTube Description
  * TikTok Caption
  * Instagram Caption
  * LinkedIn Caption
* Hashtags pro Plattform
* Caption Mode Empfehlung

  * word-by-word
  * phrase-by-phrase
  * keyword-pulse
* TTS Plan
* Subtitle Alignment Plan
* ShotPlan
* RenderManifest Draft
* Wan 2.2 Visual Prompt Plan
* Negative Prompts
*
Asset List
* Estimated Render Cost/Time Class:

  * low
  * medium
  * high

5. Wan 2.2 Promptplanung, aber noch kein Finalrender

Bitte erstelle für Wan 2.2 nur den Promptplan.

Keine Wan-Generierung ohne explizite Freigabe.

Der Wan Prompt Plan soll enthalten:

* scene_id
* duration_seconds
* visual_goal
* prompt
* negative_prompt
* camera_motion
* required_overlay_text
* renderer_owned_overlay_elements
* safe_zone_notes
* transition_to_next_scene

Wichtig:
Wan soll keine eingebrannten Texte, Logos, Captions, Zahlen oder Fakten erzeugen.
Alle Texte, Captions, Pfeile, Labels und Fakten bleiben renderer-owned.

6. Optional maximal 1 Styleframe-Entwurf

Wenn es wirklich hilft, darfst du maximal einen Styleframe-Prompt vorbereiten.

Aber:

* Kein Bild generieren, solange ich nicht APPROVED_FOR_STYLEFRAME schreibe.
* Kein Wan Render, solange ich nicht APPROVED_FOR_FINAL_RENDER schreibe.

7. Deterministische Preview ist erlaubt

Falls technisch bereits möglich:
Erstelle eine sehr einfache deterministic Preview oder HTML-Timeline-Preview für den Hauptpilot.

Erlaubt:

* JSON
* HTML Preview
* Text Timeline
* Placeholder Frames
* lokal generierte einfache Preview ohne Wan

Nicht erlaubt:

* finales Wan Video
* mehrere AI-Bilder
* Plattform-Upload

8. Approval Commands vorbereiten

Bitte am Ende klare Kommandos ausgeben:

Für Styleframe:

APPROVE_STYLEFRAME <candidate_id> <version> <script_hash>

Für Finalrender:

APPROVED_FOR_FINAL_RENDER <candidate_id> <version> <script_hash>

Für Revision:

REVISE <candidate_id> <field> "Änderungswunsch"

Für Ablehnung:

REJECT <candidate_id> "Grund"

9. Tests ergänzen

Bitte Tests ergänzen für:

* ResourceCostTier
* RenderApprovalState
* MediaGenerationGate
* WAN_FINAL_RENDER blockiert ohne APPROVED_FOR_FINAL_RENDER
* PUBLISHING immer blockiert
* falscher script_hash blockiert Finalrender
* AI Disclosure unclear blockiert Finalrender
* Claim Gate blockiert Finalrender bei invaliden Claims
* PilotProductionPackage ist deterministisch serialisierbar
* demo erzeugt maximal 1 Hauptpilot + 1 Backup
* keine Wan-/AI-Bildgenerierung wird durch CLI ausgelöst

10. CLI ergänzen

Bitte ergänze eine CLI:

python -m autoshorts.cli.demo_pilot_production_package_v2
python -m autoshorts.cli.demo_pilot_production_package_v2 --json

Eigenschaften:

* maximal 1 Hauptpilot
* maximal 1 Backup
* kein Finalrender
* keine Wan-Calls
* keine AI-Bildgenerierung
* keine Telegram-API
* keine Plattform-API
* side_effects=[] ausser explizitem optionalem JSON-write-plan
* external_calls=[]

11. Abschlussbericht

Bitte am Ende kurz berichten:

* git status
* commit hash von Sprint 1.5, falls commit erfolgt
* geänderte Dateien Sprint 1.6
* pytest Ergebnis
* ausgewählter Hauptpilot
* Backup-Kandidat
* Score Breakdown
* Claim Gate Result
* AI Disclosure Result
* Resource Gate Result
* PilotProductionPackage Kurzfassung
* Wan Prompt Plan Kurzfassung
* Approval Commands
* offene Punkte

Wichtig:
Keine 30 Kandidaten.
Keine Massenproduktion.
Kein finales Rendering.
Kein Wan-2.2-Render ohne APPROVED_FOR_FINAL_RENDER.
Keine Plattform-API.
Kein Auto-Posting.
Kein Dashboard.
Keine Remotion-Migration.

Ziel dieses Sprints:
Ein einzelnes Video so weit vorbereiten, dass ich danach bewusst entscheiden kann:

* überarbeiten,
* Styleframe freigeben,
* oder genau einen Wan-2.2-Finalrender freigeben.


Explanation:
**Planning coding tasks**

I need to focus on acting and loading the relevant skills for this new coding task, like TDD. If social skills are already loaded, that’s helpful! But I better confirm if systematic skills are necessary. I’ll use available tools and ensure I'm ready to commit to sprint 1.5. After that, I'll implement the plan and manage my time effectively. It feels like there’s a lot to organize, but I’m determined to get it right!

todo

Sprint 1.5 Status prüfen, Tests laufen lassen und committen

Sprint 1.6 Tests für Resource Gates, PilotPackage und CLI schreiben/RED prüfen

Resource-Gating, PilotProductionPackage, Wan Prompt Plan, Preview/CLI implementieren

Pytest, CLI JSON/Text ausführen und Abschlussdaten sammeln


terminal

git status --short && printf '\nBRANCH=' && git branch --show-current && printf '\nPYTEST\n' && python -m pytest tests -q


Output:
M autoshorts/strategy/spine_v2.py
 M tests/test_strategy_spine_v2.py
?? autoshorts/claims/
?? autoshorts/cli/demo_review_batch_v2.py
?? autoshorts/compliance/
?? autoshorts/content/to_candidate_v2.py
?? autoshorts/review/approvals_v2.py
?? autoshorts/review/workflow_v2.py
?? docs/rendering_backend_options.md
?? tests/test_strategy_spine_v2_operational.py

BRANCH=strategy-refactor-v2

PYTEST
........................................................................ [ 37%]
........................................................................ [ 75%]
..............................................                           [100%]
190 passed in 31.86s


git add autoshorts/strategy/spine_v2.py tests/test_strategy_spine_v2.py autoshorts/claims/fact_gate.py autoshorts/cli/demo_review_batch_v2.py autoshorts/compliance/ai_disclosure.py autoshorts/content/to_candidate_v2.py autoshorts/review/approvals_v2.py autoshorts/review/workflow_v2.py docs/rendering_backend_options.md tests/test_strategy_spine_v2_operational.py && git commit -m "Operationalize strategy spine v2 review workflow"


Output:
[strategy-refactor-v2 4c57b52] Operationalize strategy spine v2 review workflow
 10 files changed, 854 insertions(+), 384 deletions(-)
 create mode 100644 autoshorts/claims/fact_gate.py
 create mode 100644 autoshorts/cli/demo_review_batch_v2.py
 create mode 100644 autoshorts/compliance/ai_disclosure.py
 create mode 100644 autoshorts/content/to_candidate_v2.py
 create mode 100644 autoshorts/review/approvals_v2.py
 create mode 100644 autoshorts/review/workflow_v2.py
 create mode 100644 docs/rendering_backend_options.md
 create mode 100644 tests/test_strategy_spine_v2_operational.py


Need create tests. Need inspect package maybe no __init__ needed py3 namespace ok. Use write_file.

import json
from io import StringIO

from autoshorts.claims.fact_gate import Claim, ClaimType
from autoshorts.cli.demo_pilot_production_package_v2 import build_demo_pilot_package_v2, main
from autoshorts.compliance.ai_disclosure import AIDisclosureReview, DisclosureValue, SyntheticMediaType
from autoshorts.production.pilot_package_v2 import (
    PilotSelection,
    build_pilot_production_package,
    select_pilot_candidates,
)
from autoshorts.production.resource_gate import (
    MediaGenerationGate,
    RenderApprovalState,
    ResourceCostTier,
    evaluate_media_generation_gate,
)
from autoshorts.strategy.spine_v2 import AIDisclosureDecision, build_demo_candidates_v2, build_review_package_v2

def pilot_candidate():
    return build_demo_candidates_v2(limit=1)[0]

def pilot_package():
    candidate = pilot_candidate()
    review = build_review_package_v2(candidate)
    return build_pilot_production_package(candidate, review)

def test_resource_cost_tier_and_render_state_values_exist():
    assert ResourceCostTier.TEXT_REASONING.value == "text_reasoning"
    assert ResourceCostTier.WAN_FINAL_RENDER.value == "wan_final_render"
    assert ResourceCostTier.PUBLISHING.value == "publishing"
    assert RenderApprovalState.APPROVED_FOR_FINAL_RENDER.value == "approved_for_final_render"

def test_media_generation_gate_allows_text_and_preview_but_blocks_wan_without_final_approval():
    candidate = pilot_candidate()
    review = build_review_package_v2(candidate)

    text_gate = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=review,
        requested_resource_tier=ResourceCostTier.TEXT_REASONING,
        approval_state=RenderApprovalState.DRAFT,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=review.script_hash,
    )
    assert text_gate.allowed

    preview_gate = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=review,
        requested_resource_tier=ResourceCostTier.DETERMINISTIC_PREVIEW,
        approval_state=RenderApprovalState.READY_FOR_REVIEW,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=review.script_hash,
    )
    assert preview_gate.allowed

    wan_gate = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=review,
        requested_resource_tier=ResourceCostTier.WAN_FINAL_RENDER,
        approval_state=RenderApprovalState.APPROVED_FOR_STYLEFRAME,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=review.script_hash,
    )
    assert not wan_gate.allowed
    assert "APPROVED_FOR_FINAL_RENDER" in wan_gate.reason

def test_publishing_is_always_blocked_and_wrong_hash_blocks_finalrender():
    candidate = pilot_candidate()
    review = build_review_package_v2(candidate)

    publish = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=review,
        requested_resource_tier=ResourceCostTier.PUBLISHING,
        approval_state=RenderApprovalState.APPROVED_FOR_MANUAL_POSTING,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=review.script_hash,
    )
    assert not publish.allowed
    assert "Publishing" in publish.reason

    wrong_hash = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=review,
        requested_resource_tier=ResourceCostTier.WAN_FINAL_RENDER,
        approval_state=RenderApprovalState.APPROVED_FOR_FINAL_RENDER,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash="bad-hash",
    )
    assert not wrong_hash.allowed
    assert "script_hash" in wrong_hash.reason

def test_ai_disclosure_unclear_and_claim_gate_block_finalrender():
    candidate = pilot_candidate()
    review = build_review_package_v2(candidate)
    unclear = AIDisclosureReview(
        youtube=DisclosureValue.UNCLEAR,
        tiktok=DisclosureValue.NO,
        instagram=DisclosureValue.NO,
        linkedin=DisclosureValue.NO,
        reason="unclear synthetic media status",
        synthetic_media_type=SyntheticMediaType.UNCLEAR,
    )
    gate = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=review,
        requested_resource_tier=ResourceCostTier.WAN_FINAL_RENDER,
        approval_state=RenderApprovalState.APPROVED_FOR_FINAL_RENDER,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=review.script_hash,
        ai_disclosure_review=unclear,
    )
    assert not gate.allowed
    assert "AI disclosure" in gate.reason

    bad_claim_candidate = build_demo_candidates_v2(limit=1)[0]
    bad_claim_candidate = bad_claim_candidate.__class__(
        **{**bad_claim_candidate.__dict__, "claims": (Claim("specific sourced statement", ClaimType.SOURCED_FACT),)}
    )
    bad_review = build_review_package_v2(bad_claim_candidate)
    claim_gate = evaluate_media_generation_gate(
        candidate=bad_claim_candidate,
        review_package=bad_review,
        requested_resource_tier=ResourceCostTier.WAN_FINAL_RENDER,
        approval_state=RenderApprovalState.APPROVED_FOR_FINAL_RENDER,
        approved_candidate_id=bad_claim_candidate.candidate_id,
        approved_version=bad_claim_candidate.version,
        approved_script_hash=bad_review.script_hash,
    )
    assert not claim_gate.allowed
    assert "Claim Gate" in claim_gate.reason

def test_pilot_package_is_deterministic_serializable_and_has_render_plans():
    package = pilot_package()
    first = package.to_json()
    second = package.to_json()
    data = json.loads(first)

    assert first == second
    assert data["candidate_id"] == "ai-output-autopsy-decision-summary-38s"
    assert data["approval_state"] == RenderApprovalState.READY_FOR_REVIEW.value
    assert data["voiceover_text"].startswith("Your AI summary is useless")
    assert data["wan_prompt_plan"][0]["required_overlay_text"] == []
    assert data["render_manifest_draft"]["final_render_enabled"] is False
    assert data["resource_gate_result"]["requested_resource_tier"] == ResourceCostTier.DETERMINISTIC_PREVIEW.value

def test_demo_selection_returns_one_pilot_one_backup_and_one_deferred():
    selection = select_pilot_candidates(build_demo_candidates_v2(limit=3))
    assert selection.main_pilot.candidate_id == "ai-output-autopsy-decision-summary-38s"
    assert selection.backup.candidate_id == "digital-red-flags-fake-invoice-35s"
    assert selection.deferred.candidate_id == "workflow-teardown-exception-path-40s"

def test_demo_cli_emits_max_one_pilot_one_backup_and_no_external_calls():
    out = StringIO()
    assert main(["--json"], stdout=out) == 0
    data = json.loads(out.getvalue())

    assert data["main_pilot"]["candidate_id"] == "ai-output-autopsy-decision-summary-38s"
    assert data["backup"]["candidate_id"] == "digital-red-flags-fake-invoice-35s"
    assert data["deferred"]["candidate_id"] == "workflow-teardown-exception-path-40s"
    assert data["side_effects"] == []
    assert data["external_calls"] == []
    assert data["main_pilot"]["render_manifest_draft"]["wan_calls_enabled"] is False
    assert data["main_pilot"]["render_manifest_draft"]["ai_image_generation_enabled"] is False


LSP diagnostics introduced by this edit:
<diagnostics file="/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_pilot_production_v2.py">
ERROR [5:6] Import "autoshorts.cli.demo_pilot_production_package_v2" could not be resolved [reportMissingImports] (Pyright)
ERROR [7:6] Import "autoshorts.production.pilot_package_v2" could not be resolved [reportMissingImports] (Pyright)
ERROR [12:6] Import "autoshorts.production.resource_gate" could not be resolved [reportMissingImports] (Pyright)
</diagnostics>


python -m pytest tests/test_pilot_production_v2.py -q


Output:
==================================== ERRORS ====================================
______________ ERROR collecting tests/test_pilot_production_v2.py ______________
ImportError while importing test module '/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_pilot_production_v2.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_pilot_production_v2.py:5: in <module>
    from autoshorts.cli.demo_pilot_production_package_v2 import build_demo_pilot_package_v2, main
E   ModuleNotFoundError: No module named 'autoshorts.cli.demo_pilot_production_package_v2'
=========================== short test summary info ============================
ERROR tests/test_pilot_production_v2.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.14s


"""Resource gating for controlled V2 pilot production."""

from __future__ import annotations

from dataclasses import dataclass
from enum import StrEnum

from autoshorts.claims.fact_gate import validate_claims_for_candidate
from autoshorts.compliance.ai_disclosure import AIDisclosureReview, validate_ai_disclosure_review
from autoshorts.strategy.spine_v2 import AIDisclosureDecision, ReviewPackageV2, VideoCandidateV2

class ResourceCostTier(StrEnum):
    TEXT_REASONING = "text_reasoning"
    DETERMINISTIC_PREVIEW = "deterministic_preview"
    AI_STYLEFRAME = "ai_styleframe"
    WAN_FINAL_RENDER = "wan_final_render"
    PUBLISHING = "publishing"

class RenderApprovalState(StrEnum):
    DRAFT = "draft"
    READY_FOR_REVIEW = "ready_for_review"
    APPROVED_FOR_ASSET_PLANNING = "approved_for_asset_planning"
    APPROVED_FOR_STYLEFRAME = "approved_for_styleframe"
    APPROVED_FOR_FINAL_RENDER = "approved_for_final_render"
    RENDERED_FOR_REVIEW = "rendered_for_review"
    APPROVED_FOR_MANUAL_POSTING = "approved_for_manual_posting"
    BLOCKED = "blocked"

@dataclass(frozen=True)
class MediaGenerationGate:
    candidate_id: str
    version: str
    script_hash: str
    requested_resource_tier: ResourceCostTier
    approval_state: RenderApprovalState
    allowed: bool
    reason: str

    def to_dict(self) -> dict[str, object]:
        return {
            "candidate_id": self.candidate_id,
            "version": self.version,
            "script_hash": self.script_hash,
            "requested_resource_tier": self.requested_resource_tier.value,
            "approval_state": self.approval_state.value,
            "allowed": self.allowed,
            "reason": self.reason,
        }

def _blocked(candidate: VideoCandidateV2, package: ReviewPackageV2, tier: ResourceCostTier, state: RenderApprovalState, reason: str) -> MediaGenerationGate:
    return MediaGenerationGate(candidate.candidate_id, candidate.version, package.script_hash, tier, state, False, reason)

def _allowed(candidate: VideoCandidateV2, package: ReviewPackageV2, tier: ResourceCostTier, state: RenderApprovalState, reason: str) -> MediaGenerationGate:
    return MediaGenerationGate(candidate.candidate_id, candidate.version, package.script_hash, tier, state, True, reason)

def evaluate_media_generation_gate(
    *,
    candidate: VideoCandidateV2,
    review_package: ReviewPackageV2,
    requested_resource_tier: ResourceCostTier,
    approval_state: RenderApprovalState,
    approved_candidate_id: str,
    approved_version: str,
    approved_script_hash: str,
    ai_disclosure_review: AIDisclosureReview | None = None,
) -> MediaGenerationGate:
    if requested_resource_tier == ResourceCostTier.PUBLISHING:
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "Publishing is disabled in this workflow.")

    if approved_candidate_id != candidate.candidate_id:
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "candidate_id mismatch")
    if approved_version != candidate.version:
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "version mismatch")
    if approved_script_hash != review_package.script_hash:
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "script_hash mismatch")

    if candidate.ai_disclosure_required == AIDisclosureDecision.UNCLEAR:
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "AI disclosure is unclear")
    if ai_disclosure_review is not None:
        disclosure_result = validate_ai_disclosure_review(ai_disclosure_review)
        if not disclosure_result.is_valid:
            return _blocked(candidate, review_package, requested_resource_tier, approval_state, "AI disclosure blocks rendering: " + "; ".join(disclosure_result.errors))

    claim_result = validate_claims_for_candidate(candidate)
    if not claim_result.is_valid:
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "Claim Gate blocks rendering: " + "; ".join(claim_result.errors))

    if requested_resource_tier == ResourceCostTier.TEXT_REASONING:
        return _allowed(candidate, review_package, requested_resource_tier, approval_state, "TEXT_REASONING is always allowed after identity/hash checks.")
    if requested_resource_tier == ResourceCostTier.DETERMINISTIC_PREVIEW:
        if approval_state in {
            RenderApprovalState.READY_FOR_REVIEW,
            RenderApprovalState.APPROVED_FOR_ASSET_PLANNING,
            RenderApprovalState.APPROVED_FOR_STYLEFRAME,
            RenderApprovalState.APPROVED_FOR_FINAL_RENDER,
            RenderApprovalState.RENDERED_FOR_REVIEW,
        }:
            return _allowed(candidate, review_package, requested_resource_tier, approval_state, "Deterministic preview allowed from READY_FOR_REVIEW onward.")
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "DETERMINISTIC_PREVIEW requires READY_FOR_REVIEW or later")
    if requested_resource_tier == ResourceCostTier.AI_STYLEFRAME:
        if approval_state in {RenderApprovalState.APPROVED_FOR_STYLEFRAME, RenderApprovalState.APPROVED_FOR_FINAL_RENDER}:
            return _allowed(candidate, review_package, requested_resource_tier, approval_state, "AI styleframe allowed by explicit styleframe approval.")
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "AI_STYLEFRAME requires APPROVED_FOR_STYLEFRAME")
    if requested_resource_tier == ResourceCostTier.WAN_FINAL_RENDER:
        if approval_state == RenderApprovalState.APPROVED_FOR_FINAL_RENDER:
            return _allowed(candidate, review_package, requested_resource_tier, approval_state, "WAN final render allowed by explicit final-render approval.")
        return _blocked(candidate, review_package, requested_resource_tier, approval_state, "WAN_FINAL_RENDER requires APPROVED_FOR_FINAL_RENDER")
    return _blocked(candidate, review_package, requested_resource_tier, approval_state, "Unknown resource tier")


"""Pilot production package for exactly one controlled V2 video."""

from __future__ import annotations

from dataclasses import dataclass
import json
from typing import Any

from autoshorts.compliance.ai_disclosure import AIDisclosureReview
from autoshorts.production.resource_gate import (
    MediaGenerationGate,
    RenderApprovalState,
    ResourceCostTier,
    evaluate_media_generation_gate,
)
from autoshorts.review.workflow_v2 import classify_candidate_v2
from autoshorts.strategy.spine_v2 import ReviewPackageV2, VideoCandidateV2, build_review_package_v2

@dataclass(frozen=True)
class WanScenePromptPlan:
    scene_id: str
    duration_seconds: float
    visual_goal: str
    prompt: str
    negative_prompt: str
    camera_motion: str
    required_overlay_text: tuple[str, ...]
    renderer_owned_overlay_elements: tuple[str, ...]
    safe_zone_notes: str
    transition_to_next_scene: str

    def to_dict(self) -> dict[str, Any]:
        return {
            "scene_id": self.scene_id,
            "duration_seconds": self.duration_seconds,
            "visual_goal": self.visual_goal,
            "prompt": self.prompt,
            "negative_prompt": self.negative_prompt,
            "camera_motion": self.camera_motion,
            "required_overlay_text": list(self.required_overlay_text),
            "renderer_owned_overlay_elements": list(self.renderer_owned_overlay_elements),
            "safe_zone_notes": self.safe_zone_notes,
            "transition_to_next_scene": self.transition_to_next_scene,
        }

@dataclass(frozen=True)
class PilotProductionPackage:
    candidate_id: str
    version: str
    script_hash: str
    approval_state: RenderApprovalState
    voiceover_text: str
    german_review_summary: str
    hook: str
    first_frame_description: str
    visible_fail: str
    anti_example: str
    mechanism: str
    fix: str
    before_after: str
    takeaway: str
    save_reason: str
    share_reason: str
    follow_reason: str
    claim_gate_result: dict[str, Any]
    ai_disclosure_result: dict[str, Any]
    resource_gate_result: MediaGenerationGate
    platform_fit: dict[str, str]
    social_copy: dict[str, str]
    hashtags: dict[str, tuple[str, ...]]
    caption_mode_recommendation: str
    tts_plan: dict[str, str]
    subtitle_alignment_plan: dict[str, str]
    shot_plan: tuple[dict[str, Any], ...]
    render_manifest_draft: dict[str, Any]
    wan_prompt_plan: tuple[WanScenePromptPlan, ...]
    styleframe_prompt_draft: dict[str, str]
    negative_prompts: tuple[str, ...]
    asset_list: tuple[dict[str, str], ...]
    estimated_render_cost_time_class: str
    deterministic_preview: dict[str, Any]
    score_breakdown: dict[str, Any]
    approval_commands: dict[str, str]

    def to_dict(self) -> dict[str, Any]:
        return {
            "candidate_id": self.candidate_id,
            "version": self.version,
            "script_hash": self.script_hash,
            "approval_state": self.approval_state.value,
            "voiceover_text": self.voiceover_text,
            "german_review_summary": self.german_review_summary,
            "hook": self.hook,
            "first_frame_description": self.first_frame_description,
            "visible_fail": self.visible_fail,
            "anti_example": self.anti_example,
            "mechanism": self.mechanism,
            "fix": self.fix,
            "before_after": self.before_after,
            "takeaway": self.takeaway,
            "save_reason": self.save_reason,
            "share_reason": self.share_reason,
            "follow_reason": self.follow_reason,
            "claim_gate_result": self.claim_gate_result,
            "ai_disclosure_result": self.ai_disclosure_result,
            "resource_gate_result": self.resource_gate_result.to_dict(),
            "platform_fit": dict(self.platform_fit),
            "social_copy": dict(self.social_copy),
            "hashtags": {key: list(value) for key, value in self.hashtags.items()},
            "caption_mode_recommendation": self.caption_mode_recommendation,
            "tts_plan": dict(self.tts_plan),
            "subtitle_alignment_plan": dict(self.subtitle_alignment_plan),
            "shot_plan": list(self.shot_plan),
            "render_manifest_draft": dict(self.render_manifest_draft),
            "wan_prompt_plan": [scene.to_dict() for scene in self.wan_prompt_plan],
            "styleframe_prompt_draft": dict(self.styleframe_prompt_draft),
            "negative_prompts": list(self.negative_prompts),
            "asset_list": list(self.asset_list),
            "estimated_render_cost_time_class": self.estimated_render_cost_time_class,
            "deterministic_preview": self.deterministic_preview,
            "score_breakdown": self.score_breakdown,
            "approval_commands": dict(self.approval_commands),
        }

    def to_json(self) -> str:
        return json.dumps(self.to_dict(), ensure_ascii=False, separators=(",", ":"))

@dataclass(frozen=True)
class PilotSelection:
    main_pilot: VideoCandidateV2
    backup: VideoCandidateV2
    deferred: VideoCandidateV2

def select_pilot_candidates(candidates: tuple[VideoCandidateV2, ...]) -> PilotSelection:
    by_series = {candidate.series_id: candidate for candidate in candidates}
    return PilotSelection(
        main_pilot=by_series["ai_output_autopsy"],
        backup=by_series["digital_red_flags"],
        deferred=by_series["workflow_teardown"],
    )

def _voiceover_for(candidate: VideoCandidateV2) -> str:
    return " ".join([
        candidate.hook,
        "Look at the bad output: it sounds polished, but it missed the one thing the viewer needed.",
        candidate.visible_fail,
        candidate.mechanism,
        candidate.fix,
        candidate.before_after,
        candidate.takeaway,
    ])

def _shot_plan(candidate: VideoCandidateV2) -> tuple[dict[str, Any], ...]:
    return (
        {"scene_id": "s01_fail", "duration_seconds": 5, "purpose": "instant fail recognition", "visual": candidate.first_frame, "overlay_owner": "renderer"},
        {"scene_id": "s02_bad_output", "duration_seconds": 7, "purpose": "show anti-example", "visual": candidate.anti_example or candidate.plausible_nonsense_example, "overlay_owner": "renderer"},
        {"scene_id": "s03_mechanism", "duration_seconds": 9, "purpose": "reveal mechanism", "visual": candidate.mechanism, "overlay_owner": "renderer"},
        {"scene_id": "s04_fix", "duration_seconds": 9, "purpose": "show rule/fix", "visual": candidate.fix, "overlay_owner": "renderer"},
        {"scene_id": "s05_takeaway", "duration_seconds": 8, "purpose": "memorable close", "visual": candidate.before_after + " " + candidate.takeaway, "overlay_owner": "renderer"},
    )

def _wan_prompt_plan(candidate: VideoCandidateV2) -> tuple[WanScenePromptPlan, ...]:
    negative = "no readable text, no logos, no captions, no numbers, no charts with labels, no brand names, no watermark, no UI text baked into image"
    return (
        WanScenePromptPlan("s01_fail", 5, "Create instant tension between messy transcript and useless summary.", "Vertical 9:16 cinematic abstract office desk, messy paper transcript blocks on one side, clean but suspicious summary card shape on the other, high contrast, modern minimal motion graphics feel, no readable text.", negative, "slow push-in with slight parallax", (), ("hook caption", "red USELESS stamp", "split-screen divider"), "Keep center clean; leave top and bottom subtitle-safe zones empty.", "Cut on red stamp pulse to bad-output closeup."),
        WanScenePromptPlan("s02_bad_output", 7, "Make the plausible bad AI output feel recognizable without text baked in.", "Close-up of generic glowing document card with blurred lines, confident polished look but hollow center, office meeting notes in background, subtle warning glow, no readable words.", negative, "micro dolly left to right", (), ("bad summary callouts", "missing owner/blocker/decision labels"), "Avoid important detail in bottom 20 percent for captions.", "Callout lines collapse into compression funnel."),
        WanScenePromptPlan("s03_mechanism", 9, "Reveal equal compression mechanism.", "Abstract stream of many meeting note fragments flowing into a single compressed box, all fragments treated equally, one decision node ignored, clean tech editorial style, no text.", negative, "top-down slight orbit", (), ("decision target node", "equal compression arrows", "mechanism label"), "Leave right third for renderer-owned mechanism labels.", "Decision target node lights up and becomes the fix card."),
        WanScenePromptPlan("s04_fix", 9, "Show the fix as a structured decision-evidence route.", "Minimal workflow route with four empty nodes representing blockers, owners, risks, next steps, structured path replacing messy notes, no readable labels.", negative, "smooth track along route", (), ("blockers", "owners", "risks", "next steps", "decision evidence"), "Keep nodes away from extreme edges.", "Route resolves into before/after split."),
        WanScenePromptPlan("s05_takeaway", 8, "Close with before/after contrast and memorable rule.", "Before-after abstract split: cloudy generic summary dissolves into clear decision route with one highlighted target, satisfying clean ending, no embedded text.", negative, "slow pullback, final hold", (), ("Before", "After", "Don't ask for a summary. Ask for decision evidence."), "Final hold must leave center-lower space for takeaway caption.", "End on static frame for manual review."),
    )

def build_pilot_production_package(
    candidate: VideoCandidateV2,
    review_package: ReviewPackageV2,
    *,
    approval_state: RenderApprovalState = RenderApprovalState.READY_FOR_REVIEW,
    ai_disclosure_review: AIDisclosureReview | None = None,
) -> PilotProductionPackage:
    from autoshorts.claims.fact_gate import validate_claims_for_candidate
    from autoshorts.compliance.ai_disclosure import AIDisclosureReview as Review, DisclosureValue, SyntheticMediaType, validate_ai_disclosure_review

    claim_result = validate_claims_for_candidate(candidate)
    disclosure = ai_disclosure_review or Review.text_only_assistance("Script assistance only; no realistic synthetic person/event/voice generated at this stage.")
    disclosure_result = validate_ai_disclosure_review(disclosure)
    gate = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=review_package,
        requested_resource_tier=ResourceCostTier.DETERMINISTIC_PREVIEW,
        approval_state=approval_state,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=review_package.script_hash,
        ai_disclosure_review=disclosure,
    )
    voiceover = _voiceover_for(candidate)
    command_base = f"{candidate.candidate_id} {candidate.version} {review_package.script_hash}"
    return PilotProductionPackage(
        candidate_id=candidate.candidate_id,
        version=candidate.version,
        script_hash=review_package.script_hash,
        approval_state=approval_state,
        voiceover_text=voiceover,
        german_review_summary="Pilot für AI Output Autopsy: Eine scheinbar gute Meeting-Zusammenfassung verfehlt das Entscheidungsziel. Der Fix ist, nicht nach einer Zusammenfassung zu fragen, sondern nach Decision Evidence: blocker, owners, risks und next steps.",
        hook=candidate.hook,
        first_frame_description=candidate.first_frame,
        visible_fail=candidate.visible_fail,
        anti_example=candidate.anti_example or candidate.plausible_nonsense_example,
        mechanism=candidate.mechanism,
        fix=candidate.fix,
        before_after=candidate.before_after,
        takeaway=candidate.takeaway,
        save_reason=candidate.save_reason,
        share_reason=candidate.share_reason,
        follow_reason=candidate.follow_reason,
        claim_gate_result={"is_valid": claim_result.is_valid, "errors": list(claim_result.errors)},
        ai_disclosure_result={"is_valid": disclosure_result.is_valid, "errors": list(disclosure_result.errors), "synthetic_media_type": disclosure.synthetic_media_type.value, "reason": disclosure.reason},
        resource_gate_result=gate,
        platform_fit=review_package.platform_fit,
        social_copy={
            "youtube_title": "Your AI Meeting Summary Is Missing the Decision",
            "youtube_description": "A polished AI summary can still be useless if it has no decision target. Ask for blockers, owners, risks and next steps instead.",
            "tiktok_caption": "Your AI summary sounds smart. But did it protect the decision?",
            "instagram_caption": "The fix is not a better summary. It is a clearer decision target.",
            "linkedin_caption": candidate.linkedin_caption,
        },
        hashtags={
            "youtube_shorts": ("#AIWorkflow", "#Productivity", "#MeetingNotes"),
            "tiktok": ("#AITips", "#WorkTok", "#ProductivityHack"),
            "instagram_reels": ("#WorkflowDesign", "#Productivity", "#AIForWork"),
            "linkedin_manual": ("#AIProductivity", "#WorkflowDesign", "#DecisionMaking"),
        },
        caption_mode_recommendation="word-by-word",
        tts_plan={"language": "en", "voice_direction": "clear, calm, precise, not hype", "pace": "145-155 wpm", "notes": "Leave micro-pauses after hook and takeaway for subtitle readability."},
        subtitle_alignment_plan={"mode": "word-by-word", "method": "forced alignment after TTS render", "highlight_style": "Hormozi/TikTok-style active word pulse", "quality_gate": "No caption segment may drift beyond spoken word timing."},
        shot_plan=_shot_plan(candidate),
        render_manifest_draft={"final_render_enabled": False, "wan_calls_enabled": False, "ai_image_generation_enabled": False, "renderer_owned_text": True, "caption_mode": "word-by-word", "target_duration_seconds": candidate.duration_seconds, "output": "deterministic preview/timeline only"},
        wan_prompt_plan=_wan_prompt_plan(candidate),
        styleframe_prompt_draft={"status": "not_generated", "approval_required": f"APPROVE_STYLEFRAME {command_base}", "prompt": "Single 9:16 styleframe showing messy meeting transcript versus polished but useless summary card; no readable text; renderer owns all captions, labels and facts."},
        negative_prompts=("readable text", "logos", "brand names", "captions", "numbers", "watermarks", "UI labels", "embedded facts"),
        asset_list=(
            {"asset": "tts_voiceover", "status": "planned", "owner": "deterministic/local until approved"},
            {"asset": "subtitle_alignment", "status": "planned", "owner": "renderer"},
            {"asset": "overlay_labels", "status": "planned", "owner": "renderer"},
            {"asset": "wan_background_motion", "status": "blocked_until_final_render_approval", "owner": "wan_2_2"},
        ),
        estimated_render_cost_time_class="medium",
        deterministic_preview={"type": "text_timeline", "enabled": True, "frames": list(_shot_plan(candidate))},
        score_breakdown=review_package.score.to_dict(),
        approval_commands={"styleframe": f"APPROVE_STYLEFRAME {command_base}", "final_render": f"APPROVED_FOR_FINAL_RENDER {command_base}", "revise": f"REVISE {candidate.candidate_id} <field> \"Änderungswunsch\"", "reject": f"REJECT {candidate.candidate_id} \"Grund\""},
    )

def build_default_pilot_selection_and_package() -> tuple[PilotSelection, PilotProductionPackage]:
    from autoshorts.strategy.spine_v2 import build_demo_candidates_v2

    candidates = build_demo_candidates_v2(limit=3)
    selection = select_pilot_candidates(candidates)
    review_package = build_review_package_v2(selection.main_pilot)
    return selection, build_pilot_production_package(selection.main_pilot, review_package)


"""CLI demo for one controlled V2 pilot production package."""

from __future__ import annotations

import argparse
import json
import sys
from typing import Sequence, TextIO

from autoshorts.production.pilot_package_v2 import build_default_pilot_selection_and_package

def build_demo_pilot_package_v2():
    return build_default_pilot_selection_and_package()

def _payload() -> dict[str, object]:
    selection, package = build_default_pilot_selection_and_package()
    return {
        "main_pilot": package.to_dict(),
        "backup": {"candidate_id": selection.backup.candidate_id, "series_id": selection.backup.series_id, "title": selection.backup.title},
        "deferred": {"candidate_id": selection.deferred.candidate_id, "series_id": selection.deferred.series_id, "title": selection.deferred.title},
        "side_effects": [],
        "external_calls": [],
    }

def main(argv: Sequence[str] | None = None, *, stdout: TextIO | None = None) -> int:
    parser = argparse.ArgumentParser(description="Build one side-effect-free pilot production package.")
    parser.add_argument("--json", action="store_true", help="Emit deterministic JSON")
    args = parser.parse_args(argv)
    out = stdout or sys.stdout
    payload = _payload()
    main_pilot = payload["main_pilot"]
    if args.json:
        print(json.dumps(payload, ensure_ascii=False, separators=(",", ":")), file=out)
        return 0
    print("PilotProductionPackage V2 Demo", file=out)
    print("Side effects: none", file=out)
    print("External calls: none", file=out)
    print(f"Main Pilot: {main_pilot['candidate_id']}", file=out)
    print(f"Script Hash: {main_pilot['script_hash']}", file=out)
    print(f"Score: {main_pilot['score_breakdown']['total']}/100", file=out)
    print(f"Resource Gate: {main_pilot['resource_gate_result']['allowed']} — {main_pilot['resource_gate_result']['reason']}", file=out)
    print(f"Backup: {payload['backup']['candidate_id']}", file=out)
    print(f"Deferred: {payload['deferred']['candidate_id']}", file=out)
    print("\nVoiceover:", file=out)
    print(main_pilot["voiceover_text"], file=out)
    print("\nWan Prompt Plan:", file=out)
    for scene in main_pilot["wan_prompt_plan"]:
        print(f"- {scene['scene_id']} ({scene['duration_seconds']}s): {scene['visual_goal']}", file=out)
    print("\nApproval Commands:", file=out)
    for key, value in main_pilot["approval_commands"].items():
        print(f"- {key}: {value}", file=out)
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


LSP diagnostics introduced by this edit:
<diagnostics file="/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/cli/demo_pilot_production_package_v2.py">
ERROR [41:26] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [42:27] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [43:21] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [44:29] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [44:79] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [45:22] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [46:24] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [48:11] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [50:18] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
ERROR [53:23] "__getitem__" method not defined on type "object" [reportIndexIssue] (Pyright)
</diagnostics>


Need run tests.

Need run tests.


Output:
.......                                                                  [100%]
7 passed in 0.08s


Output:
........................................................................ [ 36%]
........................................................................ [ 73%]
.....................................................                    [100%]
197 passed in 32.78s


from hermes_tools import terminal
wd='/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot'
cmds=[('STATUS','git status --short'),('BRANCH','git branch --show-current'),('LOG','git log --oneline -2'),('CHANGED','git diff --name-only && git ls-files --others --exclude-standard'),('CLI_JSON','python -m autoshorts.cli.demo_pilot_production_package_v2 --json'),('CLI_TEXT','python -m autoshorts.cli.demo_pilot_production_package_v2')]
for name,cmd in cmds:
 print(f'## {name}')
 out=terminal(cmd,workdir=wd)['output']
 print(out[:24000])


Output:
## STATUS
?? autoshorts/cli/demo_pilot_production_package_v2.py
?? autoshorts/production/
?? tests/test_pilot_production_v2.py
## BRANCH
strategy-refactor-v2
## LOG
4c57b52 Operationalize strategy spine v2 review workflow
658c170 Add strategy spine v2 quality gates and review package
## CHANGED
autoshorts/cli/demo_pilot_production_package_v2.py
autoshorts/production/pilot_package_v2.py
autoshorts/production/resource_gate.py
tests/test_pilot_production_v2.py
## CLI_JSON
{"main_pilot":{"candidate_id":"ai-output-autopsy-decision-summary-38s","version":"v2.0.0","script_hash":"72129542b86819fd","approval_state":"ready_for_review","voiceover_text":"Your AI summary is useless because it has no decision target. Look at the bad output: it sounds polished, but it missed the one thing the viewer needed. The summary says 'great discussion' but misses the launch decision. Without a decision target, AI compresses every sentence equally. Ask for blockers, owners, risks, and next steps tied to the decision. Before: generic summary. After: decision evidence with owner and risk. Don't ask for a summary. Ask for decision evidence.","german_review_summary":"Pilot für AI Output Autopsy: Eine scheinbar gute Meeting-Zusammenfassung verfehlt das Entscheidungsziel. Der Fix ist, nicht nach einer Zusammenfassung zu fragen, sondern nach Decision Evidence: blocker, owners, risks und next steps.","hook":"Your AI summary is useless because it has no decision target.","first_frame_description":"Split-screen: 47-minute transcript vs. useless AI summary stamped USELESS.","visible_fail":"The summary says 'great discussion' but misses the launch decision.","anti_example":"A useless AI summary saying great discussion with no owner, blocker, or decision.","mechanism":"Without a decision target, AI compresses every sentence equally.","fix":"Ask for blockers, owners, risks, and next steps tied to the decision.","before_after":"Before: generic summary. After: decision evidence with owner and risk.","takeaway":"Don't ask for a summary. Ask for decision evidence.","save_reason":"Copyable sentence for the next meeting summary.","share_reason":"Colleagues recognize the useless-summary problem.","follow_reason":"Follow for small systems that make AI output useful.","claim_gate_result":{"is_valid":true,"errors":[]},"ai_disclosure_result":{"is_valid":true,"errors":[],"synthetic_media_type":"text_only_assistance","reason":"Script assistance only; no realistic synthetic person/event/voice generated at this stage."},"resource_gate_result":{"candidate_id":"ai-output-autopsy-decision-summary-38s","version":"v2.0.0","script_hash":"72129542b86819fd","requested_resource_tier":"deterministic_preview","approval_state":"ready_for_review","allowed":true,"reason":"Deterministic preview allowed from READY_FOR_REVIEW onward."},"platform_fit":{"youtube_shorts":"strong","tiktok":"strong","instagram_reels":"medium","linkedin_manual":"manual B2B caption; check silent-autoplay readability"},"social_copy":{"youtube_title":"Your AI Meeting Summary Is Missing the Decision","youtube_description":"A polished AI summary can still be useless if it has no decision target. Ask for blockers, owners, risks and next steps instead.","tiktok_caption":"Your AI summary sounds smart. But did it protect the decision?","instagram_caption":"The fix is not a better summary. It is a clearer decision target.","linkedin_caption":"Most meeting summaries fail because they protect no decision. Name the decision first, then ask for blockers, owners, risks and next steps."},"hashtags":{"youtube_shorts":["#AIWorkflow","#Productivity","#MeetingNotes"],"tiktok":["#AITips","#WorkTok","#ProductivityHack"],"instagram_reels":["#WorkflowDesign","#Productivity","#AIForWork"],"linkedin_manual":["#AIProductivity","#WorkflowDesign","#DecisionMaking"]},"caption_mode_recommendation":"word-by-word","tts_plan":{"language":"en","voice_direction":"clear, calm, precise, not hype","pace":"145-155 wpm","notes":"Leave micro-pauses after hook and takeaway for subtitle readability."},"subtitle_alignment_plan":{"mode":"word-by-word","method":"forced alignment after TTS render","highlight_style":"Hormozi/TikTok-style active word pulse","quality_gate":"No caption segment may drift beyond spoken word timing."},"shot_plan":[{"scene_id":"s01_fail","duration_seconds":5,"purpose":"instant fail recognition","visual":"Split-screen: 47-minute transcript vs. useless AI summary stamped USELESS.","overlay_owner":"renderer"},{"scene_id":"s02_bad_output","duration_seconds":7,"purpose":"show anti-example","visual":"A useless AI summary saying great discussion with no owner, blocker, or decision.","overlay_owner":"renderer"},{"scene_id":"s03_mechanism","duration_seconds":9,"purpose":"reveal mechanism","visual":"Without a decision target, AI compresses every sentence equally.","overlay_owner":"renderer"},{"scene_id":"s04_fix","duration_seconds":9,"purpose":"show rule/fix","visual":"Ask for blockers, owners, risks, and next steps tied to the decision.","overlay_owner":"renderer"},{"scene_id":"s05_takeaway","duration_seconds":8,"purpose":"memorable close","visual":"Before: generic summary. After: decision evidence with owner and risk. Don't ask for a summary. Ask for decision evidence.","overlay_owner":"renderer"}],"render_manifest_draft":{"final_render_enabled":false,"wan_calls_enabled":false,"ai_image_generation_enabled":false,"renderer_owned_text":true,"caption_mode":"word-by-word","target_duration_seconds":38,"output":"deterministic preview/timeline only"},"wan_prompt_plan":[{"scene_id":"s01_fail","duration_seconds":5,"visual_goal":"Create instant tension between messy transcript and useless summary.","prompt":"Vertical 9:16 cinematic abstract office desk, messy paper transcript blocks on one side, clean but suspicious summary card shape on the other, high contrast, modern minimal motion graphics feel, no readable text.","negative_prompt":"no readable text, no logos, no captions, no numbers, no charts with labels, no brand names, no watermark, no UI text baked into image","camera_motion":"slow push-in with slight parallax","required_overlay_text":[],"renderer_owned_overlay_elements":["hook caption","red USELESS stamp","split-screen divider"],"safe_zone_notes":"Keep center clean; leave top and bottom subtitle-safe zones empty.","transition_to_next_scene":"Cut on red stamp pulse to bad-output closeup."},{"scene_id":"s02_bad_output","duration_seconds":7,"visual_goal":"Make the plausible bad AI output feel recognizable without text baked in.","prompt":"Close-up of generic glowing document card with blurred lines, confident polished look but hollow center, office meeting notes in background, subtle warning glow, no readable words.","negative_prompt":"no readable text, no logos, no captions, no numbers, no charts with labels, no brand names, no watermark, no UI text baked into image","camera_motion":"micro dolly left to right","required_overlay_text":[],"renderer_owned_overlay_elements":["bad summary callouts","missing owner/blocker/decision labels"],"safe_zone_notes":"Avoid important detail in bottom 20 percent for captions.","transition_to_next_scene":"Callout lines collapse into compression funnel."},{"scene_id":"s03_mechanism","duration_seconds":9,"visual_goal":"Reveal equal compression mechanism.","prompt":"Abstract stream of many meeting note fragments flowing into a single compressed box, all fragments treated equally, one decision node ignored, clean tech editorial style, no text.","negative_prompt":"no readable text, no logos, no captions, no numbers, no charts with labels, no brand names, no watermark, no UI text baked into image","camera_motion":"top-down slight orbit","required_overlay_text":[],"renderer_owned_overlay_elements":["decision target node","equal compression arrows","mechanism label"],"safe_zone_notes":"Leave right third for renderer-owned mechanism labels.","transition_to_next_scene":"Decision target node lights up and becomes the fix card."},{"scene_id":"s04_fix","duration_seconds":9,"visual_goal":"Show the fix as a structured decision-evidence route.","prompt":"Minimal workflow route with four empty nodes representing blockers, owners, risks, next steps, structured path replacing messy notes, no readable labels.","negative_prompt":"no readable text, no logos, no captions, no numbers, no charts with labels, no brand names, no watermark, no UI text baked into image","camera_motion":"smooth track along route","required_overlay_text":[],"renderer_owned_overlay_elements":["blockers","owners","risks","next steps","decision evidence"],"safe_zone_notes":"Keep nodes away from extreme edges.","transition_to_next_scene":"Route resolves into before/after split."},{"scene_id":"s05_takeaway","duration_seconds":8,"visual_goal":"Close with before/after contrast and memorable rule.","prompt":"Before-after abstract split: cloudy generic summary dissolves into clear decision route with one highlighted target, satisfying clean ending, no embedded text.","negative_prompt":"no readable text, no logos, no captions, no numbers, no charts with labels, no brand names, no watermark, no UI text baked into image","camera_motion":"slow pullback, final hold","required_overlay_text":[],"renderer_owned_overlay_elements":["Before","After","Don't ask for a summary. Ask for decision evidence."],"safe_zone_notes":"Final hold must leave center-lower space for takeaway caption.","transition_to_next_scene":"End on static frame for manual review."}],"styleframe_prompt_draft":{"status":"not_generated","approval_required":"APPROVE_STYLEFRAME ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd","prompt":"Single 9:16 styleframe showing messy meeting transcript versus polished but useless summary card; no readable text; renderer owns all captions, labels and facts."},"negative_prompts":["readable text","logos","brand names","captions","numbers","watermarks","UI labels","embedded facts"],"asset_list":[{"asset":"tts_voiceover","status":"planned","owner":"deterministic/local until approved"},{"asset":"subtitle_alignment","status":"planned","owner":"renderer"},{"asset":"overlay_labels","status":"planned","owner":"renderer"},{"asset":"wan_background_motion","status":"blocked_until_final_render_approval","owner":"wan_2_2"}],"estimated_render_cost_time_class":"medium","deterministic_preview":{"type":"text_timeline","enabled":true,"frames":[{"scene_id":"s01_fail","duration_seconds":5,"purpose":"instant fail recognition","visual":"Split-screen: 47-minute transcript vs. useless AI summary stamped USELESS.","overlay_owner":"renderer"},{"scene_id":"s02_bad_output","duration_seconds":7,"purpose":"show anti-example","visual":"A useless AI summary saying great discussion with no owner, blocker, or decision.","overlay_owner":"renderer"},{"scene_id":"s03_mechanism","duration_seconds":9,"purpose":"reveal mechanism","visual":"Without a decision target, AI compresses every sentence equally.","overlay_owner":"renderer"},{"scene_id":"s04_fix","duration_seconds":9,"purpose":"show rule/fix","visual":"Ask for blockers, owners, risks, and next steps tied to the decision.","overlay_owner":"renderer"},{"scene_id":"s05_takeaway","duration_seconds":8,"purpose":"memorable close","visual":"Before: generic summary. After: decision evidence with owner and risk. Don't ask for a summary. Ask for decision evidence.","overlay_owner":"renderer"}]},"score_breakdown":{"total":86,"render_allowed":true,"reject_reasons":[],"breakdown":{"problem_urgency":{"points":13,"max_points":15,"rationale":"Visible everyday fail with recognizable audience need."},"first_frame_clarity":{"points":10,"max_points":12,"rationale":"First frame shows a conflict without relying on audio."},"hook_curiosity":{"points":10,"max_points":12,"rationale":"Hook opens a mechanism loop; generic hooks lose points."},"mechanism_value":{"points":11,"max_points":12,"rationale":"Mechanism is tied to anti-example/concrete scene."},"practical_payoff":{"points":13,"max_points":15,"rationale":"Fix and takeaway are actionable."},"retention_path":{"points":8,"max_points":10,"rationale":"Before/after plus motion-led path."},"share_save_potential":{"points":9,"max_points":10,"rationale":"Save, share, and follow reasons are explicit."},"human_texture":{"points":7,"max_points":8,"rationale":"Concrete human texture, not just an enum."},"series_fit":{"points":4,"max_points":4,"rationale":"Known TrueTrace series."},"saturation_defense":{"points":1,"max_points":2,"rationale":"Saturation risk penalty applied."}}},"approval_commands":{"styleframe":"APPROVE_STYLEFRAME ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd","final_render":"APPROVED_FOR_FINAL_RENDER ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd","revise":"REVISE ai-output-autopsy-decision-summary-38s <field> \"Änderungswunsch\"","reject":"REJECT ai-output-autopsy-decision-summary-38s \"Grund\""}},"backup":{"candidate_id":"digital-red-flags-fake-invoice-35s","series_id":"digital_red_flags","title":"This fake invoice looks normal"},"deferred":{"candidate_id":"workflow-teardown-exception-path-40s","series_id":"workflow_teardown","title":"Your automation only knows the happy path"},"side_effects":[],"external_calls":[]}
## CLI_TEXT
PilotProductionPackage V2 Demo
Side effects: none
External calls: none
Main Pilot: ai-output-autopsy-decision-summary-38s
Script Hash: 72129542b86819fd
Score: 86/100
Resource Gate: True — Deterministic preview allowed from READY_FOR_REVIEW onward.
Backup: digital-red-flags-fake-invoice-35s
Deferred: workflow-teardown-exception-path-40s

Voiceover:
Your AI summary is useless because it has no decision target. Look at the bad output: it sounds polished, but it missed the one thing the viewer needed. The summary says 'great discussion' but misses the launch decision. Without a decision target, AI compresses every sentence equally. Ask for blockers, owners, risks, and next steps tied to the decision. Before: generic summary. After: decision evidence with owner and risk. Don't ask for a summary. Ask for decision evidence.

Wan Prompt Plan:
- s01_fail (5s): Create instant tension between messy transcript and useless summary.
- s02_bad_output (7s): Make the plausible bad AI output feel recognizable without text baked in.
- s03_mechanism (9s): Reveal equal compression mechanism.
- s04_fix (9s): Show the fix as a structured decision-evidence route.
- s05_takeaway (8s): Close with before/after contrast and memorable rule.

Approval Commands:
- styleframe: APPROVE_STYLEFRAME ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd
- final_render: APPROVED_FOR_FINAL_RENDER ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd
- revise: REVISE ai-output-autopsy-decision-summary-38s <field> "Änderungswunsch"
- reject: REJECT ai-output-autopsy-decision-summary-38s "Grund"


Sir, Sprint 1.6 ist umgesetzt — kontrolliert, ohne Wan-Render, ohne AI-Bildgenerierung, ohne Plattform-API und ohne Content-Massenproduktion. Der Bot ist jetzt bereit für eine bewusste Entscheidung: überarbeiten, Styleframe freigeben oder genau einen Finalrender freigeben.

## Git Status

Aktueller Branch:

`strategy-refactor-v2`

Aktueller git status:

```text
?? autoshorts/cli/demo_pilot_production_package_v2.py
?? autoshorts/production/
?? tests/test_pilot_production_v2.py
```

Sprint-1.6 ist **noch nicht committed**.

## Sprint-1.5 Commit

Sprint 1.5 wurde erfolgreich gesichert:

```text
4c57b52 Operationalize strategy spine v2 review workflow
```

Vorheriger Sprint-1 Commit:

```text
658c170 Add strategy spine v2 quality gates and review package
```

## Geänderte Dateien Sprint 1.6

Neu:

```text
autoshorts/production/resource_gate.py
autoshorts/production/pilot_package_v2.py
autoshorts/cli/demo_pilot_production_package_v2.py
tests/test_pilot_production_v2.py
```

## Pytest Ergebnis

Final:

```text
197 passed in 32.78s
```

## Neue CLI

Nutzbar:

```bash
python -m autoshorts.cli.demo_pilot_production_package_v2
python -m autoshorts.cli.demo_pilot_production_package_v2 --json
```

Eigenschaften verifiziert:

- maximal 1 Hauptpilot
- maximal 1 Backup
- 1 zurückgestellter Kandidat
- kein Finalrender
- keine Wan-Calls
- keine AI-Bildgenerierung
- keine Telegram-API
- keine Plattform-API
- `side_effects=[]`
- `external_calls=[]`

## Auswahl der 3 Kandidaten

Wie von Ihnen präferiert übernommen:

### Hauptpilot

`AI Output Autopsy / Meeting Summary Decision Target`

Candidate ID:

`ai-output-autopsy-decision-summary-38s`

Begründung:

- direkt anschlussfähig an TrueTrace-Strategie
- starkes wiedererkennbares Alltagsproblem
- niedriger Claim-Risk
- klare Before/After-Struktur
- gutes erstes echtes Video, weil es den Kanal erklärt, ohne wie ein generischer AI-Tipp zu wirken

### Backup

`Digital Red Flags / Fake Invoice`

Candidate ID:

`digital-red-flags-fake-invoice-35s`

### Zurückgestellt

`Workflow Teardown / Exception Path`

Candidate ID:

`workflow-teardown-exception-path-40s`

## Hauptpilot Kurzfassung

**Candidate ID:**
`ai-output-autopsy-decision-summary-38s`

**Version:**
`v2.0.0`

**Script Hash:**
`72129542b86819fd`

**Approval State:**
`READY_FOR_REVIEW`

**Hook:**
Your AI summary is useless because it has no decision target.

**Visible Fail:**
The summary says “great discussion” but misses the launch decision.

**Anti Example:**
A useless AI summary saying great discussion with no owner, blocker, or decision.

**Mechanism:**
Without a decision target, AI compresses every sentence equally.

**Fix:**
Ask for blockers, owners, risks, and next steps tied to the decision.

**Takeaway:**
Don’t ask for a summary. Ask for decision evidence.

## Finaler englischer Voiceover-Text

```text
Your AI summary is useless because it has no decision target.

Look at the bad output: it sounds polished, but it missed the one thing the viewer needed.

The summary says “great discussion” but misses the launch decision.

Without a decision target, AI compresses every sentence equally.

Ask for blockers, owners, risks, and next steps tied to the decision.

Before: generic summary. After: decision evidence with owner and risk.

Don’t ask for a summary. Ask for decision evidence.
```

## Deutsches Review Summary

Eine scheinbar gute Meeting-Zusammenfassung verfehlt das eigentliche Entscheidungsziel. Der Fix ist, nicht nach einer generischen Zusammenfassung zu fragen, sondern nach **Decision Evidence**: Blocker, Owner, Risiken und Next Steps.

## Score Breakdown

Gesamtscore:

`86/100`

Breakdown:

- problem_urgency: `13/15`
- first_frame_clarity: `10/12`
- hook_curiosity: `10/12`
- mechanism_value: `11/12`
- practical_payoff: `13/15`
- retention_path: `8/10`
- share_save_potential: `9/10`
- human_texture: `7/8`
- series_fit: `4/4`
- saturation_defense: `1/2`

Interpretation:

Solider bis sehr guter Pilot. Nicht künstlich auf 100 aufgeblasen. Kleine Abzüge wegen mittlerer Saturation und weil der visuelle Mechanismus noch im Render/ShotPlan beweisen muss, dass er wirklich stark wirkt.

## Claim Gate Result

```text
valid: true
errors: []
```

Claim:

```text
Decision-targeted summaries preserve relevance better than generic summaries.
```

Risk:

`low`

Keine externe Recherche oder API genutzt. Nur manuelle Claim-Validierung.

## AI Disclosure Result

```text
valid: true
synthetic_media_type: text_only_assistance
reason: Script assistance only; no realistic synthetic person/event/voice generated at this stage.
```

Keine realistische Person, kein realistisches Event, keine realistische Stimme generiert.

## Resource Gate Result

Für deterministic preview:

```text
requested_resource_tier: deterministic_preview
approval_state: ready_for_review
allowed: true
reason: Deterministic preview allowed from READY_FOR_REVIEW onward.
```

Wichtige Blocker sind implementiert:

- `WAN_FINAL_RENDER` blockiert ohne `APPROVED_FOR_FINAL_RENDER`
- `PUBLISHING` immer blockiert
- falscher `script_hash` blockiert
- falsche `candidate_id` / `version` blockiert
- AI Disclosure unclear blockiert
- Claim Gate invalid blockiert

## Platform Fit

- YouTube Shorts: strong
- TikTok: strong
- Instagram Reels: medium
- LinkedIn Manual: manual B2B caption; check silent-autoplay readability

## Social Copy

**YouTube Title:**
Your AI Meeting Summary Is Missing the Decision

**YouTube Description:**
A polished AI summary can still be useless if it has no decision target. Ask for blockers, owners, risks and next steps instead.

**TikTok Caption:**
Your AI summary sounds smart. But did it protect the decision?

**Instagram Caption:**
The fix is not a better summary. It is a clearer decision target.

**LinkedIn Caption:**
Most meeting summaries fail because they protect no decision. Name the decision first, then ask for blockers, owners, risks and next steps.

## Hashtags

**YouTube Shorts:**

```text
#AIWorkflow #Productivity #MeetingNotes
```

**TikTok:**

```text
#AITips #WorkTok #ProductivityHack
```

**Instagram Reels:**

```text
#WorkflowDesign #Productivity #AIForWork
```

**LinkedIn Manual:**

```text
#AIProductivity #WorkflowDesign #DecisionMaking
```

## Caption Mode Empfehlung

`word-by-word`

Begründung:

- Voiceover ist erklärend und präzise.
- Zuschauer sollen gleichzeitig hören und mitlesen können.
- Das aktive Wort-Highlighting ist hier wichtiger als dekorative Captions.
- Phrase-by-phrase wäre günstiger, aber weniger professionell.
- Keyword-pulse wäre für diesen Pilot zu grob.

## TTS Plan

- Sprache: Englisch
- Voice Direction: clear, calm, precise, not hype
- Pace: 145–155 WPM
- Pausen:
  - kurze Pause nach Hook
  - kurze Pause vor Takeaway
- Ziel: professionell, nicht Influencer-Marktschreier. Man glaubt es kaum, aber das hilft.

## Subtitle Alignment Plan

- Modus: `word-by-word`
- Methode: forced alignment nach TTS-Render
- Highlight Style: Hormozi/TikTok-style active word pulse
- Quality Gate:
  - kein Caption-Segment darf merklich vom gesprochenen Wort abweichen
  - Text bleibt renderer-owned
  - keine Wan-eingebrannten Captions

## ShotPlan Kurzfassung

### s01_fail — 5s

Ziel: sofortiges Erkennen des Problems
Visual: Split-screen Transcript vs. useless summary
Overlay: Hook, red USELESS stamp, split-screen divider

### s02_bad_output — 7s

Ziel: Anti-Beispiel zeigen
Visual: polished bad summary without decision evidence
Overlay: missing owner / blocker / decision labels

### s03_mechanism — 9s

Ziel: Mechanismus zeigen
Visual: notes compressed equally into generic summary
Overlay: decision target node, equal compression arrows

### s04_fix — 9s

Ziel: konkrete Regel zeigen
Visual: route from messy notes to blockers / owners / risks / next steps
Overlay: renderer-owned labels

### s05_takeaway — 8s

Ziel: merkbarer Abschluss
Visual: Before/After contrast
Overlay: “Don’t ask for a summary. Ask for decision evidence.”

## RenderManifest Draft

```text
final_render_enabled: false
wan_calls_enabled: false
ai_image_generation_enabled: false
renderer_owned_text: true
caption_mode: word-by-word
target_duration_seconds: 38
output: deterministic preview/timeline only
```

## Wan 2.2 Prompt Plan Kurzfassung

Keine Wan-Generierung wurde ausgeführt.

### s01_fail

Visual Goal: tension between messy transcript and useless summary
Prompt: vertical abstract office desk, messy transcript blocks vs polished suspicious summary card, no readable text
Camera: slow push-in
Renderer-owned overlays: hook caption, USELESS stamp, divider

### s02_bad_output

Visual Goal: plausible but useless AI output
Prompt: glowing generic document card, blurred lines, hollow polished feel, no readable text
Camera: micro dolly
Renderer-owned overlays: bad summary callouts, missing owner/blocker/decision labels

### s03_mechanism

Visual Goal: equal compression mechanism
Prompt: meeting note fragments flowing into compressed box, ignored decision node, no text
Camera: top-down slight orbit
Renderer-owned overlays: decision target node, compression arrows

### s04_fix

Visual Goal: structured decision-evidence route
Prompt: workflow route with four empty nodes, no readable labels
Camera: smooth track
Renderer-owned overlays: blockers, owners, risks, next steps

### s05_takeaway

Visual Goal: before/after and rule
Prompt: cloudy generic summary dissolves into clear decision route, no embedded text
Camera: slow pullback
Renderer-owned overlays: before, after, final takeaway

## Negative Prompts

```text
readable text
logos
brand names
captions
numbers
watermarks
UI labels
embedded facts
```

Wan soll also keine Texte, Labels, Fakten, Logos oder Captions erzeugen. Alles bleibt renderer-owned.

## Asset List

- `tts_voiceover`
  - status: planned
  - owner: deterministic/local until approved

- `subtitle_alignment`
  - status: planned
  - owner: renderer

- `overlay_labels`
  - status: planned
  - owner: renderer

- `wan_background_motion`
  - status: blocked_until_final_render_approval
  - owner: Wan 2.2

## Estimated Render Cost/Time Class

`medium`

## Approval Commands

### Styleframe freigeben

```text
APPROVE_STYLEFRAME ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd
```

### Finalrender freigeben

```text
APPROVED_FOR_FINAL_RENDER ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd
```

### Revision anfordern

```text
REVISE ai-output-autopsy-decision-summary-38s <field> "Änderungswunsch"
```

### Ablehnen

```text
REJECT ai-output-autopsy-decision-summary-38s "Grund"
```

## Offene Punkte

- Sprint-1.6 Änderungen sind noch nicht committed.
- Es wurde nur ein deterministic text/timeline preview package erstellt, kein Video.
- Noch kein TTS-Audio erzeugt.
- Noch kein forced alignment durchgeführt.
- Noch kein Styleframe generiert.
- Noch kein Wan-2.2-Render.
- Keine Plattform-API.
- Kein Posting.
- Kein Dashboard.

## Nächster sinnvoller Schritt

Sie entscheiden jetzt bewusst eine der vier Optionen:

1. **Revision:**
   `REVISE ai-output-autopsy-decision-summary-38s <field> "Änderungswunsch"`

2. **Styleframe freigeben:**
   `APPROVE_STYLEFRAME ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd`

3. **Finalrender freigeben:**
   `APPROVED_FOR_FINAL_RENDER ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd`

4. **Ablehnen:**
   `REJECT ai-output-autopsy-decision-summary-38s "Grund"`

Explanation:
**Summarizing Git Status**

I need to produce a final concise summary while ensuring I include all necessary details. For instance, I should mention that there was no commit at 1.6 and possibly include the changed files. It’d be helpful to check the git status output for context too. I see there are 197 tests, but no media was generated. Lastly, I must summarize the prompt plan effectively in this summary.


[M G] JARVIS, sehr gut. Wir gehen jetzt zum ersten echten Video über, aber nicht direkt zum Wan-2.2-Finalrender.

Bitte führe jetzt Sprint 1.7 aus:

Pilot Preview Render V2 — First Telegram Approval Video

Ziel:
Erstelle für den Hauptpilot ein echtes Preview-/Konzeptvideo, das ich per Telegram prüfen kann. Es soll Voiceover, Timing, Captions, Szenenfolge, Overlays und grobe Visuals zeigen. Es ist noch NICHT das finale Wan-2.2-Video.

Freigabe für Preview:

APPROVED_FOR_PREVIEW_RENDER ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd

Wichtig:
Diese Freigabe erlaubt nur den Preview-Render, nicht den Finalrender.

1. Sprint 1.6 sichern

* git status prüfen
* pytest ausführen
* Sprint-1.6-Dateien committen, sofern sauber

Commit Message:
Add pilot production package and resource gates

Wenn Tests fehlschlagen:
nicht committen, sondern Status melden.

2. Preview-Render-Gate ergänzen

Bitte ergänze oder nutze einen neuen Approval State:

APPROVED_FOR_PREVIEW_RENDER

Regeln:

* TTS-Erzeugung erlaubt.
* Subtitle Alignment erlaubt.
* deterministische Preview/Animatic erlaubt.
* Telegram-Versand des Preview-Videos erlaubt, falls Telegram bereits eingerichtet ist.
* Wan-2.2-Finalrender bleibt blockiert.
* AI-Bildgenerierung bleibt blockiert, ausser separat mit APPROVE_STYLEFRAME freigegeben.
* Plattform-API bleibt blockiert.
* Auto-Posting bleibt blockiert.

3. Erstelle ein echtes Preview-/Konzeptvideo

Für Candidate:

ai-output-autopsy-decision-summary-38s
Version: v2.0.0
Script Hash: 72129542b86819fd

Preview-Anforderungen:

* Format: 9:16
* Auflösung: gern 540x960 oder 720x1280, nicht final nötig
* Dauer: ca. 35–40 Sekunden
* Voiceover: englisch
* TTS: klar, ruhig, professionell, nicht hype
* Captions: word-by-word oder bestmöglich synchronisiert
* Overlays: renderer-owned
* Visuals: deterministische Cards / Motion / Placeholder Visuals
* sichtbarer Hinweis: PREVIEW / NOT FINAL
* keine eingebrannten Wan-Texte
* keine finalen AI-Backgrounds nötig

Ziel des Preview-Videos:
Ich muss prüfen können:

* Hook funktioniert?
* Tempo passt?
* Voiceover klingt gut?
* Untertitel sind lesbar?
* Szenenwechsel sind verständlich?
* Mechanismus wird sichtbar?
* Takeaway bleibt hängen?
* Video wirkt nicht wie AI-Slop?

4. TTS und Caption Alignment

Bitte erstelle für das Preview:

* TTS Audio
* gemessene Audiodauer
* Subtitle Cues
* wenn forced alignment verfügbar: nutzen
* wenn forced alignment noch nicht stabil verfügbar: bestmögliche zeitbasierte Word-/Phrase-Sync-Preview erstellen und als Einschränkung markieren

Quality Gate:
Wenn Captions massiv asynchron sind, Preview nicht als ready markieren.

5. Preview-Szenen

Nutze den bestehenden ShotPlan:

s01_fail — ca. 5s
Split-screen Transcript vs. useless summary

s02_bad_output — ca. 7s
Polished bad summary without decision evidence

s03_mechanism — ca. 9s
Notes compressed equally into generic summary

s04_fix — ca. 9s
Route from messy notes to blockers / owners / risks / next steps

s05_takeaway — ca. 8s
Before/After contrast and final takeaway

Wichtig:
Texte, Labels, Captions, Pfeile, USELESS-Stempel und Takeaway bleiben renderer-owned.

6. Telegram Delivery

Wenn Telegram-Versand bereits eingerichtet und sicher ist:
Sende mir das Preview-MP4 per Telegram mit Kurzreview.

Wenn Telegram-Versand nicht aktiv ist:
Erstelle stattdessen:

* lokalen MP4-Pfad
* SHA/Checksum
* Telegram Delivery Plan
* Review Text

Nicht blockieren, nur transparent berichten.

7. Kein Finalrender

Bitte ausdrücklich NICHT ausführen:

* kein Wan-2.2-Finalrender
* keine Wan-Calls
* keine AI-Bildgenerierung
* keine Plattform-API
* kein Auto-Posting
* kein Manual Posting Pack als final freigegeben

8. Output am Ende

Bitte berichte kurz:

* git status
* Commit Hash von Sprint 1.6
* pytest Ergebnis
* Preview Approval State
* erzeugte Dateien
* MP4-Pfad
* Audio-Pfad
* Subtitle-/ASS-/SRT-Pfad, falls vorhanden
* Audiodauer
* Preview-Dauer
* bekannte Einschränkungen
* ob Telegram-Versand erfolgreich war
* nächstmögliche Review Commands

9. Danach mögliche Kommandos

Wenn ich überarbeiten will:

REVISE
ai-output-autopsy-decision-summary-38s <field> "Änderungswunsch"

Wenn das Preview passt und genau ein finaler Wan-2.2-Render erlaubt ist:

APPROVED_FOR_FINAL_RENDER ai-output-autopsy-decision-summary-38s v2.0.0 72129542b86819fd

Wenn ich ablehne:

REJECT ai-output-autopsy-decision-summary-38s "Grund"

Wichtigster Grundsatz:
Jetzt ein echtes Preview-Video erstellen, aber Finalrender bleibt gesperrt, bis ich es nach Sichtung des Previews ausdrücklich freigebe.


Explanation:
**Planning TTS and video features**

I see we need to take action regarding TTS since the user has asked about it. I’m considering the edge-tts skill as it seems relevant. There's also a possibility of needing to create a video, especially if it's already on social media. First, I need to commit to version 1.6 and then think about implementing a preview gate, potentially using enums. For the video, I should check dependencies and use tools like Python's Pillow, MoviePy, or FFmpeg.


edge-tts

---
name: edge-tts
description: "Text-to-speech using Microsoft Edge TTS. Supports multiple voices including German male/female options. Used for voice messages and audio output."
version: 1.0.0
author: Hermes Agent
license: MIT
metadata:
  hermes:
    tags: [TTS, Voice, Audio, Edge]
    related_skills: [sherpa-onnx-tts]
---

# Edge TTS

Text-to-speech using Microsoft Edge TTS (`edge-tts` Python package). No API key required — uses Microsoft's free Edge browser TTS engine.

## When to Use

Use this skill when:
- You need to convert text to speech for voice messages
- You want to test or compare different TTS voices
- The user asks for a voice message or speech output
- TTS configuration needs to be changed

## Prerequisites

```bash
pip install edge-tts
```

## User Voice Preference

**Default voice: `de-DE-KillianNeural`** (German male, natural, clear)
Set in `~/.hermes/config.yaml` → `tts.edge.voice`.

## German Voices Available

| Voice | Gender | Style | Description |
|-------|--------|-------|-------------|
| `de-DE-KillianNeural` | Male | Natural | Clear, natural, slightly youthful (DEFAULT) |
| `de-DE-ConradNeural` | Male | Friendly | Warm, positive, slightly melodic |
| `de-DE-FlorianMultilingualNeural` | Male | Professional | Clear, neutral, professional |
| `de-DE-AmalaNeural` | Female | Friendly | Positive, warm |
| `de-DE-KatjaNeural` | Female | Friendly | Positive, clear |
| `de-DE-SeraphinaMultilingualNeural` | Female | General | Natural, clear |

## Usage

### Basic Synthesis

```bash
edge-tts --voice de-DE-ConradNeural --text "Guten Tag, Sir." --write-media output.mp3
```

### Testing a Voice

```bash
edge-tts --voice de-DE-FlorianMultilingualNeural --text "Testnachricht" --write-media /tmp/test.mp3
```

### Listing Available Voices

```bash
python3 -m edge_tts --list-voices | grep "de-DE"
```

### Configuring in Hermes

Update `~/.hermes/config.yaml`:

```yaml
tts:
  provider: edge
  edge:
    voice: de-DE-ConradNeural
```

## Pitfalls

- **Voice compatibility**: Not all voices work with all text. German text requires a German voice (`de-DE-*`).
- **File format**: Edge TTS outputs MP3 by default. Convert to OGG for Telegram voice messages if needed: `ffmpeg -i input.mp3 -codec:a libopus -b:a 128k output.ogg`
- **Gateway restart**: After changing the TTS voice in config, restart the gateway for changes to take effect.
- **Character limits**: Very long texts may produce unusually long audio. Split long messages before TTS.
- **Config verification**: After updating `tts.edge.voice` in config, always verify the change took effect by reading the config back. YAML writes can silently fail or be overwritten by subsequent edits. Check the TTS response field `voice_compatible: true` — if `false`, the voice may not have been applied.
- **Voice switching workflow**: Always test a voice with `edge-tts --voice <voice> --text "Test" --write-media /tmp/test.mp3` before committing to it. Multiple voices can sound similar; listening is the only reliable way to choose.
- **Config persistence**: Python one-liner YAML edits may not persist — always verify with a separate read-back step.

## Shorts / AutoShorts Baseline Voice Use

For short-form content pipelines where the GPU is needed for ComfyUI/Wan/Qwen image or video generation, use EdgeTTS as the first baseline voice because it does not consume local GPU resources and is quick to iterate.

Recommended first English A/B voices for a precise, non-guru AI/workflow channel tone:

```bash
edge-tts --voice en-GB-RyanNeural --text "Most AI automations do not fail dramatically. They quietly rot." --write-media /tmp/ryan.mp3
edge-tts --voice en-US-ChristopherNeural --text "Most AI automations do not fail dramatically. They quietly rot." --write-media /tmp/christopher.mp3
```

For AutoShorts-style production code, keep EdgeTTS behind an explicit adapter boundary:

```text
TTSProviderPlan(edge_tts, voice)
→ EdgeTTSRequest(text, output_path.mp3)
→ edge-tts CLI call
→ EdgeTTSResult(side_effects=("call_edge_tts", "write_audio"), external_calls=("edge-tts",))
```

Then mux/burn captions in a separate FFmpeg boundary:

```text
MP4 timing preview + EdgeTTS MP3 + renderer-owned ASS subtitles
→ FFmpeg audio/caption review MP4
```

Important FFmpeg pitfall: do not blindly use `-shortest` when adding audio to a longer timing preview. It can silently truncate the video to the shorter voiceover duration. Preserve the planned video duration unless the feature explicitly asks to trim to audio length.

Compare against any external/cloud TTS such as NVIDIA Magpie with the same 15–20 second script sample. Score clarity, pronunciation of AI/workflow terms, retention energy, artifacts, latency, cost, and operational reliability. Whisper is not a TTS model; use it later for transcript/alignment QA only.

## Linked Resources

- `references/german-voice-comparison.md` — German male voice comparison data from session 2026-05-07, with recommendations for voice selection.
- `references/autoshorts-edge-tts-audio-caption.md` — EdgeTTS boundary pattern for AutoShorts audio/caption review MP4s, including ffprobe duration alignment and the `-shortest` pitfall.

## Voice Switching Workflow

1. List available voices: `python3 -m edge_tts --list-voices | grep "de-DE"`
2. Test candidate voice: `edge-tts --voice <voice> --text "Testnachricht" --write-media /tmp/test.mp3`
3. Listen and decide
4. Update config: `tts.edge.voice: <chosen_voice>`
5. Restart gateway

## Related Skills

- `sherpa-onnx-tts` — Offline TTS with local German female voice (Eva). No internet required.

To view linked files, call skill_view(name, file_path) where file_path is e.g. 'references/api.md' or 'assets/config.yaml'


todo

Sprint 1.6 Status prüfen, pytest ausführen und committen

Preview-Render-Gate APPROVED_FOR_PREVIEW_RENDER ergänzen und testen

TTS, Subtitle-Cues und deterministisches 9:16 Preview-MP4 erzeugen

Preview verifizieren, Telegram senden bzw. lokalen Pfad/Checksums berichten


terminal

Output:
?? autoshorts/cli/demo_pilot_production_package_v2.py
?? autoshorts/production/
?? tests/test_pilot_production_v2.py

BRANCH=strategy-refactor-v2

PYTEST
........................................................................ [ 36%]
........................................................................ [ 73%]
.....................................................                    [100%]
197 passed in 32.42s


git add autoshorts/cli/demo_pilot_production_package_v2.py autoshorts/production/resource_gate.py autoshorts/production/pilot_package_v2.py tests/test_pilot_production_v2.py && git commit -m "Add pilot production package and resource gates"


Output:
[strategy-refactor-v2 b1766a4] Add pilot production package and resource gates
 4 files changed, 621 insertions(+)
 create mode 100644 autoshorts/cli/demo_pilot_production_package_v2.py
 create mode 100644 autoshorts/production/pilot_package_v2.py
 create mode 100644 autoshorts/production/resource_gate.py
 create mode 100644 tests/test_pilot_production_v2.py


Explanation:
**Considering enum and tests**

I need to add an enum state and associated rules for my project, which feels essential for clarity. I'm pondering the possibility of writing tests to ensure everything works as expected. Using a patch might help streamline this process. It seems a bit complicated, but breaking it down should make it manageable. I want to ensure I cover all aspects thoroughly so nothing gets overlooked. Let's see how to approach this step by step!

patch

    APPROVED_FOR_STYLEFRAME = "approved_for_styleframe"
    APPROVED_FOR_FINAL_RENDER = "approved_for_final_render"

    APPROVED_FOR_STYLEFRAME = "approved_for_styleframe"
    APPROVED_FOR_PREVIEW_RENDER = "approved_for_preview_render"
    APPROVED_FOR_FINAL_RENDER = "approved_for_final_render"


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/production/resource_gate.py
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/production/resource_gate.py
@@ -23,6 +23,7 @@
     READY_FOR_REVIEW = "ready_for_review"
     APPROVED_FOR_ASSET_PLANNING = "approved_for_asset_planning"
     APPROVED_FOR_STYLEFRAME = "approved_for_styleframe"
+    APPROVED_FOR_PREVIEW_RENDER = "approved_for_preview_render"
     APPROVED_FOR_FINAL_RENDER = "approved_for_final_render"
     RENDERED_FOR_REVIEW = "rendered_for_review"
     APPROVED_FOR_MANUAL_POSTING = "approved_for_manual_posting"

/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/production/resource_gate.py was modified by sibling subagent '20260525_081301_675d674d' but this agent never read it. Read the file before writing to avoid overwriting the sibling's changes.


            RenderApprovalState.APPROVED_FOR_STYLEFRAME,
            RenderApprovalState.APPROVED_FOR_PREVIEW_RENDER,
            RenderApprovalState.APPROVED_FOR_FINAL_RENDER,

            RenderApprovalState.APPROVED_FOR_STYLEFRAME,
            RenderApprovalState.APPROVED_FOR_FINAL_RENDER,


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/production/resource_gate.py
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/production/resource_gate.py
@@ -99,6 +99,7 @@
             RenderApprovalState.READY_FOR_REVIEW,
             RenderApprovalState.APPROVED_FOR_ASSET_PLANNING,
             RenderApprovalState.APPROVED_FOR_STYLEFRAME,
+            RenderApprovalState.APPROVED_FOR_PREVIEW_RENDER,
             RenderApprovalState.APPROVED_FOR_FINAL_RENDER,
             RenderApprovalState.RENDERED_FOR_REVIEW,
         }:


from autoshorts.production.resource_gate import RenderApprovalState, ResourceCostTier, evaluate_media_generation_gate
from autoshorts.strategy.spine_v2 import build_demo_candidates_v2, build_review_package_v2

def test_preview_render_approval_allows_deterministic_preview_but_not_wan_or_styleframe():
    candidate = build_demo_candidates_v2(limit=1)[0]
    package = build_review_package_v2(candidate)

    preview = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=package,
        requested_resource_tier=ResourceCostTier.DETERMINISTIC_PREVIEW,
        approval_state=RenderApprovalState.APPROVED_FOR_PREVIEW_RENDER,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=package.script_hash,
    )
    assert preview.allowed

    wan = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=package,
        requested_resource_tier=ResourceCostTier.WAN_FINAL_RENDER,
        approval_state=RenderApprovalState.APPROVED_FOR_PREVIEW_RENDER,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=package.script_hash,
    )
    assert not wan.allowed

    styleframe = evaluate_media_generation_gate(
        candidate=candidate,
        review_package=package,
        requested_resource_tier=ResourceCostTier.AI_STYLEFRAME,
        approval_state=RenderApprovalState.APPROVED_FOR_PREVIEW_RENDER,
        approved_candidate_id=candidate.candidate_id,
        approved_version=candidate.version,
        approved_script_hash=package.script_hash,
    )
    assert not styleframe.allowed


terminal

python -m pytest tests/test_preview_render_gate_v2.py tests/test_pilot_production_v2.py -q


Output:
........                                                                 [100%]
8 passed in 0.08s


Explanation:
**Planning video creation**

I need to create a video, and I’m thinking about the dependencies I’ll require. It looks like I’ll be using tools like terminal, edge-tts for text-to-speech, ffmpeg for video processing, Python for scripting, and PIL for image handling. There’s a lot to manage here, so I'll make sure everything is set up correctly before I dive into the actual creation. I want to make sure I have everything lined up for success!

command -v edge-tts || true; command -v ffmpeg || true; python - <<'PY'
try:
 import PIL; print('PIL ok')
except Exception as e: print('PIL missing',e)
try:
 import moviepy; print('moviepy ok')
except Exception as e: print('moviepy missing',e)
PY


Output:
/home/agent/.local/bin/edge-tts
/usr/bin/ffmpeg
PIL ok
moviepy missing No module named 'moviepy'


Explanation:
**Exploring audio and video generation**

I need to generate audio with an edge, then consider if I should use Python to create frames, possibly using PIL and ffmpeg for image frames with drawings. Alternatively, I could create an animated video using ffmpeg with color and draw text. Captions are needed, so I might explore creating ASS subtitles and burning those in. It could be easier to create a background video from those frames using Python and OpenCV. I'll check cv2 for that.

python - <<'PY'
try:
 import cv2; print('cv2 ok')
except Exception as e: print('cv2 missing',e)
PY


Output: cv2 ok


Explanation:
**Generating audio and subtitles**

I need to use the edge voice, possibly en-GB-Ryan, to generate an MP3. First, I should determine the duration of the audio using ffprobe. Then, I'll generate a word cue based on the audio duration and word count. I wonder if better edge-tts can also output subtitles? It seems I can use the command edge-tts --write-subtitles, which might work with word boundaries. Let me check that out!


Output:
usage: edge-tts [-h] [-t TEXT] [-f FILE] [-v VOICE] [-l] [--rate RATE]
                [--volume VOLUME] [--pitch PITCH] [--write-media WRITE_MEDIA]
                [--write-subtitles WRITE_SUBTITLES] [--proxy PROXY]
                [--version]

Text-to-speech using Microsoft Edge's online TTS service.

options:
  -h, --help            show this help message and exit
  -t TEXT, --text TEXT  what TTS will say
  -f FILE, --file FILE  same as --text but read from file
  -v VOICE, --voice VOICE
                        voice for TTS. Default: en-US-EmmaMultilingualNeural
  -l, --list-voices     lists available voices and exits
  --rate RATE           set TTS rate. Default +0%.
  --volume VOLUME       set TTS volume. Default +0%.
  --pitch PITCH         set TTS pitch. Default +0Hz.
  --write-media WRITE_MEDIA
                        send media output to file instead of stdout
  --write-subtitles WRITE_SUBTITLES
                        send subtitle output to provided file instead of
                        stderr
  --proxy PROXY         use a proxy for TTS and voice list.
  --version             show program's version number and exit


Explanation:
**Planning subtitle generation**

I'm considering using SRT for subtitles and need to generate a preview using a script. I think I'll need to write the script and utilize execute_code. Creating files under data/previews seems like a good plan. I'll decide between using write_file for the script content or executing it directly with Python. The flow would involve creating TTS first, then video frames. I’ll also need to manage packages and run terminal commands for audio, subtitles, and muxing the final output.
**Creating active subtitles**

I'm thinking of using simpler active words, with large yellow text at the bottom and a white contextual phrase above it. I’d like to create a separate subtitle file for each word using ASS overrides for better timing. Time-based alignment seems more effective than forced alignment, so we can distribute words based on segment durations from the voiceover. I’ll need to get actual word timings and might consider using an edge subtitle file that’s sentence-based. I'll inspect the subtitles for this.

from hermes_tools import terminal
from pathlib import Path
wd='/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot'
out=Path('/tmp/autoshorts_preview_v2/ai-output-autopsy-decision-summary-38s')
out.mkdir(parents=True, exist_ok=True)
text=("Your AI summary is useless because it has no decision target. "
"Look at the bad output: it sounds polished, but it missed the one thing the viewer needed. "
"The summary says great discussion, but misses the launch decision. "
"Without a decision target, AI compresses every sentence equally. "
"Ask for blockers, owners, risks, and next steps tied to the decision. "
"Before: generic summary. After: decision evidence with owner and risk. "
"Don't ask for a summary. Ask for decision evidence.")
(out/'voiceover.txt').write_text(text)
cmd=f"edge-tts --voice en-GB-RyanNeural --rate -3% --text {terminal('python - <<\'PY\'\nimport shlex, pathlib\nprint(shlex.quote(pathlib.Path(\'/tmp/autoshorts_preview_v2/ai-output-autopsy-decision-summary-38s/voiceover.txt\').read_text()))\nPY')['output'].strip()} --write-media {out/'voiceover.mp3'} --write-subtitles {out/'edge_subtitles.srt'}"
res=terminal(cmd, timeout=180, workdir=wd)
print(res['output'])
print('exit',res['exit_code'])
print('files', list(p.name for p in out.iterdir()))


Output:
--- stderr ---
  File "/tmp/hermes_sandbox_ipivk6hj/script.py", line 14
    cmd=f"edge-tts --voice en-GB-RyanNeural --rate -3% --text {terminal('python - <<\'PY\'\nimport shlex, pathlib\nprint(shlex.quote(pathlib.Path(\'/tmp/autoshorts_preview_v2/ai-output-autopsy-decision-summary-38s/voiceover.txt\').read_text()))\nPY')['output'].strip()} --write-media {out/'voiceover.mp3'} --write-subtitles {out/'edge_subtitles.srt'}"
                                                                                ^
SyntaxError: f-string expression part cannot include a backslash

  File "/tmp/hermes_sandbox_ipivk6hj/script.py", line 14
    cmd=f"edge-tts --voice en-GB-RyanNeural --rate -3% --text {terminal('python - <<\'PY\'\nimport shlex, pathlib\nprint(shlex.quote(pathlib.Path(\'/tmp/autoshorts_preview_v2/ai-output-autopsy-decision-summary-38s/voiceover.txt\').read_text()))\nPY')['output'].strip()} --write-media {out/'voiceover.mp3'} --write-subtitles {out/'edge_subtitles.srt'}"
                                                                                ^
SyntaxError: f-string expression part cannot include a backslash


from hermes_tools import terminal, shell_quote
from pathlib import Path
wd='/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot'
out=Path('/tmp/autoshorts_preview_v2/ai-output-autopsy-decision-summary-38s')
out.mkdir(parents=True, exist_ok=True)
text=("Your AI summary is useless because it has no decision target. "
"Look at the bad output: it sounds polished, but it missed the one thing the viewer needed. "
"The summary says great discussion, but misses the launch decision. "
"Without a decision target, AI compresses every sentence equally. "
"Ask for blockers, owners, risks, and next steps tied to the decision. "
"Before: generic summary. After: decision evidence with owner and risk. "
"Don't ask for a summary. Ask for decision evidence.")
text_path=out/'voiceover.txt'
text_path.write_text(text)
cmd=f"edge-tts --voice en-GB-RyanNeural --rate -3% --file {shell_quote(str(text_path))} --write-media {shell_quote(str(out/'voiceover.mp3'))} --write-subtitles {shell_quote(str(out/'edge_subtitles.srt'))}"
res=terminal(cmd, timeout=180, workdir=wd)
print(res['output'])
print('exit',res['exit_code'])
print('files', sorted(p.name for p in out.iterdir()))


Output:
usage: edge-tts [-h] [-t TEXT] [-f FILE] [-v VOICE] [-l] [--rate RATE]
                [--volume VOLUME] [--pitch PITCH] [--write-media WRITE_MEDIA]
                [--write-subtitles WRITE_SUBTITLES] [--proxy PROXY]
                [--version]
edge-tts: error: argument --rate: expected one argument
exit 2
files ['voiceover.txt']


from hermes_tools import terminal, shell_quote
from pathlib import Path
wd='/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot'
out=Path('/tmp/autoshorts_preview_v2/ai-output-autopsy-decision-summary-38s')
cmd=f"edge-tts --voice en-GB-RyanNeural --rate=-3% --file {shell_quote(str(out/'voiceover.txt'))} --write-media {shell_quote(str(out/'voiceover.mp3'))} --write-subtitles {shell_quote(str(out/'edge_subtitles.srt'))}"
res=terminal(cmd, timeout=180, workdir=wd)
print(res['output'])
print('exit',res['exit_code'])
print('files', sorted(p.name for p in out.iterdir()))


Output:
exit 0
files ['edge_subtitles.srt', 'voiceover.mp3', 'voiceover.txt']


from hermes_tools import terminal
out='/tmp/autoshorts_preview_v2/ai-output-autopsy-decision-summary-38s'
print(terminal(f"ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {out}/voiceover.mp3")['output'])
print(open(f'{out}/edge_subtitles.srt').read()[:1000])


Output:
34.968000
1
00:00:00,100 --> 00:00:04,468
Your AI summary is useless because it has no decision target.

2
00:00:04,418 --> 00:00:10,024
Look at the bad output: it sounds polished, but it missed the one thing the viewer needed.

3
00:00:10,024 --> 00:00:14,431
The summary says great discussion, but misses the launch decision.

4
00:00:14,431 --> 00:00:19,109
Without a decision target, AI compresses every sentence equally.

5
00:00:19,109 --> 00:00:24,276
Ask for blockers, owners, risks, and next steps tied to the decision.

6
00:00:24,276 --> 00:00:26,931
Before: generic summary.

7
00:00:26,931 --> 00:00:30,397
After: decision evidence with owner and risk.

8
00:00:30,397 --> 00:00:32,562
Don't ask for a summary.

9
00:00:32,562 --> 00:00:34,959
Ask for decision evidence.