social-media-shorts-growth-engine

---
name: social-media-shorts-growth-engine
description: Design and operate a growth-oriented short-form video engine for YouTube Shorts, TikTok, Instagram Reels, and adjacent channels, with human approval before posting.
version: 1.0.0
tags: [shorts, tiktok, youtube-shorts, reels, content-strategy, growth, monetization, approval-workflow]
---

# Social-Media Shorts Growth Engine

Use this skill when the user wants to build, improve, or operate an automated/semi-automated system for short-form social media content: YouTube Shorts, TikTok, Instagram Reels, Facebook Reels, Pinterest, LinkedIn video, or similar.

## Core principle

Do **not** treat the task as “make videos.” Treat it as a **growth engine**:

1. Generate many sharply positioned ideas.
2. Score hooks and scripts before rendering.
3. Produce previews.
4. Send the user a review package.
5. Post only after explicit approval.
6. Capture analytics.
7. Feed performance back into the next batch.

A technically correct content generator that produces generic content is failure with extra rendering time. Charming, but still failure.

## Recommended workflow

### 1. Define content pillars

Start with 2–4 pillars, not a vague infinite topic pool. For each pillar define:

- audience
- positioning
- tone
- hook patterns
- visual rules
- no-go topics
- platform fit
- monetization angle

Example pillar structure:

```json
{
  "pillar": "ai_life_systems",
  "positioning": "AI as a servant for ordinary life systems, never as values or authority.",
  "tone": ["clear", "useful", "values-first", "non-hype"],
  "hook_patterns": ["AI is a bad god, but a good servant."],
  "visual_rules": ["screen-recording friendly", "human remains in control"],
  "no_go": ["fake earnings", "overpromising automation"]
}
```

### 2. Build a testbatch before building a full automation platform

For a new channel or unvalidated strategy, create a small batch first:

- 3 ideas per pillar
- 6 short growth candidates, usually 20–45 seconds
- 1–3 monetization-length candidates, usually 60+ seconds where the platform rewards length
- one clear hypothesis per idea

Each idea should include:

- id
- pillar
- title
- platform target
- duration
- hook
- script outline
- visual concept
- caption
- CTA
- quality score
- risk/policy notes

### 3. Add a script quality gate

Before rendering, score each draft for:

- hook strength in the first 1–2 seconds
- novelty / non-generic angle
- emotional pull
- clarity
- retention risk
- comment/share potential
- AI-slop risk
- policy/copyright risk

Render only drafts above the threshold, e.g. 8/10.

### 4. Use a human approval loop

Default to semi-automatic operation first. Send a Telegram review package with:

- preview video or script draft
- pillar and format
- title
- duration
- hook
- caption
- hashtags
- quality score
- policy notes
- suggested platforms

Recommended approval commands:

- `Freigabe alle`
- `Freigabe YouTube`
- `Freigabe TikTok`
- `Freigabe Instagram`
- `Ablehnen`
- `Ändern: <requested change>`

If approval is ambiguous, do not post. Ask once in the project topic.

### 5. Keep platform strategy separate from rendering

Rendering code should not hardcode content strategy. Keep separate config files for:

- niches/pillars
- platforms
- script formats
- visual styles
- approval workflow
- analytics schema

This lets the same renderer support different formats and prevents prompt spaghetti. Prompt spaghetti is still spaghetti even if it is wearing a YAML hat.

## Platform notes

### YouTube Shorts

- Shorts can be vertical or square and up to 3 minutes long.
- YouTube Partner Program Shorts route typically requires 1,000 subscribers and 10M valid public Shorts views in the last 90 days.
- Alternative longform YPP route: 1,000 subscribers and 4,000 valid public watch hours in the last 12 months.
- Shorts views in the Shorts feed do not count toward the 4,000 longform watch hours.
- Practical early growth tests often work best at 20–45 seconds; longer Shorts need a strong story or payoff.

### TikTok

- Creator Rewards availability depends on country/account eligibility.
- TikTok Creator Rewards generally requires a non-business account in good standing, 10,000 followers, and 100,000 authentic views in the last 30 days before joining, subject to country availability and current terms.
- Reward-eligible videos generally need to be at least 1 minute long and meet quality/originality criteria.
- This creates a strategy split:
  - 20–45s for growth and format discovery.
  - 60–75s for monetization candidates once a format works.

### Instagram / Facebook Reels

Useful for republishing 9:16 assets and testing share/save behavior. Treat direct payout as volatile; prefer indirect monetization through audience, affiliate, newsletter, or products.

### LinkedIn / X / Pinterest

Use selectively:

- LinkedIn: AI/productivity/workflows.
- X: AI demos, build-in-public, technical positioning.
- Pinterest: evergreen checklists, routines, faith/life prompts.

## Repository scaffold

A clean shorts-engine repo can start as:

```text
AutoShortsBot/
  autoshorts/
    config/
    ideas/
    scripting/
    rendering/
    review/
    publishing/
    analytics/
  configs/
    niches/
    platforms/
  data/
    drafts/
    performance/
  scripts/
  tests/
  README.md
  .gitignore
  pyproject.toml
```

Never commit:

- `.env`
- OAuth tokens
- API keys
- generated credentials
- rendered videos or large media outputs unless explicitly intended
- raw analytics exports with sensitive account data

## Implementation quality

When coding the engine, prefer TDD for deterministic parts:

- structured video candidate validation
- testbatch validation and duplicate-id checks
- renderable-candidate filtering before any renderer runs
- candidate factory conversion from raw drafts to validated `VideoCandidate` objects
- duration classification
- stable candidate-id generation and default hashtag derivation
- review formatting
- analytics import
- approval command parsing
- platform metadata generation
- script quality gates

