    if pillar == "attention_design":
        return "This is not a willpower lecture. It is attention design: change the default before the feed chooses for you."
    return "This is not another motivation trick. It is a small environmental system you can test today."


patch

--- a//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/script_drafts.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/script_drafts.py
@@ -86,6 +86,8 @@
         return "A quiet, honest moment for people who feel spiritually tired but still want to return to God."
     if pillar == "ai_life_systems":
         return "The point is not hype. The point is a simple system where the human stays in charge."
+    if pillar == "attention_design":
+        return "This is not a willpower lecture. It is attention design: change the default before the feed chooses for you."
     return "This is not another motivation trick. It is a small environmental system you can test today."


    return f"""## AutoShortsBot Review

**ID:** `{draft['id']}`
**Pillar:** `{draft['pillar']}`
**Title:** {draft['title']}
**Duration:** {draft['duration_seconds']}s — {draft['platform_note']}
**Platforms:** {platforms}
**Quality Score:** {draft['quality_score']}/10
**Retention Score:** {draft['retention_score']}/10
**Retention Signals:** {', '.join(draft['retention_signals']) or 'none'}
**Retention Risks:** {', '.join(draft['retention_risks']) or 'none'}


--- a//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/script_drafts.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/script_drafts.py
@@ -107,6 +107,9 @@
 **Duration:** {draft['duration_seconds']}s — {draft['platform_note']}
 **Platforms:** {platforms}
 **Quality Score:** {draft['quality_score']}/10
+**Retention Score:** {draft['retention_score']}/10
+**Retention Signals:** {', '.join(draft['retention_signals']) or 'none'}
+**Retention Risks:** {', '.join(draft['retention_risks']) or 'none'}

 **Hook:** {draft['hook']}


    assert "Retention Score" in text
    assert "Quality Score" in text


--- a//home/agent/jarvis_runtime/AutoShortsBot/tests/test_script_drafts.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/tests/test_script_drafts.py
@@ -38,6 +38,7 @@
     assert "## AutoShortsBot Review" in text
     assert "Freigabe alle" in text
     assert draft["hook"] in text
+    assert "Retention Score" in text
     assert "Quality Score" in text


terminal

python3 -m pytest -q
python3 scripts/generate_review_drafts.py --batch data/drafts/testbatch_002.json --out data/reviews/testbatch_002
python3 - <<'PY'
from pathlib import Path
files=sorted(Path('data/reviews/testbatch_002').glob('*.md'))
print('review_files', len(files))
print(files[0].read_text(encoding='utf-8')[:1300])
PY


Output:
............                                                             [100%]
12 passed in 0.06s
Generated 9 review drafts in data/reviews/testbatch_002
Validation: {'name': 'testbatch_002_retention_probe_no_faith', 'total_ideas': 9, 'pillars': {'ai_life_systems': 3, 'attention_design': 3, 'life_optimization': 3}, 'duration_buckets': {'growth': 6, 'tiktok_rewards_candidate': 3}, 'missing_fields': [], 'minimum_quality_score': 9}
Retention: {'life-004': {'score': 10, 'signals': ['comment_trigger', 'concrete_payoff', 'direct_address', 'growth_duration', 'open_loop', 'short_hook', 'specificity', 'tension', 'visual_energy'], 'risks': []}, 'life-005': {'score': 10, 'signals': ['concrete_payoff', 'direct_address', 'growth_duration', 'open_loop', 'short_hook', 'tension', 'visual_energy'], 'risks': []}, 'life-006': {'score': 10, 'signals': ['concrete_payoff', 'direct_address', 'growth_duration', 'open_loop', 'short_hook', 'tension', 'visual_energy'], 'risks': []}, 'ai-004': {'score': 10, 'signals': ['comment_trigger', 'concrete_payoff', 'direct_address', 'growth_duration', 'short_hook', 'specificity', 'tension', 'visual_energy'], 'risks': []}, 'ai-005': {'score': 10, 'signals': ['comment_trigger', 'concrete_payoff', 'direct_address', 'monetization_duration', 'short_hook', 'specificity', 'visual_energy'], 'risks': []}, 'ai-006': {'score': 9, 'signals': ['concrete_payoff', 'monetization_duration', 'open_loop', 'specificity', 'tension'], 'risks': []}, 'attention-001': {'score': 8, 'signals': ['direct_address', 'growth_duration', 'open_loop', 'visual_energy'], 'risks': []}, 'attention-002': {'score': 10, 'signals': ['concrete_payoff', 'direct_address', 'monetization_duration', 'open_loop', 'short_hook', 'tension', 'visual_energy'], 'risks': []}, 'attention-003': {'score': 9, 'signals': ['comment_trigger', 'direct_address', 'growth_duration', 'open_loop', 'short_hook'], 'risks': []}}
review_files 9
## AutoShortsBot Review

**ID:** `ai-004`
**Pillar:** `ai_life_systems`
**Title:** Stop asking AI to think for you
**Duration:** 41s — Growth Short
**Platforms:** youtube_shorts, tiktok, instagram_reels
**Quality Score:** 10/10
**Retention Score:** 10/10
**Retention Signals:** comment_trigger, concrete_payoff, direct_address, growth_duration, short_hook, specificity, tension, visual_energy
**Retention Risks:** none

**Hook:** Stop asking AI to think for you. Ask it to argue with you.

**Script Draft:**
- **Hook** (3s): Stop asking AI to think for you. Ask it to argue with you.
- **Setup** (9s): The point is not hype. The point is a simple system where the human stays in charge.
- **Core Value** (26s): Use AI as a challenger: list assumptions, find weak spots, suggest alternatives, then you decide. Better thinking, not outsourced thinking.
- **CTA** (3s): Comment CRITIC if you want the prompt.

**Visual Concept:** Screen recording with prompt cards: assumptions, weak spots, alternatives, decision; human checkmark at end.

**Caption:** AI is more useful as a critic than as a guru.
**Hashtags:** #ai #productivity #prompts #lifesystems

**Policy/Risk Notes:** Keep human decision authority explicit.

**Approval commands:**
- `Freigabe alle`
- `Freigabe YouTube`
- `Freigabe TikTok`
- `


todo

Retention-Scoring/Validation implementieren und Review-Drafts neu generieren

Tests ausführen, committen und pushen


Recherche-Erkenntnisse zu Retention/Monetarisierung in Projektregeln übersetzen

FaithTok pausieren und Testbatch v2 ohne religiöse Themen modellieren

Tests ausführen, committen und pushen


terminal

Output:
M autoshorts/__init__.py
 M autoshorts/growth_plan.py
 M autoshorts/script_drafts.py
 M configs/niches/faithtok.json
 M scripts/generate_review_drafts.py
 M tests/test_script_drafts.py
?? autoshorts/retention.py
?? configs/niches/attention_design.json
?? data/drafts/testbatch_002.json
?? data/reviews/testbatch_002/
?? docs/retention_research.md
?? tests/test_retention.py
?? tests/test_testbatch_002.py
 autoshorts/__init__.py            |  2 +-
 autoshorts/growth_plan.py         |  3 +--
 autoshorts/script_drafts.py       | 12 ++++++++++++
 configs/niches/faithtok.json      | 27 +++------------------------
 scripts/generate_review_drafts.py |  3 +++
 tests/test_script_drafts.py       |  1 +
 6 files changed, 21 insertions(+), 27 deletions(-)


set -euo pipefail
python3 -m pytest -q
git add autoshorts configs data docs scripts tests
git commit -m "feat: add retention-led no-faith testbatch"


Output:
............                                                             [100%]
12 passed in 0.04s
[feat/initial-growth-engine 5799d16] feat: add retention-led no-faith testbatch
 21 files changed, 728 insertions(+), 27 deletions(-)
 create mode 100644 autoshorts/retention.py
 create mode 100644 configs/niches/attention_design.json
 create mode 100644 data/drafts/testbatch_002.json
 create mode 100644 data/reviews/testbatch_002/ai-004-stop-asking-ai-to-think-for-you.md
 create mode 100644 data/reviews/testbatch_002/ai-005-the-4-box-prompt-for-mental-overload.md
 create mode 100644 data/reviews/testbatch_002/ai-006-a-real-ai-assistant-needs-an-approval-gate.md
 create mode 100644 data/reviews/testbatch_002/attention-001-your-phone-wins-before-breakfast.md
 create mode 100644 data/reviews/testbatch_002/attention-002-the-algorithm-does-not-need-you-happy.md
 create mode 100644 data/reviews/testbatch_002/attention-003-the-5-second-rule-for-opening-any-app.md
 create mode 100644 data/reviews/testbatch_002/life-004-you-do-not-need-more-discipline.md
 create mode 100644 data/reviews/testbatch_002/life-005-your-to-do-list-is-an-anxiety-museum.md
 create mode 100644 data/reviews/testbatch_002/life-006-the-calendar-exposes-the-lie.md
 create mode 100644 docs/retention_research.md
 create mode 100644 tests/test_retention.py
 create mode 100644 tests/test_testbatch_002.py


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


Output:
branch=feat/initial-growth-engine
local=5799d16982686932f1e2c78b0524f4c8cad1c1e5
remote=5799d16982686932f1e2c78b0524f4c8cad1c1e5
To https://github.com/Gamexgit/AutoShortsBot.git
   2100bfd..5799d16  feat/initial-growth-engine -> feat/initial-growth-engine


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


Output:
## feat/initial-growth-engine...origin/feat/initial-growth-engine
............                                                             [100%]
12 passed in 0.05s


todo

Tests ausführen, committen und pushen


Erledigt, Sir.

Ich habe die religiösen Themen **vorerst sauber pausiert** und den nächsten Entwicklungsschritt umgesetzt: weg von „Themen sammeln“, hin zu **Retention-led Content Engineering**.

## Recherche-Kernaussagen

Ich habe offizielle/nahe Quellen geprüft, u.a.:

- TikTok Creator Academy / Creator Rewards
- TikTok Creator Rewards Terms
- YouTube Help / YPP / Performance & Discovery FAQs

## Was Menschen eher hängen lässt

Die wichtigen Muster für Shorts sind ziemlich klar:

- **Immediate self-relevance**
  Der Zuschauer muss in Sekunde 0–2 denken: „Das bin ich.“

- **Open loop**
  Nicht: „Hier sind Tipps.“
  Sondern: „Wenn X passiert, mach Y bevor Z.“

- **Specificity**
  Zahlen, konkrete Situationen, bekannte Tools/Plattformen.
  Beispiel: „47 Tabs im Kopf“ schlägt „mental overload“.

- **Tension / Contrarian Angle**
  „Du brauchst nicht mehr Disziplin. Du brauchst einen kleineren ersten Schritt.“

- **Concrete payoff**
  Template, Prompt, 2-Minuten-System, Vorher/Nachher.

- **Visual pattern interrupt**
  Kein statisches KI-Bild. Bewegung, Freeze-frame, Before/After, Screen Recording, Timer, große Captions.

- **Conversation trigger**
  Nicht nur „Follow for more“, sondern konkrete Frage oder Kommentar-Keyword.

- **Niche authority**
  Nicht generische Lebensberatung. Eine wiedererkennbare Haltung.

TikTok sagt zudem explizit: Für Creator Rewards braucht es hochwertige, originale, kreative Inhalte über 1 Minute, Nischenexpertise, sinnvolle Gespräche, 1080p+ und keine reinen Slideshows/copy-paste/low-quality reactions.

Kurz: Die Plattformen mögen keinen KI-Slideshow-Salat. Skandalös, aber nachvollziehbar.

---

## Projektänderungen im Repo

Gepusht auf:

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

Neuer Commit:

```text
5799d16 feat: add retention-led no-faith testbatch
```

Remote-Verifikation erfolgreich:

```text
local == remote
```

Tests:

```text
12 passed in 0.05s
```

PR-Link bleibt:

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

---

## Was neu ist

### 1. FaithTok pausiert

Datei geändert:

```text
configs/niches/faithtok.json
```

Status jetzt:

```json
{
  "status": "paused",
  "reason": "Religious topics are intentionally paused because mixing Christian/FaithTok messaging with AI positioning is sensitive and could confuse the channel brand."
}
```

Auch in der JARVIS-Projektmemory aktualisiert.

---

### 2. Neue Säule: Attention Design

Neue Datei:

```text
configs/niches/attention_design.json
```

Positionierung:

> Praktisches digitales Attention Design: nicht moralisch predigen, sondern Defaults, Apps, Feeds und Benachrichtigungen durch kleine Systeme entschärfen.

Das passt sehr gut zu Life Optimization und AI Life Systems, ohne religiös heikel zu werden.

---

### 3. Retention Research dokumentiert

Neue Datei:

```text
docs/retention_research.md
```

Enthält:

- Quellen
- TikTok/YouTube-Anforderungen
- Retention-Hypothesen
- operative Regeln für AutoShortsBot

---

### 4. Retention Scoring Engine

Neue Datei:

```text
autoshorts/retention.py
```

Der Bot bewertet jetzt Ideen heuristisch nach:

- direct address
- open loop
- specificity
- tension
- short hook
- growth duration
- monetization duration
- visual energy
- concrete payoff
- comment trigger

Und erkennt Risiken wie:

- slow intro
- generic value
- static visuals
- too long for unproven format

Beispiel: „In this video I will talk about productivity tips“ wird abgestraft. Sehr verdient.

---

### 5. Testbatch v2 ohne Religion

Neue Datei:

```text
data/drafts/testbatch_002.json
```

Enthält 9 neue Ideen:

- 3 × Life Optimization
- 3 × AI Life Systems
- 3 × Attention Design

Davon:

- 6 Growth Shorts
- 3 TikTok-Rewards-Kandidaten mit 60+ Sekunden

---

## Neuer Testbatch v2

### Life Optimization

1. **You do not need more discipline**
   - Hook: “You do not need more discipline. You need a smaller first step.”
   - Dauer: 38s
   - Retention Score: 10/10

2. **Your to-do list is an anxiety museum**
   - Hook: “Your to-do list might not be a plan. It might be an anxiety museum.”
   - Dauer: 42s
   - Retention Score: 10/10

3. **The calendar exposes the lie**
   - Hook: “Your values are not in your notes app. They are in your calendar.”
   - Dauer: 33s
   - Retention Score: 10/10

---

### AI Life Systems

4. **Stop asking AI to think for you**
   - Hook: “Stop asking AI to think for you. Ask it to argue with you.”
   - Dauer: 41s
   - Retention Score: 10/10

5. **The 4-box prompt for mental overload**
   - Hook: “If your brain has 47 tabs open, paste the mess into this 4-box prompt.”
   - Dauer: 61s
   - TikTok-Rewards-Kandidat
   - Retention Score: 10/10

6. **A real AI assistant needs an approval gate**
   - Hook: “The most important part of an AI assistant is not the model. It is the approval gate.”
   - Dauer: 75s
   - TikTok-Rewards-Kandidat
   - Retention Score: 9/10

---

### Attention Design

7. **Your phone wins before breakfast**
   - Hook: “If you touch your phone before your feet touch the floor, your day already has an owner.”
   - Dauer: 36s
   - Retention Score: 8/10

8. **The algorithm does not need you happy**
   - Hook: “The algorithm does not need you happy. It needs you to stay.”
   - Dauer: 67s
   - TikTok-Rewards-Kandidat
   - Retention Score: 10/10

9. **The 5-second rule for opening any app**
   - Hook: “Before you open any app, give it a job description.”
   - Dauer: 31s
   - Retention Score: 9/10

---

## Review-Drafts generiert

Neue Review-Dateien:

```text
data/reviews/testbatch_002/
```

Jede Idee hat jetzt einen Telegram-tauglichen Review-Draft mit:

- Title
- Duration
- Platform targets
- Quality Score
- Retention Score
- Retention Signals
- Retention Risks
- Hook
- Script Draft
- Visual Concept
- Caption
- Hashtags
- Approval Commands

Beispiel:

```text
ID: ai-004
Title: Stop asking AI to think for you
Duration: 41s — Growth Short
Quality Score: 10/10
Retention Score: 10/10

Hook:
Stop asking AI to think for you. Ask it to argue with you.

Script Draft:
- Hook (3s): Stop asking AI to think for you. Ask it to argue with you.
- Setup (9s): The point is not hype. The point is a simple system where the human stays in charge.
- Core Value (26s): Use AI as a challenger: list assumptions, find weak spots, suggest alternatives, then you decide. Better thinking, not outsourced thinking.
- CTA (3s): Comment CRITIC if you want the prompt.
```

---

## Meine Empfehlung für den nächsten Schritt

Jetzt sollten wir **nicht sofort rendern**.

Der nächste saubere Schritt ist:

## Voiceover-Finalizer v1

Aus den strukturellen Drafts sollen echte, natürlich gesprochene Skripte werden:

- eine klare Sprache pro Clip
- vermutlich zunächst **Englisch**, weil die Hooks global stärker wirken und KI/Productivity dort besser skaliert
- 120–160 Wörter für 60–75s
- 70–100 Wörter für 30–45s
- keine generischen Übergänge
- Hook in den ersten 1–2 Sekunden
- Captions so schreiben, dass sie visuell funktionieren

Danach nehmen wir die besten **3 Videos** aus Batch v2:

Meine Empfehlung:

1. `ai-004` — **Stop asking AI to think for you**
2. `ai-005` — **The 4-box prompt for mental overload**
3. `attention-002` — **The algorithm does not need you happy**

Warum diese drei?

- starke Hooks
- klare Zielgruppe
- hoher Kommentar-/Template-Trigger
- direkter Bezug zu KI + Life Systems
- keine religiöse Brand-Komplexität
- gute Mischung aus Growth und 1min+ Monetization Candidate

Wenn du willst, mache ich als nächsten Schritt direkt den **Voiceover-Finalizer v1** und generiere die finalen Scripts für diese 3 Kandidaten.


[M G] Ok mach das.


Tests für Voiceover-Finalizer schreiben

Voiceover-Finalizer implementieren

Finale Skripte für Top-3 generieren

Tests ausführen, committen und pushen


from pathlib import Path

from autoshorts.growth_plan import load_testbatch
from autoshorts.voiceover_finalizer import finalize_voiceover, write_final_scripts

def test_finalize_voiceover_keeps_hook_first_and_estimates_duration():
    batch = load_testbatch(Path("data/drafts/testbatch_002.json"))
    idea = next(item for item in batch["ideas"] if item["id"] == "ai-004")

    final = finalize_voiceover(idea)

    assert final["id"] == "ai-004"
    assert final["voiceover"].startswith(idea["hook"])
    assert final["estimated_seconds"] <= idea["duration_seconds"] + 4
    assert final["estimated_seconds"] >= idea["duration_seconds"] - 12
    assert final["word_count"] > 70
    assert final["caption_beats"][0]["start"] == 0
    assert final["production_notes"]

def test_finalize_voiceover_uses_longer_script_for_rewards_candidate():
    batch = load_testbatch(Path("data/drafts/testbatch_002.json"))
    idea = next(item for item in batch["ideas"] if item["id"] == "ai-005")

    final = finalize_voiceover(idea)

    assert final["platform_note"] == "TikTok Rewards Candidate"
    assert final["word_count"] >= 120
    assert final["estimated_seconds"] >= 55
    assert "comment" in final["voiceover"].lower()

def test_write_final_scripts_only_writes_selected_ids(tmp_path):
    batch = load_testbatch(Path("data/drafts/testbatch_002.json"))

    paths = write_final_scripts(batch, tmp_path, selected_ids=["ai-004", "ai-005", "attention-002"])

    assert len(paths) == 3
    names = {path.name for path in paths}
    assert any(name.startswith("ai-004") for name in names)
    assert all(path.suffix == ".md" for path in paths)
    assert "Voiceover" in paths[0].read_text(encoding="utf-8")


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


terminal

python3 -m pytest tests/test_voiceover_finalizer.py -q


Output:
==================================== ERRORS ====================================
______________ ERROR collecting tests/test_voiceover_finalizer.py ______________
ImportError while importing test module '/home/agent/jarvis_runtime/AutoShortsBot/tests/test_voiceover_finalizer.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_voiceover_finalizer.py:4: in <module>
    from autoshorts.voiceover_finalizer import finalize_voiceover, write_final_scripts
E   ModuleNotFoundError: No module named 'autoshorts.voiceover_finalizer'
=========================== short test summary info ============================
ERROR tests/test_voiceover_finalizer.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.10s


todo

Tests für Voiceover-Finalizer schreiben

Voiceover-Finalizer implementieren


"""Deterministic voiceover finalizer for curated AutoShortsBot ideas."""

from __future__ import annotations

import re
from pathlib import Path
from typing import Any

from autoshorts.script_drafts import _slug

TOP3_CUSTOM_VOICEOVERS = {
    "ai-004": """Stop asking AI to think for you. Ask it to argue with you. Most people use AI like a vending machine: type a question, get a confident answer, move on. That is the weakest use case. The better move is this: paste your idea and ask, what assumptions am I making? What is the weakest part? What would a smart critic disagree with? What are three alternatives I have not considered? Now AI is not replacing your thinking. It is putting resistance against lazy thinking. You still decide. You still own the consequence. But you arrive sharper, because the first version of your idea had to survive a fight. Comment CRITIC if you want the exact prompt.""",
    "ai-005": """If your brain has 47 tabs open, paste the mess into this four-box prompt. Do not ask AI to motivate you. Ask it to sort the noise. First, dump everything: tasks, worries, random reminders, half-decisions, all of it. Then ask AI: split this into four boxes. Box one: tasks I can actually do. Box two: worries I cannot solve right now. Box three: decisions I need to make. Box four: later, not today. Then ask one final question: what is the next action that takes less than two minutes? That is the whole trick. You are not looking for a perfect life plan. You are looking for one clean handle on the chaos. When the output appears, do only the two-minute action first. Momentum beats clarity. Comment RESET if you want me to turn this into a copy-paste template.""",
    "attention-002": """The algorithm does not need you happy. It needs you to stay. That does not mean every app is evil. It means the incentives are not the same as yours. You want focus, sleep, actual progress, maybe a quiet brain for once. The feed wants one more swipe. So stop fighting a machine with vibes. Change the design. First, turn your phone to greyscale when you need deep work. Second, remove your worst app from the home screen. Do not delete it if that feels dramatic. Just make it less automatic. Third, before you open a feed, set a leaving cue: one search, three videos, or five minutes. If there is no exit rule, the app owns the exit. This is not about becoming a monk. It is about making your phone slightly less talented at stealing your day. Which app gets moved off your home screen today?""",
}

def _word_count(text: str) -> int:
    return len(re.findall(r"\b[\w'-]+\b", text))

def _estimate_seconds(word_count: int, words_per_minute: int = 145) -> int:
    return round(word_count / words_per_minute * 60)

def _generic_voiceover(idea: dict[str, Any]) -> str:
    """Fallback finalizer for non-custom ideas."""
    return (
        f"{idea['hook']} "
        f"Here is the useful part. {idea['script_outline']} "
        f"Do not turn this into another idea you save and forget. Try the smallest version today. "
        f"{idea['cta']}"
    )

def _caption_beats(voiceover: str, duration: int) -> list[dict[str, Any]]:
    sentences = [part.strip() for part in re.split(r"(?<=[.!?])\s+", voiceover) if part.strip()]
    if not sentences:
        return []
    step = max(2, round(duration / len(sentences)))
    beats: list[dict[str, Any]] = []
    current = 0
    for sentence in sentences:
        beats.append({"start": current, "text": sentence})
        current += step
    return beats

def _production_notes(idea: dict[str, Any], estimated_seconds: int) -> list[str]:
    notes = [
        "First frame must show the hook as large kinetic caption text.",
        "Use motion or a visual state change in the first 1-2 seconds.",
        "Avoid static AI slideshow visuals; use screen recordings, cards, timer, or before/after cuts.",
    ]
    if estimated_seconds >= 60:
        notes.append("Keep this version above 60 seconds for TikTok Creator Rewards testing.")
    if idea["pillar"] == "ai_life_systems":
        notes.append("Show the human approval/decision moment; do not imply AI has final authority.")
    if idea["pillar"] == "attention_design":
        notes.append("Avoid moral panic; frame as attention design and defaults.")
    return notes

def finalize_voiceover(idea: dict[str, Any]) -> dict[str, Any]:
    """Create a final spoken script and production metadata for one idea."""
    voiceover = TOP3_CUSTOM_VOICEOVERS.get(idea["id"], _generic_voiceover(idea)).strip()
    word_count = _word_count(voiceover)
    estimated_seconds = _estimate_seconds(word_count)
    platform_note = "TikTok Rewards Candidate" if int(idea["duration_seconds"]) >= 60 else "Growth Short"

    return {
        "id": idea["id"],
        "pillar": idea["pillar"],
        "title": idea["title"],
        "target_seconds": int(idea["duration_seconds"]),
        "estimated_seconds": estimated_seconds,
        "platform_note": platform_note,
        "word_count": word_count,
        "voiceover": voiceover,
        "caption_beats": _caption_beats(voiceover, estimated_seconds),
        "visual_concept": idea["visual_concept"],
        "caption": idea["caption"],
        "cta": idea["cta"],
        "production_notes": _production_notes(idea, estimated_seconds),
    }

def format_final_script(final: dict[str, Any]) -> str:
    """Format final voiceover package as Markdown."""
    beats = "\n".join(f"- {beat['start']:02d}s: {beat['text']}" for beat in final["caption_beats"])
    notes = "\n".join(f"- {note}" for note in final["production_notes"])
    return f"""# {final['title']}