A strong first implementation path is deterministic gating before any renderer exists:

1. Add structured `VideoCandidate` and `TestBatch` models before rendering code. Require candidate id, pillar, title, platforms, duration, hook, script outline, visual concept, caption, CTA, hashtags, quality score, policy notes, and one clear hypothesis.
2. Validate candidates and batches: reject missing strategic fields, unsupported platforms, out-of-range durations, empty batches, and duplicate candidate ids.
3. Add a `ContentPillar` registry and validate candidate pillar/platform fit before review or rendering.
4. Add a deterministic candidate factory: `CandidateDraft → create_candidate() → VideoCandidate`. It should generate stable ids, derive default hashtags, classify duration (`growth_short`, `monetization_candidate`, `experiment`), append the duration class to policy notes, require a hypothesis, and enforce both candidate and pillar-fit validation.
5. Add an approval parser that accepts only explicit commands such as `Freigabe alle`, `Freigabe YouTube`, `Freigabe TikTok`, `Freigabe Instagram`, `Ablehnen`, and `Ändern: <requested change>`.
6. Treat vague consent like `passt schon` as `needs_clarification`; never publish on ambiguous approval.
7. Add a conservative script quality gate before rendering, e.g. average hook/novelty/clarity/retention ≥ 8, AI-slop risk below threshold, and no high policy risk.
8. Add a Telegram review package renderer that surfaces the candidate, gate status, policy notes, preview path, and only relevant platform approval commands.
9. Test these deterministic pieces first with RED-GREEN-REFACTOR before adding renderers, image generation, video generation, or publishing APIs.

Rendering and external API calls can be integration-tested separately.

For this AutoShortsBot/Retention-Studio workflow, prefer a free-first renderer path before any paid video provider: `free_production_jobs → local_motion_renderer → FFmpeg MP4`. The local renderer should use Pillow/FFmpeg procedural motion, mechanism layers, kinetic captions, and generated metadata; preserve exact scene durations from render manifests, exclude text-only LLM refinement jobs from rendering, and keep generated local renders out of git (`data/local_motion_renders/`). NVIDIA/build.nvidia.com LLMs are optional text-refinement helpers only, not video generation.

When Hermes has the `openai-codex` image-generation backend available, GPT Image 2 is a good cloud-side keyframe/thumbnail/concept generator for the pipeline: `script idea → GPT Image 2 keyframe → human review → Wan/ComfyUI I2V or local renderer → deterministic captions/overlays`. Because GPT Image 2 runs cloud-side, it does not compete with local/remote GPU VRAM used by Wan/ComfyUI video jobs. Use low/medium for iteration and high for final keyframes; still add captions, labels, numbers, UI text, and audit-sensitive facts later in the deterministic renderer rather than trusting baked image text.

If the user wants more realistic footage without spending money, first check whether a local GPU/ComfyUI host is available. Preferred path when reachable: `scene spec → ComfyUI/Wan API over Tailscale → imported_ai_clips/<video_id>/scene_XX.mp4 → local captions/mechanism layers/FFmpeg assembly`. For a 16 GB A5000 Laptop GPU, use the `comfyui` skill reference `wan-video-tailscale-a5000.md`: Wan2.1 T2V 1.3B FP8, `--lowvram`, CPU text encoding, VAE tiling, and a small smoke-test ladder before quality probes. If no local GPU path is available or quality is insufficient, use the hybrid free-tier handoff rather than pretending local procedural graphics are realistic: `free_production_jobs → free_ai_video_handoff → manual browser generation in free-tier tools (PixVerse/Pika/CapCut/Hailuo only with existing free credits) → import clips under data/imported_ai_clips/ → local captions/mechanism layers/FFmpeg assembly`. Guardrails: no paid top-ups, no subscriptions, avoid Pro templates, no baked captions/logos/watermarks where possible, and keep imported clips out of git.

For this AutoShortsBot/Retention-Studio workflow, prefer a free-first renderer path before any paid video provider: `free_production_jobs → local_motion_renderer → FFmpeg MP4`. The local renderer should produce moving mechanism/caption previews, not final static cards. When the user has a capable local/remote GPU (e.g. RTX A5000 16 GB via Tailscale), promote the path to `ComfyUI/Wan or LTX local clip generation → imported_ai_clips → hybrid local renderer → FFmpeg`, with free web providers (PixVerse/Pika/CapCut) only as fallback. External free-tier AI video tools can still be used manually for realistic b-roll, but keep paid top-ups/subscriptions disabled and store downloaded clips under `data/imported_ai_clips/<video_id>/scene_XX.mp4`. Generate provider handoff packets for manual browser generation and imported-clip assembly plans so missing clips fall back to local placeholders.

## Pitfalls

- **Generic advice content:** “4 tips to be healthier/productive” is usually too weak unless the hook, identity, or story is sharp.
- **AI-image slop:** avoid generated images containing fake UI text, gibberish signs, distorted hands/faces, or abstract generic graphs. These damage trust fast.
- **Too much automation too soon:** validate scripts and formats before building posting APIs.
- **No analytics loop:** producing content without tracking retention, likes, shares, comments, and follows is blind output.
- **Mixed language drift:** decide language per channel or per pillar before rendering. Mixed English/German drafts are fine for ideation, not final voiceover unless intentional.

## References

- `references/autoshortsbot-case-study.md` — case-study details from the AutoShortsBot setup: first testbatch shape, repo scaffold, and approval loop.
- `references/approval-first-bot-foundation.md` — implementation pattern for the first safe bot slice: explicit approval parser, quality gate, Telegram review package, and TDD test shape.
- `references/idea-candidate-testbatch-foundation.md` — deterministic `VideoCandidate`/`TestBatch` foundation before rendering: validation rules, renderable filtering, and test shape.
- `references/content-pillar-registry-foundation.md` — strategic `ContentPillar` registry pattern: default `ai_life_systems`, pillar/candidate fit validation, and TDD test shape.
- `references/candidate-factory-foundation.md` — deterministic `CandidateDraft → create_candidate() → VideoCandidate` layer: stable ids, duration classes, default hashtags, hypothesis requirement, and tests.


test-driven-development

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

# Test-Driven Development (TDD)

## Overview

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

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

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

## When to Use

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

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

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

## The Iron Law

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

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

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

Implement fresh from tests. Period.

## Red-Green-Refactor Cycle

### RED — Write Failing Test

Write one minimal test showing what should happen.

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

    result = retry_operation(operation)

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

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

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

### Verify RED — Watch It Fail

**MANDATORY. Never skip.**

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

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

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

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

### GREEN — Minimal Code

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

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

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

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

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

We'll fix it in REFACTOR.

### Verify GREEN — Watch It Pass

**MANDATORY.**

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

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

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

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

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

### REFACTOR — Clean Up

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

Keep tests green throughout. Don't add behavior.

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

### Repeat

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

## Why Order Matters

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

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

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

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

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

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

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

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

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

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

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

"Pragmatic" shortcuts = debugging in production = slower.

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

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

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

## Common Rationalizations

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

## Red Flags — STOP and Start Over

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

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

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

## Verification Checklist

Before marking work complete:

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

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

## When Stuck

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

## Hermes Agent Integration

### Running Tests

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

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

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

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

### With delegate_task

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

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

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

### With systematic-debugging

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

Never fix bugs without a test.

## Testing Anti-Patterns

- **Testing mock behavior instead of real behavior** — mocks should verify interactions, not replace the system under test
- **Testing implementation details** — test behavior/results, not internal method calls
- **Happy path only** — always test edge cases, errors, and boundaries
- **Brittle tests** — tests should verify behavior, not structure; refactoring shouldn't break them
- **Substring sentinels that collide with generated IDs** — when asserting that amounts or fixture values are excluded from a result, assert the full formatted token (e.g. `999.00`) or a typed field, not a short substring like `999` that can appear inside UUIDs/hash-like IDs.
- **Domain classes named `Test*` trigger pytest collection warnings** — if a production dataclass/model must be named like `TestBatch`, set `__test__ = False` on the class or choose a non-`Test` prefix. Keep the fix in production code when the name is part of the domain language, and verify the warning is gone before committing.

## Final Rule

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

No exceptions without the user's explicit permission.


Need implement workflow.

Need implement workflow.

todo

Repo-Status prüfen

Review-Workflow-Tests schreiben und RED verifizieren

review.workflow minimal implementieren

Tests laufen lassen, README aktualisieren, committen


Repo-Status prüfen

review.workflow minimal implementieren


terminal

git status --short --branch && git log --oneline -7


Output:
## feat/core-approval-quality-gates
deea24f feat: add candidate factory
ae6a4f6 feat: add content pillar registry
49d6daf feat: add idea candidates and testbatches
55b17fa feat: add telegram review package
3555ec2 feat: add approval parser and quality gate
7642749 Initial commit


todo

Repo-Status prüfen


from autoshorts.ideas.candidate import VideoCandidate
from autoshorts.ideas.testbatch import TestBatch
from autoshorts.review.workflow import (
    CandidateReviewItem,
    ReviewBatch,
    build_candidate_review_item,
    build_review_batch,
)
from autoshorts.scripting.quality import ScriptQualityScore

def _score_good() -> ScriptQualityScore:
    return ScriptQualityScore(
        hook_strength=9,
        novelty=8,
        clarity=9,
        retention=8,
        ai_slop_risk=2,
        policy_risk="low",
    )

def _score_weak() -> ScriptQualityScore:
    return ScriptQualityScore(
        hook_strength=5,
        novelty=5,
        clarity=6,
        retention=5,
        ai_slop_risk=8,
        policy_risk="low",
    )

def _candidate(**overrides) -> VideoCandidate:
    data = {
        "candidate_id": "vid-001",
        "pillar": "ai_life_systems",
        "title": "AI ist ein schlechter Gott, aber ein guter Diener",
        "platforms": ("youtube", "tiktok"),
        "duration_seconds": 34,
        "hook": "AI sollte dir dienen, nicht dich führen.",
        "script_outline": "Show AI chaos, then a controlled human-approved workflow.",
        "visual_concept": "Calm desk setup, explicit approval checkpoint, no fake dashboards.",
        "caption": "KI als Werkzeug, nicht als Wertekompass.",
        "cta": "Follow for practical AI life systems.",
        "hashtags": ("#AI", "#LifeSystems"),
        "quality_score": _score_good(),
        "policy_notes": ("No fake earnings claims",),
        "hypothesis": "Values-first AI positioning gets more saves than hype content.",
    }
    data.update(overrides)
    return VideoCandidate(**data)

def test_candidate_review_item_ready_for_review_when_valid_and_renderable():
    item = build_candidate_review_item(
        _candidate(),
        preview_path="data/local_motion_renders/vid-001.mp4",
    )

    assert item.status == "ready_for_review"
    assert item.errors == ()
    assert item.package is not None
    assert item.package.preview_path == "data/local_motion_renders/vid-001.mp4"