- ID: `{final['id']}`
- Pillar: `{final['pillar']}`
- Target duration: {final['target_seconds']}s
- Estimated voiceover duration: {final['estimated_seconds']}s
- Word count: {final['word_count']}
- Platform note: {final['platform_note']}

## Voiceover

{final['voiceover']}

## Caption beats

{beats}

## Visual concept

{final['visual_concept']}

## Caption

{final['caption']}

## CTA

{final['cta']}

## Production notes

{notes}
"""

def write_final_scripts(
    batch: dict[str, Any], output_dir: Path, selected_ids: list[str] | None = None
) -> list[Path]:
    """Write final voiceover scripts for selected ideas."""
    selected = set(selected_ids or [])
    output_dir.mkdir(parents=True, exist_ok=True)
    paths: list[Path] = []
    for idea in batch.get("ideas", []):
        if selected and idea["id"] not in selected:
            continue
        final = finalize_voiceover(idea)
        path = output_dir / f"{final['id']}-{_slug(final['title'])}.md"
        path.write_text(format_final_script(final), encoding="utf-8")
        paths.append(path)
    return paths


patch

__all__ = ["growth_plan", "retention", "script_drafts", "voiceover_finalizer"]

__all__ = ["growth_plan", "retention", "script_drafts"]


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

-__all__ = ["growth_plan", "retention", "script_drafts"]
+__all__ = ["growth_plan", "retention", "script_drafts", "voiceover_finalizer"]


terminal

{"output": "F..                                                                      [100%]\n=================================== FAILURES ===================================\n_______ test_finalize_voiceover_keeps_hook_first_and_estimates_duration ________\n\n    def test_finalize_voiceover_keeps_hook_first_and_estimates_duration():\n        batch = load_testbatch(Path(\"data/drafts/testbatch_002.json\"))\n        idea = next(item for item in batch[\"ideas\"] if item[\"id\"] == \"ai-004\")\n    \n        final = finalize_voiceover(idea)\n    \n        assert final[\"id\"] == \"ai-004\"\n        assert final[\"voiceover\"].startswith(idea[\"hook\"])\n>       assert final[\"estimated_seconds\"] <= idea[\"duration_seconds\"] + 4\nE       assert 48 <= (41 + 4)\n\ntests/test_voiceover_finalizer.py:15: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_voiceover_finalizer.py::test_finalize_voiceover_keeps_hook_first_and_estimates_duration\n1 failed, 2 passed in 0.05s", "exit_code": 1, "error": null}

[Tool loop warning: repeated_exact_failure_warning; count=2; terminal has failed 2 times with identical arguments. This looks like a loop; inspect the error and change strategy instead of retrying it unchanged.]


patch

Stop asking AI to think for you. Ask it to argue with you. Most people use AI like a vending machine: type a question, get a confident answer, move on. That is the weakest use case. The better move is this: paste your idea and ask, what assumptions am I making? What is the weakest part? What would a smart critic disagree with? What are three alternatives I have not considered? Now AI is not replacing your thinking. It is putting resistance against lazy thinking. You still decide. You still own the consequence. But you arrive sharper, because the first version of your idea had to survive a fight. Comment CRITIC if you want the exact prompt.

Stop asking AI to think for you. Ask it to argue with you. Most people use AI like a vending machine: type a question, get a confident answer, move on. That is the weakest use case. Try this instead: paste your idea and ask, what assumptions am I making? What is the weakest part? What would a smart critic disagree with? Now AI is not replacing your thinking. It is putting resistance against lazy thinking. You still decide. You still own the consequence. But you arrive sharper, because the first version had to survive a fight. Comment CRITIC if you want the exact prompt.


--- a//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/voiceover_finalizer.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/voiceover_finalizer.py
@@ -9,7 +9,7 @@
 from autoshorts.script_drafts import _slug

 TOP3_CUSTOM_VOICEOVERS = {
-    "ai-004": """Stop asking AI to think for you. Ask it to argue with you. Most people use AI like a vending machine: type a question, get a confident answer, move on. That is the weakest use case. The better move is this: paste your idea and ask, what assumptions am I making? What is the weakest part? What would a smart critic disagree with? What are three alternatives I have not considered? Now AI is not replacing your thinking. It is putting resistance against lazy thinking. You still decide. You still own the consequence. But you arrive sharper, because the first version of your idea had to survive a fight. Comment CRITIC if you want the exact prompt.""",
+    "ai-004": """Stop asking AI to think for you. Ask it to argue with you. Most people use AI like a vending machine: type a question, get a confident answer, move on. That is the weakest use case. Try this instead: paste your idea and ask, what assumptions am I making? What is the weakest part? What would a smart critic disagree with? Now AI is not replacing your thinking. It is putting resistance against lazy thinking. You still decide. You still own the consequence. But you arrive sharper, because the first version had to survive a fight. Comment CRITIC if you want the exact prompt.""",
     "ai-005": """If your brain has 47 tabs open, paste the mess into this four-box prompt. Do not ask AI to motivate you. Ask it to sort the noise. First, dump everything: tasks, worries, random reminders, half-decisions, all of it. Then ask AI: split this into four boxes. Box one: tasks I can actually do. Box two: worries I cannot solve right now. Box three: decisions I need to make. Box four: later, not today. Then ask one final question: what is the next action that takes less than two minutes? That is the whole trick. You are not looking for a perfect life plan. You are looking for one clean handle on the chaos. When the output appears, do only the two-minute action first. Momentum beats clarity. Comment RESET if you want me to turn this into a copy-paste template.""",
     "attention-002": """The algorithm does not need you happy. It needs you to stay. That does not mean every app is evil. It means the incentives are not the same as yours. You want focus, sleep, actual progress, maybe a quiet brain for once. The feed wants one more swipe. So stop fighting a machine with vibes. Change the design. First, turn your phone to greyscale when you need deep work. Second, remove your worst app from the home screen. Do not delete it if that feels dramatic. Just make it less automatic. Third, before you open a feed, set a leaving cue: one search, three videos, or five minutes. If there is no exit rule, the app owns the exit. This is not about becoming a monk. It is about making your phone slightly less talented at stealing your day. Which app gets moved off your home screen today?""",
 }


terminal

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


todo

Voiceover-Finalizer implementieren

Finale Skripte für Top-3 generieren


"""Generate final voiceover scripts for selected AutoShortsBot ideas."""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

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

from autoshorts.growth_plan import load_testbatch
from autoshorts.voiceover_finalizer import write_final_scripts

DEFAULT_SELECTED_IDS = ["ai-004", "ai-005", "attention-002"]

def main() -> None:
    parser = argparse.ArgumentParser(description="Generate final voiceover scripts.")
    parser.add_argument("--batch", default="data/drafts/testbatch_002.json")
    parser.add_argument("--out", default="data/final_scripts/testbatch_002_top3")
    parser.add_argument("--ids", nargs="*", default=DEFAULT_SELECTED_IDS)
    args = parser.parse_args()

    batch = load_testbatch(Path(args.batch))
    paths = write_final_scripts(batch, Path(args.out), selected_ids=args.ids)
    print(f"Generated {len(paths)} final scripts in {args.out}")
    for path in paths:
        print(path)

if __name__ == "__main__":
    main()


terminal

python3 scripts/generate_final_scripts.py --batch data/drafts/testbatch_002.json --out data/final_scripts/testbatch_002_top3
python3 - <<'PY'
from pathlib import Path
files=sorted(Path('data/final_scripts/testbatch_002_top3').glob('*.md'))
print('final_files', len(files))
for p in files:
    print('\n---', p.name, '---')
    text=p.read_text(encoding='utf-8')
    print('\n'.join(text.splitlines()[:18]))
PY


Output:
Generated 3 final scripts in data/final_scripts/testbatch_002_top3
data/final_scripts/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.md
data/final_scripts/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.md
data/final_scripts/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.md
final_files 3

--- ai-004-stop-asking-ai-to-think-for-you.md ---
# Stop asking AI to think for you

- ID: `ai-004`
- Pillar: `ai_life_systems`
- Target duration: 41s
- Estimated voiceover duration: 43s
- Word count: 104
- Platform note: Growth Short

## Voiceover

Stop asking AI to think for you. Ask it to argue with you. Most people use AI like a vending machine: type a question, get a confident answer, move on. That is the weakest use case. Try this instead: paste your idea and ask, what assumptions am I making? What is the weakest part? What would a smart critic disagree with? Now AI is not replacing your thinking. It is putting resistance against lazy thinking. You still decide. You still own the consequence. But you arrive sharper, because the first version had to survive a fight. Comment CRITIC if you want the exact prompt.

## Caption beats

- 00s: Stop asking AI to think for you.
- 03s: Ask it to argue with you.
- 06s: Most people use AI like a vending machine: type a question, get a confident answer, move on.

--- ai-005-the-4-box-prompt-for-mental-overload.md ---
# The 4-box prompt for mental overload

- ID: `ai-005`
- Pillar: `ai_life_systems`
- Target duration: 61s
- Estimated voiceover duration: 58s
- Word count: 139
- Platform note: TikTok Rewards Candidate

## Voiceover

If your brain has 47 tabs open, paste the mess into this four-box prompt. Do not ask AI to motivate you. Ask it to sort the noise. First, dump everything: tasks, worries, random reminders, half-decisions, all of it. Then ask AI: split this into four boxes. Box one: tasks I can actually do. Box two: worries I cannot solve right now. Box three: decisions I need to make. Box four: later, not today. Then ask one final question: what is the next action that takes less than two minutes? That is the whole trick. You are not looking for a perfect life plan. You are looking for one clean handle on the chaos. When the output appears, do only the two-minute action first. Momentum beats clarity. Comment RESET if you want me to turn this into a copy-paste template.

## Caption beats

- 00s: If your brain has 47 tabs open, paste the mess into this four-box prompt.
- 04s: Do not ask AI to motivate you.
- 08s: Ask it to sort the noise.

--- attention-002-the-algorithm-does-not-need-you-happy.md ---
# The algorithm does not need you happy

- ID: `attention-002`
- Pillar: `attention_design`
- Target duration: 67s
- Estimated voiceover duration: 61s
- Word count: 148
- Platform note: TikTok Rewards Candidate

## Voiceover

The algorithm does not need you happy. It needs you to stay. That does not mean every app is evil. It means the incentives are not the same as yours. You want focus, sleep, actual progress, maybe a quiet brain for once. The feed wants one more swipe. So stop fighting a machine with vibes. Change the design. First, turn your phone to greyscale when you need deep work. Second, remove your worst app from the home screen. Do not delete it if that feels dramatic. Just make it less automatic. Third, before you open a feed, set a leaving cue: one search, three videos, or five minutes. If there is no exit rule, the app owns the exit. This is not about becoming a monk. It is about making your phone slightly less talented at stealing your day. Which app gets moved off your home screen today?

## Caption beats

- 00s: The algorithm does not need you happy.
- 04s: It needs you to stay.
- 08s: That does not mean every app is evil.


Explanation:
**Adjusting criteria for AI tests**

I need to consider the issue with the AI005 candidate. It’s estimated that a score of 58 is below the 60 threshold. The tests currently allow scores down to 55, but for production, I might want to aim for over 60. I should add a sentence emphasizing that the target is now 61, and make sure to include a few additional words to clarify that the tests may still be acceptable.

patch

If your brain has 47 tabs open, paste the mess into this four-box prompt. Do not ask AI to motivate you. Ask it to sort the noise. First, dump everything: tasks, worries, random reminders, half-decisions, all of it. Then ask AI: split this into four boxes. Box one: tasks I can actually do. Box two: worries I cannot solve right now. Box three: decisions I need to make. Box four: later, not today. Then ask one final question: what is the next action that takes less than two minutes? That is the whole trick. You are not looking for a perfect life plan. You are looking for one clean handle on the chaos. When the output appears, do only the two-minute action first. If it still feels too big, shrink it again. Momentum beats clarity. Comment RESET if you want me to turn this into a copy-paste template.

If your brain has 47 tabs open, paste the mess into this four-box prompt. Do not ask AI to motivate you. Ask it to sort the noise. First, dump everything: tasks, worries, random reminders, half-decisions, all of it. Then ask AI: split this into four boxes. Box one: tasks I can actually do. Box two: worries I cannot solve right now. Box three: decisions I need to make. Box four: later, not today. Then ask one final question: what is the next action that takes less than two minutes? That is the whole trick. You are not looking for a perfect life plan. You are looking for one clean handle on the chaos. When the output appears, do only the two-minute action first. Momentum beats clarity. Comment RESET if you want me to turn this into a copy-paste template.


--- a//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/voiceover_finalizer.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/autoshorts/voiceover_finalizer.py
@@ -10,7 +10,7 @@

 TOP3_CUSTOM_VOICEOVERS = {
     "ai-004": """Stop asking AI to think for you. Ask it to argue with you. Most people use AI like a vending machine: type a question, get a confident answer, move on. That is the weakest use case. Try this instead: paste your idea and ask, what assumptions am I making? What is the weakest part? What would a smart critic disagree with? Now AI is not replacing your thinking. It is putting resistance against lazy thinking. You still decide. You still own the consequence. But you arrive sharper, because the first version had to survive a fight. Comment CRITIC if you want the exact prompt.""",
-    "ai-005": """If your brain has 47 tabs open, paste the mess into this four-box prompt. Do not ask AI to motivate you. Ask it to sort the noise. First, dump everything: tasks, worries, random reminders, half-decisions, all of it. Then ask AI: split this into four boxes. Box one: tasks I can actually do. Box two: worries I cannot solve right now. Box three: decisions I need to make. Box four: later, not today. Then ask one final question: what is the next action that takes less than two minutes? That is the whole trick. You are not looking for a perfect life plan. You are looking for one clean handle on the chaos. When the output appears, do only the two-minute action first. Momentum beats clarity. Comment RESET if you want me to turn this into a copy-paste template.""",
+    "ai-005": """If your brain has 47 tabs open, paste the mess into this four-box prompt. Do not ask AI to motivate you. Ask it to sort the noise. First, dump everything: tasks, worries, random reminders, half-decisions, all of it. Then ask AI: split this into four boxes. Box one: tasks I can actually do. Box two: worries I cannot solve right now. Box three: decisions I need to make. Box four: later, not today. Then ask one final question: what is the next action that takes less than two minutes? That is the whole trick. You are not looking for a perfect life plan. You are looking for one clean handle on the chaos. When the output appears, do only the two-minute action first. If it still feels too big, shrink it again. Momentum beats clarity. Comment RESET if you want me to turn this into a copy-paste template.""",
     "attention-002": """The algorithm does not need you happy. It needs you to stay. That does not mean every app is evil. It means the incentives are not the same as yours. You want focus, sleep, actual progress, maybe a quiet brain for once. The feed wants one more swipe. So stop fighting a machine with vibes. Change the design. First, turn your phone to greyscale when you need deep work. Second, remove your worst app from the home screen. Do not delete it if that feels dramatic. Just make it less automatic. Third, before you open a feed, set a leaving cue: one search, three videos, or five minutes. If there is no exit rule, the app owns the exit. This is not about becoming a monk. It is about making your phone slightly less talented at stealing your day. Which app gets moved off your home screen today?""",
 }


--- a//home/agent/jarvis_runtime/AutoShortsBot/tests/test_voiceover_finalizer.py
+++ b//home/agent/jarvis_runtime/AutoShortsBot/tests/test_voiceover_finalizer.py
@@ -27,7 +27,7 @@

     assert final["platform_note"] == "TikTok Rewards Candidate"
     assert final["word_count"] >= 120
-    assert final["estimated_seconds"] >= 55
+    assert final["estimated_seconds"] >= 60
     assert "comment" in final["voiceover"].lower()


terminal

rm -rf data/final_scripts/testbatch_002_top3
python3 scripts/generate_final_scripts.py --batch data/drafts/testbatch_002.json --out data/final_scripts/testbatch_002_top3
python3 -m pytest tests/test_voiceover_finalizer.py -q
python3 - <<'PY'
from pathlib import Path
for p in sorted(Path('data/final_scripts/testbatch_002_top3').glob('*.md')):
    for line in p.read_text(encoding='utf-8').splitlines():
        if line.startswith('- Estimated') or line.startswith('- Word') or line.startswith('- ID'):
            print(p.name, line)
PY


Output:
Generated 3 final scripts in data/final_scripts/testbatch_002_top3
data/final_scripts/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.md
data/final_scripts/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.md
data/final_scripts/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.md
...                                                                      [100%]
3 passed in 0.04s
ai-004-stop-asking-ai-to-think-for-you.md - ID: `ai-004`
ai-004-stop-asking-ai-to-think-for-you.md - Estimated voiceover duration: 43s
ai-004-stop-asking-ai-to-think-for-you.md - Word count: 104
ai-005-the-4-box-prompt-for-mental-overload.md - ID: `ai-005`
ai-005-the-4-box-prompt-for-mental-overload.md - Estimated voiceover duration: 61s
ai-005-the-4-box-prompt-for-mental-overload.md - Word count: 148
attention-002-the-algorithm-does-not-need-you-happy.md - ID: `attention-002`
attention-002-the-algorithm-does-not-need-you-happy.md - Estimated voiceover duration: 61s
attention-002-the-algorithm-does-not-need-you-happy.md - Word count: 148


todo

Finale Skripte für Top-3 generieren

Tests ausführen, committen und pushen


Tests für Voiceover-Finalizer schreiben

Voiceover-Finalizer implementieren

Finale Skripte für Top-3 generieren

Tests ausführen, committen und pushen


terminal

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


Output:
...............                                                          [100%]
15 passed in 0.05s
 M autoshorts/__init__.py
?? autoshorts/voiceover_finalizer.py
?? data/final_scripts/
?? scripts/generate_final_scripts.py
?? tests/test_voiceover_finalizer.py
 autoshorts/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)


set -euo pipefail
git add autoshorts data/final_scripts scripts/generate_final_scripts.py tests/test_voiceover_finalizer.py
git commit -m "feat: add voiceover finalizer for top retention scripts"


Output:
[feat/initial-growth-engine f73ddef] feat: add voiceover finalizer for top retention scripts
 7 files changed, 371 insertions(+), 1 deletion(-)
 create mode 100644 autoshorts/voiceover_finalizer.py
 create mode 100644 data/final_scripts/testbatch_002_top3/ai-004-stop-asking-ai-to-think-for-you.md
 create mode 100644 data/final_scripts/testbatch_002_top3/ai-005-the-4-box-prompt-for-mental-overload.md
 create mode 100644 data/final_scripts/testbatch_002_top3/attention-002-the-algorithm-does-not-need-you-happy.md
 create mode 100644 scripts/generate_final_scripts.py
 create mode 100644 tests/test_voiceover_finalizer.py


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


Output:
branch=feat/initial-growth-engine
local=f73ddef6f49aff7472fafed67558db01baf2706c
remote=f73ddef6f49aff7472fafed67558db01baf2706c
To https://github.com/Gamexgit/AutoShortsBot.git
   5799d16..f73ddef  feat/initial-growth-engine -> feat/initial-growth-engine


Output:
## feat/initial-growth-engine...origin/feat/initial-growth-engine
...............                                                          [100%]
15 passed in 0.05s


todo

Tests ausführen, committen und pushen


Erledigt, Sir.

## Implementiert

Ich habe den **Voiceover-Finalizer v1** gebaut und für die Top-3-Kandidaten finale Voiceover-Skripte generiert.

Gepusht auf:

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

Neuer Commit:

```text
f73ddef feat: add voiceover finalizer for top retention scripts
```

Remote-Verifikation:

```text
local == remote
```

Tests:

```text
15 passed in 0.05s
```

PR-Link bleibt:

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

---

## Neue Dateien

### Code

```text
autoshorts/voiceover_finalizer.py
scripts/generate_final_scripts.py
tests/test_voiceover_finalizer.py
```

### Finale Top-3-Skripte

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

---

## Finalisierte Top-3

### 1. `ai-004` — Stop asking AI to think for you

- **Ziel:** Growth Short
- **Target:** 41s
- **Estimated Voiceover:** 43s
- **Word Count:** 104

**Voiceover:**

> Stop asking AI to think for you. Ask it to argue with you. Most people use AI like a vending machine: type a question, get a confident answer, move on. That is the weakest use case. Try this instead: paste your idea and ask, what assumptions am I making? What is the weakest part? What would a smart critic disagree with? Now AI is not replacing your thinking. It is putting resistance against lazy thinking. You still decide. You still own the consequence. But you arrive sharper, because the first version had to survive a fight. Comment CRITIC if you want the exact prompt.

---

### 2. `ai-005` — The 4-box prompt for mental overload

- **Ziel:** TikTok Rewards Candidate
- **Target:** 61s
- **Estimated Voiceover:** 61s
- **Word Count:** 148

**Voiceover:**

> If your brain has 47 tabs open, paste the mess into this four-box prompt. Do not ask AI to motivate you. Ask it to sort the noise. First, dump everything: tasks, worries, random reminders, half-decisions, all of it. Then ask AI: split this into four boxes. Box one: tasks I can actually do. Box two: worries I cannot solve right now. Box three: decisions I need to make. Box four: later, not today. Then ask one final question: what is the next action that takes less than two minutes? That is the whole trick. You are not looking for a perfect life plan. You are looking for one clean handle on the chaos. When the output appears, do only the two-minute action first. If it still feels too big, shrink it again. Momentum beats clarity. Comment RESET if you want me to turn this into a copy-paste template.

---

### 3. `attention-002` — The algorithm does not need you happy

- **Ziel:** TikTok Rewards Candidate
- **Target:** 67s
- **Estimated Voiceover:** 61s
- **Word Count:** 148

**Voiceover:**

> The algorithm does not need you happy. It needs you to stay. That does not mean every app is evil. It means the incentives are not the same as yours. You want focus, sleep, actual progress, maybe a quiet brain for once. The feed wants one more swipe. So stop fighting a machine with vibes. Change the design. First, turn your phone to greyscale when you need deep work. Second, remove your worst app from the home screen. Do not delete it if that feels dramatic. Just make it less automatic. Third, before you open a feed, set a leaving cue: one search, three videos, or five minutes. If there is no exit rule, the app owns the exit. This is not about becoming a monk. It is about making your phone slightly less talented at stealing your day. Which app gets moved off your home screen today?

---

## Was der Finalizer jetzt erzeugt

Für jedes finale Skript:

- finales Voiceover
- Ziel-/Schätzlänge
- Word Count
- Caption Beats mit Timing
- Visual Concept
- Caption
- CTA
- Production Notes

Beispiel Production Notes:

- First frame must show the hook as large kinetic caption text.
- Use motion or a visual state change in the first 1–2 seconds.
- Avoid static AI slideshow visuals.
- For AI clips: show human decision/approval moment.
- For Attention Design: avoid moral panic; frame as defaults/design.

---

## Nächster Entwicklungsschritt

Jetzt ist der Text stark genug für den ersten Rendering-Schritt.

Ich würde als Nächstes bauen:

## Render Plan v1

Noch nicht direkt voll rendern, sondern zuerst pro Video einen **Shot Plan** generieren:

Für jedes Script:

- Szene 1: First-frame hook
- Szene 2: Pattern interrupt
- Szene 3: Core explanation
- Szene 4: Screen/card visual
- Szene 5: CTA
- Caption-Beats
- Asset-Typ:
  - Screen recording
  - generated background
  - card animation
  - stock/B-roll
  - kinetic text

Danach erst Renderer anpassen.

Warum? Weil die bisherigen Videos vor allem am **generischen KI-Bild/Slideshow-Look** leiden. Wenn wir jetzt blind rendern, macht der Bot wieder hübschen, aber scrollbaren Tapetenwechsel. Das wäre ästhetisch sauber und strategisch dumm — also exakt das, was wir vermeiden.