def test_candidate_review_item_blocked_when_quality_gate_fails_but_package_exists():
    item = build_candidate_review_item(_candidate(quality_score=_score_weak()))

    assert item.status == "blocked"
    assert "quality gate blocked candidate" in item.errors
    assert item.package is not None
    assert item.package.candidate_id == "vid-001"

def test_candidate_review_item_invalid_when_candidate_validation_fails():
    item = build_candidate_review_item(_candidate(hook="", duration_seconds=4))

    assert item.status == "invalid"
    assert item.package is None
    assert "hook is required" in item.errors
    assert "duration_seconds must be between 10 and 180" in item.errors

def test_review_batch_splits_ready_blocked_and_invalid_items():
    ready = _candidate(candidate_id="ready")
    blocked = _candidate(candidate_id="blocked", quality_score=_score_weak())
    invalid = _candidate(candidate_id="invalid", hook="")
    batch = TestBatch(batch_id="batch-001", candidates=(ready, blocked, invalid))

    review_batch = build_review_batch(
        batch,
        preview_paths={"ready": "data/local_motion_renders/ready.mp4"},
    )

    assert isinstance(review_batch, ReviewBatch)
    assert [item.candidate.candidate_id for item in review_batch.ready_for_review] == ["ready"]
    assert [item.candidate.candidate_id for item in review_batch.blocked] == ["blocked"]
    assert [item.candidate.candidate_id for item in review_batch.invalid] == ["invalid"]
    assert review_batch.ready_for_review[0].package.preview_path == "data/local_motion_renders/ready.mp4"

def test_review_batch_preserves_input_order_within_each_status_bucket():
    first = _candidate(candidate_id="first")
    second = _candidate(candidate_id="second")
    third = _candidate(candidate_id="third", quality_score=_score_weak())
    batch = TestBatch(batch_id="ordered", candidates=(first, third, second))

    review_batch = build_review_batch(batch)

    assert [item.candidate.candidate_id for item in review_batch.ready_for_review] == [
        "first",
        "second",
    ]
    assert [item.candidate.candidate_id for item in review_batch.blocked] == ["third"]


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


terminal

python -m pytest tests/test_review_workflow.py -q


Output:
==================================== ERRORS ====================================
________________ ERROR collecting tests/test_review_workflow.py ________________
ImportError while importing test module '/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_review_workflow.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_review_workflow.py:3: in <module>
    from autoshorts.review.workflow import (
E   ModuleNotFoundError: No module named 'autoshorts.review.workflow'
=========================== short test summary info ============================
ERROR tests/test_review_workflow.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.11s


todo

review.workflow minimal implementieren


"""Review workflow orchestration before rendering or publishing.

This module classifies candidates into deterministic buckets so later Telegram,
rendering, and publishing code can stay boring. Boring is a feature here.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

from autoshorts.ideas.candidate import VideoCandidate, validate_candidate
from autoshorts.ideas.testbatch import TestBatch
from autoshorts.review.package import ReviewPackage
from autoshorts.scripting.quality import should_render

ReviewStatus = Literal["ready_for_review", "blocked", "invalid"]

@dataclass(frozen=True)
class CandidateReviewItem:
    candidate: VideoCandidate
    status: ReviewStatus
    package: ReviewPackage | None
    errors: tuple[str, ...] = ()

@dataclass(frozen=True)
class ReviewBatch:
    batch_id: str
    ready_for_review: tuple[CandidateReviewItem, ...]
    blocked: tuple[CandidateReviewItem, ...]
    invalid: tuple[CandidateReviewItem, ...]

# Prevent pytest collection warnings if imported into tests.
ReviewBatch.__test__ = False

def build_candidate_review_item(
    candidate: VideoCandidate,
    *,
    preview_path: str | None = None,
) -> CandidateReviewItem:
    validation = validate_candidate(candidate)
    if not validation.is_valid:
        return CandidateReviewItem(
            candidate=candidate,
            status="invalid",
            package=None,
            errors=validation.errors,
        )

    package = candidate.to_review_package(preview_path=preview_path)
    if not should_render(candidate.quality_score):
        return CandidateReviewItem(
            candidate=candidate,
            status="blocked",
            package=package,
            errors=("quality gate blocked candidate",),
        )

    return CandidateReviewItem(
        candidate=candidate,
        status="ready_for_review",
        package=package,
        errors=(),
    )

def build_review_batch(
    batch: TestBatch,
    *,
    preview_paths: dict[str, str] | None = None,
) -> ReviewBatch:
    preview_paths = preview_paths or {}
    ready: list[CandidateReviewItem] = []
    blocked: list[CandidateReviewItem] = []
    invalid: list[CandidateReviewItem] = []

    for candidate in batch.candidates:
        item = build_candidate_review_item(
            candidate,
            preview_path=preview_paths.get(candidate.candidate_id),
        )
        if item.status == "ready_for_review":
            ready.append(item)
        elif item.status == "blocked":
            blocked.append(item)
        else:
            invalid.append(item)

    return ReviewBatch(
        batch_id=batch.batch_id,
        ready_for_review=tuple(ready),
        blocked=tuple(blocked),
        invalid=tuple(invalid),
    )


LSP diagnostics introduced by this edit:
<diagnostics file="/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/review/workflow.py">
ERROR [37:13] Cannot assign to attribute "__test__" for class "type[ReviewBatch]"
  Attribute "__test__" is unknown [reportAttributeAccessIssue] (Pyright)
</diagnostics>


Need pyright diag fix by class var inside dataclass. patch.

patch

@dataclass(frozen=True)
class ReviewBatch:
    batch_id: str
    ready_for_review: tuple[CandidateReviewItem, ...]
    blocked: tuple[CandidateReviewItem, ...]
    invalid: tuple[CandidateReviewItem, ...]

# Prevent pytest collection warnings if imported into tests.
ReviewBatch.__test__ = False

@dataclass(frozen=True)
class ReviewBatch:
    batch_id: str
    ready_for_review: tuple[CandidateReviewItem, ...]
    blocked: tuple[CandidateReviewItem, ...]
    invalid: tuple[CandidateReviewItem, ...]

    # Prevent pytest collection warnings if imported into tests.
    __test__ = False


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/review/workflow.py
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/autoshorts/review/workflow.py
@@ -32,9 +32,8 @@
     blocked: tuple[CandidateReviewItem, ...]
     invalid: tuple[CandidateReviewItem, ...]

-
-# Prevent pytest collection warnings if imported into tests.
-ReviewBatch.__test__ = False
+    # Prevent pytest collection warnings if imported into tests.
+    __test__ = False

 def build_candidate_review_item(


terminal

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


todo

review.workflow minimal implementieren


patch

- `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
- `autoshorts.scripting.quality` — conservative pre-render quality gate.

- `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
- `autoshorts.review.workflow` — splits candidates into ready, blocked, and invalid review buckets.
- `autoshorts.scripting.quality` — conservative pre-render quality gate.


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -14,6 +14,7 @@
 - `autoshorts.ideas.testbatch` — testbatch validation and renderable-candidate filtering.
 - `autoshorts.review.approvals` — deterministic Telegram approval command parser.
 - `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
+- `autoshorts.review.workflow` — splits candidates into ready, blocked, and invalid review buckets.
 - `autoshorts.scripting.quality` — conservative pre-render quality gate.
 - Tests for content pillars, candidate factory, idea candidates, testbatches, approval parsing, review formatting, and script quality gating.

No linter for .md files


- Tests for content pillars, candidate factory, idea candidates, testbatches, review workflow, approval parsing, review formatting, and script quality gating.

- Tests for content pillars, candidate factory, idea candidates, testbatches, approval parsing, review formatting, and script quality gating.


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -16,7 +16,7 @@
 - `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
 - `autoshorts.review.workflow` — splits candidates into ready, blocked, and invalid review buckets.
 - `autoshorts.scripting.quality` — conservative pre-render quality gate.
-- Tests for content pillars, candidate factory, idea candidates, testbatches, approval parsing, review formatting, and script quality gating.
+- Tests for content pillars, candidate factory, idea candidates, testbatches, review workflow, approval parsing, review formatting, and script quality gating.

 ## Human approval commands

No linter for .md files


A blocked quality gate is rendered as `Render gate: BLOCKED — nicht automatisch rendern`.

## Review workflow

`build_candidate_review_item()` classifies one candidate as:

- `ready_for_review` — valid and renderable; includes a review package;
- `blocked` — valid but blocked by quality gate; still includes a review package with blocked gate status;
- `invalid` — fails candidate validation; does not create a review package.

`build_review_batch()` processes a `TestBatch` into `ready_for_review`, `blocked`, and `invalid` buckets while preserving input order inside each bucket. Optional preview paths are attached by candidate id.

## Idea candidates and testbatches

A blocked quality gate is rendered as `Render gate: BLOCKED — nicht automatisch rendern`.

## Idea candidates and testbatches


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -52,6 +52,16 @@
 - only the relevant platform-specific approval commands.

 A blocked quality gate is rendered as `Render gate: BLOCKED — nicht automatisch rendern`.
+
+## Review workflow
+
+`build_candidate_review_item()` classifies one candidate as:
+
+- `ready_for_review` — valid and renderable; includes a review package;
+- `blocked` — valid but blocked by quality gate; still includes a review package with blocked gate status;
+- `invalid` — fails candidate validation; does not create a review package.
+
+`build_review_batch()` processes a `TestBatch` into `ready_for_review`, `blocked`, and `invalid` buckets while preserving input order inside each bucket. Optional preview paths are attached by candidate id.

 ## Idea candidates and testbatches

No linter for .md files


terminal

python -m pytest tests -q && git status --short


Output:
......................................                                   [100%]
38 passed in 0.07s
 M README.md
?? autoshorts/review/workflow.py
?? tests/test_review_workflow.py


git add README.md autoshorts/review/workflow.py tests/test_review_workflow.py && git commit -m "feat: add review workflow buckets" && git status --short --branch && git log --oneline -7


Output:
[feat/core-approval-quality-gates 2155728] feat: add review workflow buckets
 3 files changed, 224 insertions(+), 1 deletion(-)
 create mode 100644 autoshorts/review/workflow.py
 create mode 100644 tests/test_review_workflow.py
## feat/core-approval-quality-gates
2155728 feat: add review workflow buckets
deea24f feat: add candidate factory
ae6a4f6 feat: add content pillar registry
49d6daf feat: add idea candidates and testbatches
55b17fa feat: add telegram review package
3555ec2 feat: add approval parser and quality gate
7642749 Initial commit


Output:
......................................                                   [100%]
38 passed in 0.07s


todo

Repo-Status prüfen

review.workflow minimal implementieren


Weitergebaut, Sir.

## Neuer Baustein: Review Workflow Buckets

Ich habe jetzt die interne Review-Pipeline ergänzt:

```text
autoshorts/review/workflow.py
tests/test_review_workflow.py
```

## Was neu ist

### 1. `CandidateReviewItem`

Ein Kandidat wird jetzt in einen klaren Review-Status einsortiert:

```text
ready_for_review
blocked
invalid
```

### 2. `build_candidate_review_item()`

Klassifiziert einen einzelnen `VideoCandidate`.

Logik:

- **`ready_for_review`**
  - Kandidat ist valide
  - Quality Gate erlaubt Rendering
  - Review Package wird erzeugt

- **`blocked`**
  - Kandidat ist valide
  - Quality Gate blockiert ihn
  - Review Package wird trotzdem erzeugt
  - Fehler enthält:

```text
quality gate blocked candidate
```

- **`invalid`**
  - Kandidat scheitert an Validierung
  - kein Review Package
  - Validierungsfehler werden gesammelt

Beispiel invalid:

```text
hook is required
duration_seconds must be between 10 and 180
```

### 3. `ReviewBatch`

Ein ganzer `TestBatch` wird jetzt in drei Buckets aufgeteilt:

```text
ready_for_review
blocked
invalid
```

### 4. `build_review_batch()`

Verarbeitet einen Batch und:

- hängt optionale Preview-Pfade per Candidate-ID an
- behält Reihenfolge innerhalb jedes Buckets bei
- trennt sauber zwischen:
  - bereit für Sir
  - blockiert wegen Qualität
  - ungültig wegen Strukturfehler

## Warum das wichtig ist

Damit haben wir jetzt die erste echte Pre-Rendering-Entscheidungsebene:

```text
Nicht alles wird gerendert.
Nicht alles wird reviewed.
Nichts Invalides bekommt ein Freigabe-Paket.
Nichts Blockiertes wird stillschweigend durchgewunken.
```

Kurz: Der Bot beginnt, Benehmen zu lernen. Ein seltener Anblick.

## TDD eingehalten

Ablauf:

1. Tests geschrieben.
2. RED verifiziert:

```text
ModuleNotFoundError: No module named 'autoshorts.review.workflow'
```

3. Minimal implementiert.
4. Tests grün.
5. README aktualisiert.
6. Commit erstellt.

## Tests

Aktueller Stand:

```text
38 passed in 0.07s
```

## Neuer Commit

Branch:

```text
feat/core-approval-quality-gates
```

Commit:

```text
2155728 feat: add review workflow buckets
```

Aktueller Verlauf:

```text
2155728 feat: add review workflow buckets
deea24f feat: add candidate factory
ae6a4f6 feat: add content pillar registry
49d6daf feat: add idea candidates and testbatches
55b17fa feat: add telegram review package
3555ec2 feat: add approval parser and quality gate
7642749 Initial commit
```

## Pipeline jetzt

Wir haben jetzt:

```text
Content Pillar
→ CandidateDraft
→ Candidate Factory
→ VideoCandidate
→ Candidate Validation
→ Pillar Fit Validation
→ TestBatch
→ Review Workflow
   → ready_for_review
   → blocked
   → invalid
→ Review Package
→ Approval Parser
```

## Nächster sinnvoller Schritt

Als Nächstes würde ich bauen:

```text
autoshorts/review/render.py
```

Ziel:

- kompletten `ReviewBatch` als Telegram-Textpaket rendern
- Ready Candidates mit Review Package ausgeben
- Blocked Candidates kompakt mit Gründen ausgeben
- Invalid Candidates kompakt mit Validierungsfehlern ausgeben
- Summary am Anfang:

```text
Ready: 2
Blocked: 1
Invalid: 1
```

Dann hätten wir den ersten direkt sendbaren Telegram-Review-Output für einen ganzen Ideenbatch.


[M G] Ok, los.


social-media-shorts-growth-engine

Design and operate a growth-oriented short-form video engine for YouTube Shorts, TikTok, Instagram Reels, and adjacent channels, with human approval before posting.

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


todo

Repo-Status prüfen

Review-Batch-Render-Tests schreiben und RED verifizieren

review.render minimal implementieren


Repo-Status prüfen

review.render minimal implementieren


terminal

git status --short --branch && git log --oneline -8


Output:
## feat/core-approval-quality-gates
2155728 feat: add review workflow buckets
deea24f feat: add candidate factory
ae6a4f6 feat: add content pillar registry
49d6daf feat: add idea candidates and testbatches
55b17fa feat: add telegram review package
3555ec2 feat: add approval parser and quality gate
7642749 Initial commit


todo

Repo-Status prüfen


from autoshorts.ideas.candidate import VideoCandidate
from autoshorts.ideas.testbatch import TestBatch
from autoshorts.review.render import render_review_batch_summary
from autoshorts.review.workflow import build_review_batch
from autoshorts.scripting.quality import ScriptQualityScore

def _good_score() -> ScriptQualityScore:
    return ScriptQualityScore(
        hook_strength=9,
        novelty=8,
        clarity=9,
        retention=8,
        ai_slop_risk=2,
        policy_risk="low",
    )

def _weak_score() -> ScriptQualityScore:
    return ScriptQualityScore(
        hook_strength=5,
        novelty=5,
        clarity=6,
        retention=5,
        ai_slop_risk=8,
        policy_risk="low",
    )

def _candidate(**overrides) -> VideoCandidate:
    data = {
        "candidate_id": "vid-001",
        "pillar": "ai_life_systems",
        "title": "AI ist ein schlechter Gott, aber ein guter Diener",
        "platforms": ("youtube", "tiktok"),
        "duration_seconds": 34,
        "hook": "AI sollte dir dienen, nicht dich führen.",
        "script_outline": "Show AI chaos, then a controlled human-approved workflow.",
        "visual_concept": "Calm desk setup, explicit approval checkpoint, no fake dashboards.",
        "caption": "KI als Werkzeug, nicht als Wertekompass.",
        "cta": "Follow for practical AI life systems.",
        "hashtags": ("#AI", "#LifeSystems"),
        "quality_score": _good_score(),
        "policy_notes": ("No fake earnings claims",),
        "hypothesis": "Values-first AI positioning gets more saves than hype content.",
    }
    data.update(overrides)
    return VideoCandidate(**data)

def test_render_review_batch_summary_contains_counts_and_sections():
    batch = TestBatch(
        batch_id="batch-001",
        candidates=(
            _candidate(candidate_id="ready", title="Ready title"),
            _candidate(candidate_id="blocked", title="Blocked title", quality_score=_weak_score()),
            _candidate(candidate_id="invalid", title="Invalid title", hook=""),
        ),
    )
    review_batch = build_review_batch(
        batch,
        preview_paths={"ready": "data/local_motion_renders/ready.mp4"},
    )

    text = render_review_batch_summary(review_batch)

    assert "## Review Batch: batch-001" in text
    assert "Ready: 1" in text
    assert "Blocked: 1" in text
    assert "Invalid: 1" in text
    assert "## Ready for review" in text
    assert "Review: ready" in text
    assert "Preview: data/local_motion_renders/ready.mp4" in text
    assert "## Blocked" in text
    assert "blocked — Blocked title" in text
    assert "quality gate blocked candidate" in text
    assert "## Invalid" in text
    assert "invalid — Invalid title" in text
    assert "hook is required" in text

def test_render_review_batch_summary_omits_empty_sections():
    batch = TestBatch(batch_id="only-ready", candidates=(_candidate(candidate_id="ready"),))
    review_batch = build_review_batch(batch)

    text = render_review_batch_summary(review_batch)

    assert "Ready: 1" in text
    assert "Blocked: 0" in text
    assert "Invalid: 0" in text
    assert "## Ready for review" in text
    assert "## Blocked" not in text
    assert "## Invalid" not in text

def test_render_review_batch_summary_has_no_publish_language():
    batch = TestBatch(batch_id="safe", candidates=(_candidate(candidate_id="ready"),))
    review_batch = build_review_batch(batch)

    text = render_review_batch_summary(review_batch)

    assert "publish" not in text.casefold()
    assert "post" not in text.casefold()


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


terminal

python -m pytest tests/test_review_render.py -q


Output:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_review_render.py _________________
ImportError while importing test module '/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_review_render.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_review_render.py:3: in <module>
    from autoshorts.review.render import render_review_batch_summary
E   ModuleNotFoundError: No module named 'autoshorts.review.render'
=========================== short test summary info ============================
ERROR tests/test_review_render.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.12s


todo

review.render minimal implementieren


"""Telegram-safe rendering for complete review batches."""

from __future__ import annotations

from autoshorts.review.package import render_telegram_review_package
from autoshorts.review.workflow import CandidateReviewItem, ReviewBatch

def _render_error_item(item: CandidateReviewItem) -> str:
    errors = "; ".join(item.errors) if item.errors else "-"
    return f"- {item.candidate.candidate_id} — {item.candidate.title}\n  Reasons: {errors}"

def render_review_batch_summary(review_batch: ReviewBatch) -> str:
    """Render a full review batch as one Telegram-friendly text block.

    The output deliberately avoids language that implies publication. It is a
    review document, not a launch button. Civilization, barely.
    """

    lines: list[str] = [
        f"## Review Batch: {review_batch.batch_id}",
        f"Ready: {len(review_batch.ready_for_review)}",
        f"Blocked: {len(review_batch.blocked)}",
        f"Invalid: {len(review_batch.invalid)}",
    ]

    if review_batch.ready_for_review:
        lines.extend(["", "## Ready for review"])
        for item in review_batch.ready_for_review:
            if item.package is not None:
                lines.append(render_telegram_review_package(item.package))

    if review_batch.blocked:
        lines.extend(["", "## Blocked"])
        lines.extend(_render_error_item(item) for item in review_batch.blocked)

    if review_batch.invalid:
        lines.extend(["", "## Invalid"])
        lines.extend(_render_error_item(item) for item in review_batch.invalid)

    return "\n".join(lines)


terminal

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


todo

review.render minimal implementieren


patch

- `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
- `autoshorts.review.render` — renders complete review batches into Telegram-safe text.
- `autoshorts.review.workflow` — splits candidates into ready, blocked, and invalid review buckets.

- `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
- `autoshorts.review.workflow` — splits candidates into ready, blocked, and invalid review buckets.


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -14,6 +14,7 @@
 - `autoshorts.ideas.testbatch` — testbatch validation and renderable-candidate filtering.
 - `autoshorts.review.approvals` — deterministic Telegram approval command parser.
 - `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
+- `autoshorts.review.render` — renders complete review batches into Telegram-safe text.
 - `autoshorts.review.workflow` — splits candidates into ready, blocked, and invalid review buckets.
 - `autoshorts.scripting.quality` — conservative pre-render quality gate.
 - Tests for content pillars, candidate factory, idea candidates, testbatches, review workflow, approval parsing, review formatting, and script quality gating.

No linter for .md files


- Tests for content pillars, candidate factory, idea candidates, testbatches, review workflow, review-batch rendering, approval parsing, review formatting, and script quality gating.

- Tests for content pillars, candidate factory, idea candidates, testbatches, review workflow, approval parsing, review formatting, and script quality gating.


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -17,7 +17,7 @@
 - `autoshorts.review.render` — renders complete review batches into Telegram-safe text.
 - `autoshorts.review.workflow` — splits candidates into ready, blocked, and invalid review buckets.
 - `autoshorts.scripting.quality` — conservative pre-render quality gate.
-- Tests for content pillars, candidate factory, idea candidates, testbatches, review workflow, approval parsing, review formatting, and script quality gating.
+- Tests for content pillars, candidate factory, idea candidates, testbatches, review workflow, review-batch rendering, approval parsing, review formatting, and script quality gating.

 ## Human approval commands

No linter for .md files


`build_review_batch()` processes a `TestBatch` into `ready_for_review`, `blocked`, and `invalid` buckets while preserving input order inside each bucket. Optional preview paths are attached by candidate id.

`render_review_batch_summary()` turns the complete `ReviewBatch` into one Telegram-safe text block with summary counts, full ready-for-review packages, and compact blocked/invalid reason lists. It deliberately avoids publish/post language; approval and publishing remain separate later stages.

## Idea candidates and testbatches

`build_review_batch()` processes a `TestBatch` into `ready_for_review`, `blocked`, and `invalid` buckets while preserving input order inside each bucket. Optional preview paths are attached by candidate id.

## Idea candidates and testbatches


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -63,6 +63,8 @@
 - `invalid` — fails candidate validation; does not create a review package.

 `build_review_batch()` processes a `TestBatch` into `ready_for_review`, `blocked`, and `invalid` buckets while preserving input order inside each bucket. Optional preview paths are attached by candidate id.
+
+`render_review_batch_summary()` turns the complete `ReviewBatch` into one Telegram-safe text block with summary counts, full ready-for-review packages, and compact blocked/invalid reason lists. It deliberately avoids publish/post language; approval and publishing remain separate later stages.

 ## Idea candidates and testbatches

No linter for .md files


terminal

Output:
.........................................                                [100%]
41 passed in 0.07s
 M README.md
?? autoshorts/review/render.py
?? tests/test_review_render.py


git add README.md autoshorts/review/render.py tests/test_review_render.py && git commit -m "feat: add review batch renderer" && git status --short --branch && git log --oneline -8


Output:
[feat/core-approval-quality-gates b7b5270] feat: add review batch renderer
 3 files changed, 149 insertions(+), 1 deletion(-)
 create mode 100644 autoshorts/review/render.py
 create mode 100644 tests/test_review_render.py
## feat/core-approval-quality-gates
b7b5270 feat: add review batch renderer
2155728 feat: add review workflow buckets
deea24f feat: add candidate factory
ae6a4f6 feat: add content pillar registry
49d6daf feat: add idea candidates and testbatches
55b17fa feat: add telegram review package
3555ec2 feat: add approval parser and quality gate
7642749 Initial commit


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


todo

Repo-Status prüfen

review.render minimal implementieren


Erledigt, Sir.

## Neuer Baustein: Review Batch Renderer

Ich habe jetzt den direkt Telegram-tauglichen Batch-Renderer eingebaut:

```text
autoshorts/review/render.py
tests/test_review_render.py
```

## Was neu ist

### `render_review_batch_summary()`

Diese Funktion rendert einen kompletten `ReviewBatch` als einen Telegram-sicheren Textblock.

Er enthält:

- Batch-ID
- Summary Counts
  - `Ready`
  - `Blocked`
  - `Invalid`
- vollständige Review Packages für ready Candidates
- kompakte Fehlerlisten für blocked Candidates
- kompakte Fehlerlisten für invalid Candidates

Beispielstruktur:

```text
## Review Batch: batch-001
Ready: 1
Blocked: 1
Invalid: 1

## Ready for review
...

## Blocked
- blocked — Blocked title
  Reasons: quality gate blocked candidate

## Invalid
- invalid — Invalid title
  Reasons: hook is required
```

## Wichtiger Guardrail

Der Renderer vermeidet bewusst Sprache wie:

```text
publish
post
```

Warum? Weil Review und Publishing getrennte Phasen bleiben sollen.

Also kein versehentlicher semantischer Stolperdraht à la “approved-looking text = upload”. Das wäre genau die Sorte Automations-Dummheit, für die später Meetings erfunden werden.

## TDD eingehalten

Ablauf:

1. Tests für Batch Renderer geschrieben.
2. RED verifiziert:

```text
ModuleNotFoundError: No module named 'autoshorts.review.render'
```

3. Minimal implementiert.
4. Tests grün.
5. README aktualisiert.
6. Commit erstellt.

## Tests

Aktueller Stand:

```text
41 passed in 0.08s
```

## Neuer Commit

Branch:

```text
feat/core-approval-quality-gates
```

Commit:

```text
b7b5270 feat: add review batch renderer
```

Aktueller Verlauf:

```text
b7b5270 feat: add review batch renderer
2155728 feat: add review workflow buckets
deea24f feat: add candidate factory
ae6a4f6 feat: add content pillar registry
49d6daf feat: add idea candidates and testbatches
55b17fa feat: add telegram review package
3555ec2 feat: add approval parser and quality gate
7642749 Initial commit
```

## Pipeline jetzt

Wir haben jetzt die erste durchgängige interne Review-Strecke:

```text
Content Pillar
→ CandidateDraft
→ Candidate Factory
→ VideoCandidate
→ TestBatch
→ Review Workflow
   → ready_for_review
   → blocked
   → invalid
→ Review Batch Renderer
→ Telegram-safe Review Text
→ Approval Parser
```

## Nächster sinnvoller Schritt

Als Nächstes würde ich bauen:

```text
autoshorts/cli/demo_review_batch.py
```

oder besser als testbarer Core:

```text
autoshorts/demo/sample_batch.py
```

Ziel:

- sample AI-Life-Systems Batch erzeugen
- 1 ready Candidate
- 1 blocked Candidate
- 1 invalid Candidate
- ReviewBatch bauen
- Telegram-Text rendern

Dann können wir mit einem einzigen Command einen echten Review-Output lokal erzeugen und später direkt an Telegram hängen.