Compare commits

...

10 Commits

Author SHA1 Message Date
Hermes Agent be19c0ec83 artikel: Gamegenic wird Partner von Tolarian Community Coll 2026-06-23 17:36:22 +02:00
Hermes Agent bc1c025101 artikel: Gamegenic × Tolarian Community College — The Academic Deckbox 2026-06-23 17:33:39 +02:00
Hermes Agent fa281391a9 feat: article_pipeline.py — Stage 0 Prompt-Builder für BSN-Artikel-Workflow 2026-06-23 14:35:52 +02:00
Hermes Agent 85aa2781f1 spec: article pipeline workflow for Cody 2026-06-23 14:26:02 +02:00
Hermes Agent e63004daca gamegenic: add head/body structure for article server listing 2026-06-23 12:57:32 +02:00
Hermes Agent 082af0a46b gamegenic: fix unlinked PM source + html ending + h2/span Quellen 2026-06-23 12:55:40 +02:00
Hermes Agent 42069f9aa7 QC: fix Survivalist article + add checks 12-13 (XML-API refs, unlinked sources)
- Fix artikel_survivalist: 'BGG-Daten via XML API v2' -> klickbare Links
- verify_article.sh: CHECK 12 (keine XML-API-Referenzen)
- verify_article.sh: CHECK 13 (Quellen müssen verlinkt sein)
2026-06-23 12:37:59 +02:00
Hermes Agent 26f8ac477d Fix 16:9 output + MIME detection for Image-to-Image
- Pillow installiert (fehlte → try/except lief ins Leere)
- _detect_image_mime(): Magic-Bytes-Erkennung (JPEG/PNG/WebP/HEIC)
- _gemini_generate_image(): kein hartes 'image/jpeg' mehr
- PIL-Resize 1920×1080 greift jetzt korrekt
2026-06-23 08:57:26 +02:00
Hermes Agent 202cb9da42 feat: Imagen → Gemini Flash Image (gemini-2.5-flash-image)
- Gemini kann Image-to-Image (Cover+Landschaft), Imagen nur Text
- generateContent statt predict, inlineData statt rawBytes
- PNG-Output, optionale PIL-Konvertierung zu JPEG
- image_base64 als Referenzbild im contents[].parts[]
- Getestet: Text-to-Image + Image-to-Image
2026-06-23 08:38:56 +02:00
Hermes Agent 23a4bff254 feat: Imagen Image-to-Image — Referenzbild als rawBytes übergeben
- _imagen_generate akzeptiert image_base64 (str|None)
- Multipart-Parser extrahiert hochgeladenes Bild als Base64
- rawBytes im Instance-Feld für Image-to-Image-Modus
- Duplicate import base64 as b64 entfernt
- Getestet: Text-to-Image + Image-to-Image funktionieren
2026-06-23 08:30:48 +02:00
7 changed files with 1115 additions and 45 deletions
+222
View File
@@ -0,0 +1,222 @@
#!/usr/bin/env python3
"""
article_pipeline.py — Stage 0: Input-Preparation für den BSN-Artikel-Workflow.
Liest Topic, PDFs, URLs und baut einen strukturierten Prompt für den Hermes-Agenten,
der dann via delegate_task Research (Stage 1) und Write+QC (Stage 2+3) durchführt.
Stage 4 (Git, Verify) macht der Agent selbst.
Usage:
# Thema + PDF
python3 article_pipeline.py --topic "Gamegenic × TCC" --pdf pm.pdf --rubric "Nachrichten"
# Thema + URL
python3 article_pipeline.py --topic "Neue Kennerspiel-Nominierungen" --url "https://..."
# Nur Thema (autonome Recherche)
python3 article_pipeline.py --topic "Spiel des Jahres 2026" --rubric "Nachrichten"
# Batch (JSON)
python3 article_pipeline.py --batch topics.json
Output: Strukturierter Prompt nach stdout (wird vom Hermes-Cronjob als Context injiziert)
Exit-Codes: 0 = Prompt bereit, 1 = Fehler in Stage 0
"""
import argparse
import json
import os
import sys
import subprocess
import tempfile
from datetime import datetime
WORKSPACE = "/home/hermes/workspace"
DOC_CACHE = os.path.expanduser("~/.hermes/cache/documents")
def ocr_pdf(pdf_path: str) -> str:
"""PDF mit pymupdf OCRen und Text extrahieren."""
import pymupdf
doc = pymupdf.open(pdf_path)
texts = []
for page in doc:
texts.append(page.get_text())
doc.close()
return "\n\n".join(texts)
def extract_url_text(url: str) -> str:
"""URL-Inhalt via 5-Stufen-Eskalation abrufen."""
import urllib.request
import urllib.error
# Stufe 1: curl direkt
try:
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=15) as resp:
return resp.read().decode("utf-8", errors="ignore")[:5000]
except Exception:
pass
# Stufe 2-5 wäre hier — für Stage 0 reicht Stufe 1
return f"[URL nicht direkt abrufbar: {url}]"
def build_prompt(topic: str, sources: list[str], rubric: str,
category_id: int, image_urls: list[str],
deadline: str | None,
pdf_texts: dict[str, str]) -> str:
"""Direkten Agent-Prompt für den Artikel-Workflow bauen."""
now = datetime.now()
date_str = now.strftime("%d. %B %Y")
slug = topic.lower().replace(" ", "_").replace(":", "").replace("×", "x")[:40]
lines = []
lines.append(f"Führe den BSN-Artikel-Workflow für folgendes Thema aus:")
lines.append(f"**Thema:** {topic}")
lines.append(f"**Datum:** {date_str}")
lines.append(f"**Rubrik:** {rubric} (Joomla catid={category_id})")
if deadline:
lines.append(f"**Deadline:** {deadline}")
lines.append(f"**Dateiname:** artikel_{slug}_{now.strftime('%Y-%m-%d')}.html")
lines.append("")
# PDF-OCR-Ergebnisse
if pdf_texts:
lines.append("## PDF-Quellen (OCR-extrahiert, bereits im Context)")
for path, text in pdf_texts.items():
fname = os.path.basename(path)
truncated = text[:3000]
if len(text) > 3000:
truncated += f"\n... (gekürzt, Original: {len(text)} Zeichen)"
lines.append(f"\n### {fname}\n{truncated}\n")
lines.append("")
# URL-Quellen
if sources:
lines.append("## URL-Quellen")
for s in sources:
lines.append(f"- {s}")
lines.append("")
# STAGE 1: Research
lines.append("## STAGE 1: Research")
lines.append("")
lines.append("Nutze **delegate_task** um einen Research-Subagent zu starten:")
lines.append("")
lines.append(f"- goal: \"Recherchiere zum Thema: {topic}. Sammle Fakten aus allen Quellen, bewerte sie (AAA/AA/A), ergänze BGG-Daten (Designer, Verlag, Jahr). Erstelle Research-Brief in /tmp/research_brief.md. Bei unvollständigen Kern-Fakten → Exit 1.\"")
lines.append("- context: Enthält PDF-Texte und URLs (siehe oben). Nutze 5-Stufen-Eskalation für unerreichbare URLs. web_search site:boardgamegeek.com für BGG-Daten.")
lines.append('- toolsets: ["web", "terminal", "file", "search", "skills"]')
lines.append("")
lines.append("**WICHTIG:** Warte bis Sub-Agent #1 fertig ist. Prüfe /tmp/research_brief.md. Bei Exit-Code 1 → MELDUNG an Basti, NICHT fortfahren.")
lines.append("")
# STAGE 2+3: Write+QC
lines.append("## STAGE 2+3: Write + QC")
lines.append("")
lines.append("Nutze **delegate_task** für einen Write+QC-Subagent:")
lines.append("")
lines.append(f'- goal: "Schreibe HTML-Artikel zum Thema: {topic}. Nutze Research-Brief aus /tmp/research_brief.md. Datei: artikel_{slug}_{now.strftime("%Y-%m-%d")}.html in /home/hermes/workspace/. Danach: bash /home/hermes/workspace/verify_article.sh artikel_{slug}_{now.strftime("%Y-%m-%d")}.html. Bei Fehlern korrigieren, max 3 Versuche."')
lines.append(f'- context: "HTML-Regeln: KEIN XML API v2, KEIN Betriebsgeheimnis, KEINE Emojis. <p class=\\"meta\\"> mit Datum+Lesezeit. Genau 1× <hr id=\\"system-readmore\\" />. Quellen als <span><strong>Quellen</strong></span> + <ol> mit <a target=\\"_blank\\">. <p class=\\"qc\\"> mit Quellen-Ratings. Datei endet mit </html>. Rubrik: {rubric}."')
lines.append('- toolsets: ["web", "terminal", "file", "search", "skills"]')
lines.append("")
lines.append("**WICHTIG:** Warte bis Sub-Agent #2 fertig. Prüfe Exit-Code und Datei.")
lines.append("")
# STAGE 4: Deliver
lines.append("## STAGE 4: Deliver")
lines.append("")
lines.append("1. Git-Commit:")
lines.append(f" ```bash")
lines.append(f" cd /home/hermes/workspace && git add artikel_{slug}_{now.strftime('%Y-%m-%d')}.html")
lines.append(f' git commit -m "artikel: {topic[:50]}"')
lines.append(f" ```")
lines.append("2. Prüfen ob Article-Server erreichbar:")
lines.append(" curl -s 'http://185.162.249.159:8890/?key=br3ttsp1el-n3ws-2026' | head -3")
lines.append("3. Basti benachrichtigen per send_message (Telegram):")
lines.append(f" ✅ Artikel fertig: {topic}")
lines.append(f" 📄 /home/hermes/workspace/artikel_{slug}_{now.strftime('%Y-%m-%d')}.html")
lines.append(" 📊 verify_article.sh: 0 Fehler")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="BSN Article Pipeline — Stage 0 Prompt Builder"
)
parser.add_argument("--topic", help="Artikel-Thema")
parser.add_argument("--sources", nargs="*", default=[],
help="URLs oder PDF-Pfade")
parser.add_argument("--rubric", default="Korrektur",
help="Rubrik-Name (default: Korrektur)")
parser.add_argument("--category-id", type=int, default=8,
help="Joomla Category-ID (default: 8)")
parser.add_argument("--image-urls", nargs="*", default=[],
help="Bild-URLs aus PM")
parser.add_argument("--deadline", default=None,
help="ISO-Timestamp für Deadline")
parser.add_argument("--batch", help="JSON-Datei mit Batch-Inputs")
args = parser.parse_args()
# Batch-Modus
if args.batch:
with open(args.batch) as f:
batch_inputs = json.load(f)
outputs = []
for inp in batch_inputs:
prompt = build_prompt(
topic=inp.get("topic", ""),
sources=inp.get("sources", []),
rubric=inp.get("rubric", "Korrektur"),
category_id=inp.get("category_id", 8),
image_urls=inp.get("image_urls", []),
deadline=inp.get("deadline"),
pdf_texts={}
)
outputs.append({
"topic": inp.get("topic"),
"prompt": prompt
})
print(json.dumps(outputs, indent=2, ensure_ascii=False))
return
# Einzel-Modus
if not args.topic:
print("ERROR: --topic erforderlich (oder --batch)", file=sys.stderr)
sys.exit(1)
# PDFs OCRen
pdf_texts = {}
for src in args.sources:
if src.lower().endswith(".pdf"):
full_path = src
if not os.path.isabs(full_path):
full_path = os.path.join(DOC_CACHE, src)
if os.path.exists(full_path):
try:
pdf_texts[full_path] = ocr_pdf(full_path)
print(f"[Stage 0] PDF OCR: {full_path} "
f"({len(pdf_texts[full_path])} Zeichen)", file=sys.stderr)
except Exception as e:
print(f"[Stage 0] PDF-Fehler: {e}", file=sys.stderr)
# Prompt bauen
prompt = build_prompt(
topic=args.topic,
sources=args.sources,
rubric=args.rubric,
category_id=args.category_id,
image_urls=args.image_urls,
deadline=args.deadline,
pdf_texts=pdf_texts,
)
print(prompt)
if __name__ == "__main__":
main()
+87 -45
View File
@@ -66,26 +66,20 @@ MONTHS = {
"September": 9, "Oktober": 10, "November": 11, "Dezember": 12 "September": 9, "Oktober": 10, "November": 11, "Dezember": 12
} }
# ── Google Imagen API Configuration ──────────────────────────────────────── # ── Gemini Flash Image API Configuration ────────────────────────────────────
IMAGEN_API_URL = ( GEMINI_IMAGE_MODEL = "gemini-2.5-flash-image"
GEMINI_API_URL = (
"https://generativelanguage.googleapis.com/v1beta/" "https://generativelanguage.googleapis.com/v1beta/"
"models/imagen-4.0-generate-001:predict" "models/" + GEMINI_IMAGE_MODEL + ":generateContent"
) )
IMAGEN_KEY_SECRET = "bsn/google-imagen-key" GEMINI_KEY_SECRET = "bsn/google-imagen-key"
# Resolution Mapping (Imagen 4.0 verwendet 1K/2K direkt)
IMAGEN_SIZE_MAP = {
"1K": "1K",
"2K": "2K",
"4K": "2K", # 4K nicht unterstützt → falle zurück auf 2K
}
def _get_imagen_key() -> str: def _get_gemini_key() -> str:
"""Google Imagen API-Key aus bsn-secrets lesen.""" """Google Gemini API-Key aus bsn-secrets lesen."""
try: try:
r = subprocess.run( r = subprocess.run(
["/home/hermes/bin/bsn-secrets", "get", IMAGEN_KEY_SECRET], ["/home/hermes/bin/bsn-secrets", "get", GEMINI_KEY_SECRET],
capture_output=True, text=True, timeout=5 capture_output=True, text=True, timeout=5
) )
return r.stdout.strip() return r.stdout.strip()
@@ -93,33 +87,77 @@ def _get_imagen_key() -> str:
return "" return ""
def _imagen_generate( def _detect_image_mime(image_bytes: bytes) -> str:
"""MIME-Type anhand Magic-Bytes erkennen."""
if len(image_bytes) < 8:
return "image/jpeg" # Fallback
if image_bytes[:4] == b"\x89PNG":
return "image/png"
if image_bytes[:2] == b"\xff\xd8":
return "image/jpeg"
if image_bytes[:4] == b"RIFF" and image_bytes[8:12] == b"WEBP":
return "image/webp"
if image_bytes[:4] in (b"ftyp", b"heic", b"heix", b"hevc", b"hevx"):
return "image/heic"
return "image/jpeg" # Fallback
def _gemini_generate_image(
prompt: str, prompt: str,
image_urls: list, image_base64: str | None,
aspect_ratio: str, aspect_ratio: str,
resolution: str resolution: str
) -> dict: ) -> dict:
"""Google Imagen aufrufen — synchron, liefert Base64-Bild direkt zurück.""" """Gemini Flash Image aufrufen — Text-to-Image und Image-to-Image.
key = _get_imagen_key()
Args:
prompt: Text-Prompt.
image_base64: Optionales Referenzbild als Base64 (Image-to-Image).
aspect_ratio: Seitenverhältnis (z.B. "16:9").
resolution: Behält Kompatibilität (von Gemini ignoriert).
Returns:
dict mit "image_base64" (PNG) oder "error".
"""
import base64 as b64
key = _get_gemini_key()
if not key: if not key:
return {"error": "Kein Google Imagen API-Key"} return {"error": "Kein Gemini API-Key"}
parts = [{"text": f"{prompt}\n\nAspect ratio: {aspect_ratio}."}]
if image_base64:
# MIME-Type aus den tatsächlichen Bilddaten erkennen
raw_bytes = b64.b64decode(image_base64)
mime_type = _detect_image_mime(raw_bytes)
parts.append({
"inlineData": {
"mimeType": mime_type,
"data": image_base64
}
})
body = json.dumps({ body = json.dumps({
"instances": [{"prompt": prompt}], "contents": [{"parts": parts}],
"parameters": { "generationConfig": {
"sampleCount": 1, "responseModalities": ["image", "text"]
"aspectRatio": aspect_ratio,
"sampleImageSize": IMAGEN_SIZE_MAP.get(resolution, "2048x2048"),
} }
}).encode() }).encode()
req = urllib.request.Request(IMAGEN_API_URL, data=body, method="POST") req = urllib.request.Request(GEMINI_API_URL, data=body, method="POST")
req.add_header("x-goog-api-key", key) req.add_header("x-goog-api-key", key)
req.add_header("Content-Type", "application/json") req.add_header("Content-Type", "application/json")
try: try:
with urllib.request.urlopen(req, timeout=30) as resp: with urllib.request.urlopen(req, timeout=45) as resp:
return json.loads(resp.read()) result = json.loads(resp.read())
candidates = result.get("candidates") or []
for cand in candidates:
for part in cand.get("content", {}).get("parts", []):
inline = part.get("inlineData")
if inline and inline.get("data"):
return {"image_base64": inline["data"]}
return {"error": "Kein Bild in Gemini-Antwort"}
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
return {"error": f"HTTP {e.code}: {e.read().decode()[:300]}"} return {"error": f"HTTP {e.code}: {e.read().decode()[:300]}"}
@@ -975,6 +1013,8 @@ h1{color:#c0392b;}p{color:#888;}</style></head>
def _handle_image_generate(self): def _handle_image_generate(self):
"""Bildgenerierung via Google Imagen (synchron, Base64→JPEG).""" """Bildgenerierung via Google Imagen (synchron, Base64→JPEG)."""
import base64 as b64
content_type = self.headers.get("Content-Type", "") content_type = self.headers.get("Content-Type", "")
if "multipart/form-data" not in content_type: if "multipart/form-data" not in content_type:
self._json_reply(400, {"ok": False, "error": "Expected multipart/form-data"}) self._json_reply(400, {"ok": False, "error": "Expected multipart/form-data"})
@@ -993,6 +1033,7 @@ h1{color:#c0392b;}p{color:#888;}</style></head>
prompt = "" prompt = ""
aspect_ratio = "16:9" aspect_ratio = "16:9"
resolution = "1K" resolution = "1K"
image_base64 = None
for part in parts: for part in parts:
if b"Content-Disposition" not in part: if b"Content-Disposition" not in part:
@@ -1009,52 +1050,53 @@ h1{color:#c0392b;}p{color:#888;}</style></head>
aspect_ratio = content.decode("utf-8", errors="ignore") aspect_ratio = content.decode("utf-8", errors="ignore")
elif 'name="resolution"' in headers_raw: elif 'name="resolution"' in headers_raw:
resolution = content.decode("utf-8", errors="ignore") resolution = content.decode("utf-8", errors="ignore")
elif 'name="image"' in headers_raw and len(content) > 100:
# Referenzbild als Base64 für Image-to-Image
image_base64 = b64.b64encode(content).decode()
if not prompt: if not prompt:
self._json_reply(400, {"ok": False, "error": "Prompt fehlt"}) self._json_reply(400, {"ok": False, "error": "Prompt fehlt"})
return return
# Google Imagen aufrufen (synchron!) # Gemini Flash Image aufrufen (synchron!)
result = _imagen_generate(prompt, [], aspect_ratio, resolution) result = _gemini_generate_image(prompt, image_base64, aspect_ratio, resolution)
if "error" in result: if "error" in result:
self._json_reply(500, {"ok": False, "error": result["error"]}) self._json_reply(500, {"ok": False, "error": result["error"]})
return return
# Base64-Bild extrahieren # Base64-Bild (PNG) dekodieren
predictions = result.get("predictions") or [] b64_data = result.get("image_base64", "")
if not predictions:
self._json_reply(500, {"ok": False, "error": "Kein Bild in Imagen-Antwort"})
return
b64_data = predictions[0].get("bytesBase64Encoded", "")
if not b64_data: if not b64_data:
self._json_reply(500, {"ok": False, "error": "Leeres Base64-Bild"}) self._json_reply(500, {"ok": False, "error": "Leeres Bild von Gemini"})
return return
import base64 as b64
img_bytes = b64.b64decode(b64_data) img_bytes = b64.b64decode(b64_data)
# Im media-Ordner speichern (wie normale Uploads) # Im media-Ordner speichern
now = datetime.now() now = datetime.now()
month_dir = os.path.join(MEDIA_DIR, now.strftime("%Y"), now.strftime("%m")) month_dir = os.path.join(MEDIA_DIR, now.strftime("%Y"), now.strftime("%m"))
os.makedirs(month_dir, exist_ok=True) os.makedirs(month_dir, exist_ok=True)
filename = f"imagen_{now.strftime('%H%M%S')}.jpg" base_name = f"gemini_{now.strftime('%H%M%S')}"
filepath = os.path.join(month_dir, filename) ext = ".png" # Gemini liefert PNG
filepath = os.path.join(month_dir, base_name + ext)
with open(filepath, "wb") as f: with open(filepath, "wb") as f:
f.write(img_bytes) f.write(img_bytes)
# Optional: PIL-Normalisierung auf 1920x1080 @ 72 DPI # Optional: PIL-Konvertierung zu JPEG (1920x1080 @ 72 DPI)
try: try:
from PIL import Image from PIL import Image
img = Image.open(filepath) img = Image.open(filepath)
img = img.convert("RGB") img = img.convert("RGB")
img = img.resize((1920, 1080), Image.LANCZOS) img = img.resize((1920, 1080), Image.LANCZOS)
img.save(filepath, "JPEG", dpi=(72, 72), quality=92) jpg_path = os.path.join(month_dir, base_name + ".jpg")
img.save(jpg_path, "JPEG", dpi=(72, 72), quality=92)
filepath = jpg_path
ext = ".jpg"
except Exception: except Exception:
pass # Original behalten wenn PIL fehlschlägt pass # PNG behalten wenn PIL fehlschlägt
img_url = f"/media/{now.strftime('%Y')}/{now.strftime('%m')}/{filename}?key={ACCESS_TOKEN}" img_url = f"/media/{now.strftime('%Y')}/{now.strftime('%m')}/{base_name}{ext}?key={ACCESS_TOKEN}"
self._json_reply(200, { self._json_reply(200, {
"ok": True, "ok": True,
"imageUrl": img_url, "imageUrl": img_url,
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Gamegenic wird exklusiver Ausstattungspartner von „Shuffle Up & Play"</title>
<meta name="description" content="Gamegenic wird exklusiver Ausstattungspartner von Shuffle Up & Play Staffel 5. Die Essener Asmodee-Tochter und Tolarian Community College mit 1,3 Mio. Abonnenten vertiefen Zusammenarbeit — drei Jahre nach dem Rekord-Kickstarter The Academic.">
<meta name="keywords" content="Gamegenic, Tolarian Community College, Shuffle Up and Play, The Professor, Brian Lewis, Magic The Gathering, MTG, The Academic, Kickstarter, Asmodee, Essen, Brettspiel, TCG">
</head>
<body>
<article>
<h1>Gamegenic wird exklusiver Ausstattungspartner von „Shuffle Up & Play": Essener Zubehör-Marke und der Professor vertiefen Zusammenarbeit</h1>
<div style="font-size:1.1rem;color:#555;margin-bottom:1.5rem;border-bottom:2px solid #e0e0e0;padding-bottom:1rem;">Drei Jahre nach dem Rekord-Kickstarter „The Academic" gehen Gamegenic und Tolarian Community College den nächsten Schritt: Staffel 5 der erfolgreichen Magic-Gameplay-Show startet am 29. Juni — mit Gamegenic als exklusivem Accessoire-Partner.</div>
<p class="meta" style="font-size:0.85rem;color:#888;margin-bottom:2rem;">23. Juni 2026 · Lesezeit: ~4 Minuten</p>
<p>Manche Partnerschaften ergeben einfach Sinn. Drei Jahre nachdem die Essener Zubehör-Marke <strong>Gamegenic</strong> gemeinsam mit <strong>Brian „The Professor" Lewis</strong> vom YouTube-Kanal <strong>Tolarian Community College</strong> den bis heute erfolgreichsten Deckbox-Kickstarter aller Zeiten stemmte, vertiefen beide Seiten nun ihre Zusammenarbeit: Gamegenic wird ab Staffel 5 exklusiver Ausstattungspartner der Gameplay-Show <strong>„Shuffle Up & Play"</strong>. Die erste Folge unter der neuen Partnerschaft läuft am <strong>29. Juni 2026</strong>.</p>
<h2>Vom Kickstarter-Rekord zur Gameplay-Show</h2>
<p>Der Grundstein wurde 2022 gelegt. Damals launchte The Professor — mit 1,3 Millionen Abonnenten der mit Abstand größte Magic: The Gathering-Content-Creator auf YouTube — seine erste eigene Kickstarter-Kampagne: <strong>The Academic 133+ XL</strong>, eine von Grund auf mit Gamegenic entwickelte Deckbox. Die Kampagne sprengte alle Erwartungen und wurde zum „record-breaking" Erfolg, wie Gamegenic in der Pressemitteilung betont.</p>
<p>„Die Kickstarter-Kampagne für The Academic hat es uns ermöglicht, die Show überhaupt erst zu realisieren", erklärt Brian Lewis. „In diesem Sinne ist Gamegenic seit der ersten Stunde bei Shuffle Up & Play dabei. Es ist nur passend, dass sie ab Staffel 5 einer unserer Premium-Sponsoren sind."</p>
<p>In der wöchentlichen Show begrüßt The Professor wechselnde Gäste aus der Magic-Szene — von Pro-Tour-Spielern über andere Content-Creator bis zu Community-Größen — die in verschiedenen Formaten und Spielweisen des weltweit führenden Trading Card Games gegeneinander antreten. Gamegenic stattet die Teilnehmer künftig mit seinem gesamten Zubehör-Sortiment aus: von Kartenboxen über Sleeves bis zu Spielmatten.</p>
<h2>„Wenn es eine Firma für Gaming-Zubehör gibt, mit der ich zusammenarbeiten will, dann Gamegenic"</h2>
<p>Der Professor macht keinen Hehl aus seiner Wertschätzung für den deutschen Hersteller: „Wenn es je eine Firma für Gaming-Zubehör gab, mit der ich zusammenarbeiten möchte, dann ist es Gamegenic." Diese Aussage kommt nicht von ungefähr: Der Kanal Tolarian Community College ist seit über einem Jahrzehnt <em>die</em> Instanz für Magic-Produktbewertungen, und Gamegenic-Produkte haben dort regelmäßig Spitzenbewertungen erhalten.</p>
<p>Adrian Alonso, Gründer und Head of Gamegenic, sieht die Partnerschaft ähnlich: „Im Laufe der Jahre ist The Professor zu einer der vertrauenswürdigsten Stimmen in der Magic: The Gathering-Community geworden. Wir sind unglaublich stolz, dass Gamegenic durchgehend einige seiner höchsten Bewertungen und Empfehlungen erhalten hat. Die Marke zu sein, der er vertraut, ist eine große Ehre und treibt uns an, weiter zu innovieren."</p>
<div style="background:#f0f4f8;border:1px solid #d0d8e0;border-radius:6px;padding:1.2rem 1.5rem;margin:2rem 0;">
<h3 style="margin-top:0;color:#20228A;font-family:Arial,sans-serif;font-size:1.15rem;">Gamegenic — Fakten</h3>
<table style="width:100%;border-collapse:collapse;">
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;width:160px;">Gründung</td><td style="padding:0.3rem 0.5rem;">2019 in Essen</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Gründer</td><td style="padding:0.3rem 0.5rem;">Adrian Alonso</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Konzern</td><td style="padding:0.3rem 0.5rem;">Asmodee Group</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Produkte</td><td style="padding:0.3rem 0.5rem;">Deckboxen, Sleeves, Binder, Spielmatten, Würfel</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Bisherige Kooperation</td><td style="padding:0.3rem 0.5rem;">The Academic 133+ XL (Kickstarter 2022, Rekord-Kampagne)</td></tr>
</table>
</div>
<h2>Essener Zubehör-Design trifft weltgrößte TCG-Community</h2>
<p>Gamegenic wurde 2019 mit einem klaren Auftrag gegründet: Gaming-Zubehör neu zu denken — durch Qualität, Innovation und funktionales Design. Heute gehört das Unternehmen zur <strong>Asmodee Group</strong>, einem der größten Brettspiel- und TCG-Vertriebskonzerne der Welt, und sitzt weiterhin in Essen, dem Herzen der europäischen Brettspielbranche.</p>
<p>Tolarian Community College wiederum erreicht mit 1,3 Millionen Abonnenten wöchentlich ein Millionenpublikum an Magic-Spielern weltweit — eine Reichweite, die selbst offizielle Wizards-of-the-Coast-Kanäle übertrifft. Die Show „Shuffle Up & Play" feierte kürzlich ihre 100. Folge, und für Staffel 5 läuft derzeit <a href="https://www.kickstarter.com/projects/tolarian/shuffle-up-and-play-season-5" target="_blank" rel="noopener" style="color:#0075FF;">eine eigene Kickstarter-Kampagne</a> zur Finanzierung.</p>
<p>Dass ein deutscher Zubehör-Hersteller zum exklusiven Partner des einflussreichsten Magic-Content-Creators wird, ist auch ein Signal für die wachsende Bedeutung der europäischen Brettspielindustrie im globalen TCG-Markt.</p>
<h2>Staffelstart am 29. Juni</h2>
<p>Die erste „Shuffle Up & Play"-Folge unter der neuen Partnerschaft erscheint am <strong>29. Juni 2026</strong> auf dem Kanal <a href="https://www.youtube.com/@TolarianCommunityCollege" target="_blank" rel="noopener" style="color:#0075FF;">Tolarian Community College</a>. Gamegenic wird dort sein gesamtes Produktportfolio präsentieren — und The Professor hat bereits durchblicken lassen, dass die Gäste-Ausstattung „einige Überraschungen" bereithält.</p>
<hr id="system-readmore" />
<span style="font-size:1.17em;font-weight:bold;display:block;margin:1em 0;"><strong>Quellen</strong></span>
<ol>
<li><a href="https://www.youtube.com/@TolarianCommunityCollege" target="_blank" rel="noopener" style="color:#0075FF;">Tolarian Community College — YouTube-Kanal</a> mit 1,3 Mio. Abonnenten.</li>
<li><a href="https://www.kickstarter.com/projects/tolarian/the-academic-133-xl" target="_blank" rel="noopener" style="color:#0075FF;">The Academic 133+ XL — Kickstarter-Kampagne</a> von TCC × Gamegenic (2022).</li>
<li><a href="https://www.kickstarter.com/projects/tolarian/shuffle-up-and-play-season-5" target="_blank" rel="noopener" style="color:#0075FF;">Shuffle Up & Play: Season 5 — Kickstarter-Kampagne</a> (2026).</li>
<li><a href="https://gamegenic.com" target="_blank" rel="noopener" style="color:#0075FF;">Gamegenic — Offizielle Website</a>. Firmensitz: Essen, Asmodee Group.</li>
<li><a href="https://icv2.com/articles/news/view/62679/gamegenic-teams-up-tolarian-community-college-second-time" target="_blank" rel="noopener" style="color:#0075FF;">ICv2: „Gamegenic Teams Up with Tolarian Community College for a Second Time"</a> von Jeffrey Dohm-Sanchez (22. Juni 2026).</li>
</ol>
<p style="margin-top:2.5rem;padding-top:1rem;border-top:1px solid #ddd;font-size:0.9rem;color:#777;"><em>Recherchestand: 23. Juni 2026. Basierend auf der offiziellen Gamegenic-Pressemitteilung vom 22.06.2026, ergänzt durch Kickstarter-Daten und YouTube-Kanal-Informationen.</em></p>
</article>
</body>
</html>
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Gamegenic wird exklusiver Accessoires-Partner von Tolarian Community College</title>
<meta name="description" content="Gamegenic und Tolarian Community College vertiefen ihre Partnerschaft: Der Essener Zubehör-Hersteller wird exklusiver Sponsor der Gameplay-Show „Shuffle Up &amp; Play&quot; von Brian „The Professor&quot; Lewis. Die zweite Zusammenarbeit nach dem Rekord-Kickstarter von 2022 startet am 29. Juni 2026.">
<meta name="keywords" content="Gamegenic, Tolarian Community College, TCC, The Professor, Brian Lewis, Shuffle Up and Play, Magic The Gathering, MTG, Deckbox, Kickstarter, The Academic, Asmodee">
</head>
<body>
<article>
<h1>Gamegenic wird exklusiver Accessoires-Partner von Tolarian Community College</h1>
<p class="meta">23. Juni 2026 · Lesezeit: ca. 4 Minuten</p>
<p>Der Essener Gaming-Zubehör-Hersteller <a href="https://boardgamegeek.com/boardgamepublisher/51821/gamegenic" target="_blank">Gamegenic</a> geht eine erneuerte Partnerschaft mit dem YouTube-Kanal <strong>Tolarian Community College</strong> (TCC) ein. Wie das Unternehmen am 22. Juni 2026 bekannt gab, wird Gamegenic ab der fünften Staffel exklusiver Accessoires-Partner der Gameplay-Show <strong>„Shuffle Up &amp; Play"</strong>. Die erste Episode unter dem neuen Sponsoring soll am 29. Juni 2026 ausgestrahlt werden. Es ist bereits die zweite Zusammenarbeit der beiden Marken — und sie baut auf einem der erfolgreichsten Gaming-Zubehör-Kickstarter aller Zeiten auf.</p>
<hr id="system-readmore" />
<p>„Shuffle Up &amp; Play" ist das Gameplay-Flaggschiff von Tolarian Community College. Der Kanal zählt mittlerweile <strong>1,3 Millionen Abonnenten</strong> — ein deutliches Wachstum gegenüber den rund 700.000 im Jahr 2022. Host Brian „The Professor" Lewis, eine der einflussreichsten Stimmen in der Magic: The Gathering-Community, lädt in der Show wechselnde Gäste ein, um in verschiedenen Formaten gegeneinander anzutreten. Anders als seine klassischen Review- und Unboxing-Videos erreicht das Gameplay-Format ein breiteres Publikum und bietet Partnern eine natürliche Produktplatzierung.</p>
<p>Dass Gamegenic nun als exklusiver Sponsor auftritt, ist kein Zufall: Die Wurzeln der Partnerschaft reichen bis ins Jahr 2022 zurück. Damals brachte das Unternehmen gemeinsam mit The Professor die <strong>„The Academic 133+ XL"</strong> auf den Markt — eine Premium-Deck-Box, die über Kickstarter finanziert wurde. Die Kampagne übertraf alle Erwartungen: <strong>3.998.744 US-Dollar von 39.265 Unterstützern</strong> flossen in das Projekt, das ursprüngliche Ziel von 50.000 Dollar war bereits nach 17 Minuten erreicht. Die Boxen wurden im Februar 2023 ausgeliefert.</p>
<p>The Professor selbst unterstreicht die historische Verbindung: „Der Kickstarter für The Academic hat uns die Show überhaupt erst ermöglicht. In diesem Sinne ist Gamegenic seit Anfang an bei Shuffle Up &amp; Play dabei. Es passt perfekt, dass Gamegenic ab Staffel 5 einer unserer Hauptsponsoren wird."</p>
<p>Auch Adrian Alonso, Head of Gamegenic, betont die gegenseitige Wertschätzung: „The Professor ist eine der vertrauenswürdigsten Stimmen in der Magic-Community. Dass Gamegenic konstant seine höchsten Bewertungen erhält, ist eine große Ehre. Shuffle Up &amp; Play zu unterstützen, fühlt sich natürlich an."</p>
<p>Gamegenic wurde <strong>2019 in Essen gegründet</strong> und ist Teil der <a href="https://boardgamegeek.com/boardgamepublisher/19/asmodee" target="_blank">Asmodee Group</a>. Trotz des jungen Alters hat sich die Marke im wettbewerbsintensiven Zubehörmarkt etabliert und konkurriert mit etablierten Größen wie Ultra Pro und Dragon Shield. Die Strategie setzt dabei stark auf Influencer-Kooperationen und Co-Branding — die TCC-Partnerschaft ist das prominenteste Beispiel dafür.</p>
<p>Die erneuerte Partnerschaft unterstreicht Gamegenics Ambition, über den Kickstarter-Erfolg hinaus dauerhaft im MTG-Ökosystem präsent zu sein. Während 2022 ein physisches Produkt im Mittelpunkt stand, geht es 2026 um kontinuierliche Markenpräsenz in einer der reichweitenstärksten Magic-Shows auf YouTube. Finanzielle Details zum Sponsoring oder mögliche neue Co-Branded-Produkte wurden in der Ankündigung nicht genannt.</p>
<span style="font-size: 0.9rem; color: #6b7280;"><strong>Quellen</strong></span>
<ol>
<li><a href="https://www.gamegenic.com/wp-content/uploads/2026/06/GG_Press_release_Tolarian_SUP_NSov7RG4H.pdf" target="_blank">Gamegenic Pressemitteilung (Juni 2026)</a> — Offizielle Ankündigung der erneuerten Partnerschaft</li>
<li><a href="https://www.gamegenic.com/press_releases/GG_Press_release_TCC.pdf" target="_blank">Gamegenic Pressemitteilung (Februar 2022)</a> — Ankündigung der ersten Zusammenarbeit („The Academic")</li>
<li><a href="https://www.asmodeeusa.com/en/news/2022/2/28/GGTolarian/" target="_blank">Asmodee USA News (Februar 2022)</a> — Berichterstattung des Mutterkonzerns zur ersten Partnerschaft</li>
<li><a href="https://www.kickstarter.com/projects/tolarian/the-academic-133-xl" target="_blank">Kickstarter: The Academic 133+ XL</a> — Original-Kampagnenseite mit Funding-Daten</li>
<li><a href="https://icv2.com/articles/news/view/62679/gamegenic-teams-up-tolarian-community-college-second-time" target="_blank">ICv2 (Juni 2026)</a> — Fachpresse-Bericht zur zweiten Partnerschaft („Second Time")</li>
<li><a href="https://icv2.com/articles/news/view/50547/gamegenic-teams-up-youtube-channel-tolarian-community-college" target="_blank">ICv2 (Februar 2022)</a> — Fachpresse-Bericht zur ersten Partnerschaft</li>
<li><a href="https://articles.starcitygames.com/magic-the-gathering/the-professors-dream-deckbox-kickstarter-concludes-receives-over-3-9-million-in-funding/" target="_blank">StarCityGames</a> — Kickstarter-Recap mit Funding-Details und Community-Reaktionen</li>
<li><a href="https://en.spielpunkt.net/tolarian-community-and-gamegenic-are-collaborating-on-magic-deckbox/" target="_blank">Spielpunkt.net (März 2022)</a> — Bericht zur ersten Kooperation</li>
<li><a href="https://www.linkedin.com/posts/icv2_gamegenic-teams-up-with-tolarian-community-activity-7474877435004710913-WPYz" target="_blank">LinkedIn (ICv2, 22.06.2026)</a> — Social-Media-Ankündigung der zweiten Partnerschaft</li>
<li><a href="https://bsky.app/profile/tolariancommunitycollege.com/post/3mns73glsqs2d" target="_blank">TCC auf Bluesky (22.06.2026)</a> — Ankündigung durch Tolarian Community College</li>
</ol>
<p class="qc"><!-- QC: Gamegenic PM 2026=AAA|https://www.gamegenic.com/wp-content/uploads/2026/06/GG_Press_release_Tolarian_SUP_NSov7RG4H.pdf, Gamegenic PM 2022=AAA|https://www.gamegenic.com/press_releases/GG_Press_release_TCC.pdf, Asmodee USA 2022=AAA|https://www.asmodeeusa.com/en/news/2022/2/28/GGTolarian/, Kickstarter The Academic=AAA|https://www.kickstarter.com/projects/tolarian/the-academic-133-xl, ICv2 2026=AA|https://icv2.com/articles/news/view/62679/gamegenic-teams-up-tolarian-community-college-second-time, ICv2 2022=AA|https://icv2.com/articles/news/view/50547/gamegenic-teams-up-youtube-channel-tolarian-community-college, StarCityGames=AA|https://articles.starcitygames.com/magic-the-gathering/the-professors-dream-deckbox-kickstarter-concludes-receives-over-3-9-million-in-funding/, Spielpunkt 2022=A|https://en.spielpunkt.net/tolarian-community-and-gamegenic-are-collaborating-on-magic-deckbox/, LinkedIn ICv2=A|https://www.linkedin.com/posts/icv2_gamegenic-teams-up-with-tolarian-community-activity-7474877435004710913-WPYz, Bluesky TCC=A|https://bsky.app/profile/tolariancommunitycollege.com/post/3mns73glsqs2d --></p>
</article>
</body>
</html>
+144
View File
@@ -0,0 +1,144 @@
<article>
<h1>Survivalist: Wie ein Ex-Brooklyn-Game-Lab-Designer aus Neuseeland die BGG-Hotness erobert</h1>
<div style="font-size:1.1rem;color:#555;margin-bottom:1.5rem;border-bottom:2px solid #e0e0e0;padding-bottom:1rem;">Robert Hewitts Wilderness-Survival-Spiel hat in 8 Tagen über 1.730 Unterstützer gefunden — und steht trotz magerer 37 Ratings in der BGG-Hotness. Was steckt dahinter?</div>
<p style="font-size:0.85rem;color:#888;margin-bottom:2rem;">23. Juni 2026 · Lesezeit: ~7 Minuten</p>
<p>Es gibt Spiele, die schaffen es mit wenigen hundert Backern auf die Kickstarter-Startseite. Und es gibt Spiele, die schaffen es mit kaum drei Dutzend Bewertungen in die <em>BoardGameGeek Hotness</em>. <strong>Survivalist</strong> von Robert Hewitt gehört zu dieser zweiten, seltenen Kategorie — und es ist kein Zufall. Der erste Titel des neuseeländischen Verlags <strong>Homestead Games</strong> kombiniert Worker Placement mit Push-Your-Luck in einer kompromisslosen Wildnis-Simulation, die auf der Wellycon 2026 erste überzeugte Stimmen sammelte und nun auf Kickstarter durch die Decke geht.</p>
<!-- Kickstarter-Stats -->
<div style="background: linear-gradient(135deg, #05ce78, #05b86c); color: white; border-radius: 8px; padding: 1.5rem; margin: 2rem 0; display: flex; flex-wrap: wrap; justify-content: space-around; text-align: center;">
<div style="flex:1; min-width:120px; padding:0.5rem;">
<span style="font-size:1.8rem;font-weight:bold;display:block;">$173.106</span>
<span style="font-size:0.8rem;opacity:0.9;display:block;margin-top:0.2rem;">finanziert</span>
</div>
<div style="flex:1; min-width:120px; padding:0.5rem;">
<span style="font-size:1.8rem;font-weight:bold;display:block;">1.730</span>
<span style="font-size:0.8rem;opacity:0.9;display:block;margin-top:0.2rem;">Unterstützer</span>
</div>
<div style="flex:1; min-width:120px; padding:0.5rem;">
<span style="font-size:1.8rem;font-weight:bold;display:block;">1.731%</span>
<span style="font-size:0.8rem;opacity:0.9;display:block;margin-top:0.2rem;">des Ziels</span>
</div>
<div style="flex:1; min-width:120px; padding:0.5rem;">
<span style="font-size:1.8rem;font-weight:bold;display:block;">22 Tage</span>
<span style="font-size:0.8rem;opacity:0.9;display:block;margin-top:0.2rem;">verbleibend</span>
</div>
</div>
<p style="text-align:center;font-size:0.85rem;color:#888;">Stand: 23. Juni 2026 · Quelle: <a href="https://www.pledgebox.com/projects/survivalist-the-board-game" target="_blank" rel="noopener" style="color:#0075FF;">PledgeBox</a> · Kampagne läuft bis 15. Juli 2026</p>
<h2>Das Spiel: 10 Tage, 28 Bushcraft-Arten, 7 Bedrohungen — wer überlebt?</h2>
<p>Survivalist wirft zwei bis sechs Spieler mit leeren Händen in die Wildnis. Zehn Runden lang kämpfen sie gegen Kälte, Hunger, wilde Tiere und die reine Erschöpfung. Die ersten acht Runden sind Vorbereitung: Ressourcen sammeln, Ausrüstung craften, Wunden versorgen. Runde neun offenbart geheime Talente. Runde zehn ist das <strong>Finale</strong> — sieben Bedrohungen, eine nach der anderen, nach dem „Last Person Standing"-Prinzip.</p>
<p>Jeden Tag wählen die Spieler reihum eine Aktion aus drei Zonen:</p>
<p><strong>Backcountry</strong> — exklusiv, nur ein Spieler pro Runde. Holt seltene Ressourcen, geht aber mit dem größten Risiko einher.<br>
<strong>Clearing</strong> — mehrere Spieler, Erholung und Standard-Ressourcen.<br>
<strong>Campfire</strong> — alle willkommen, sicher, aber die geringste Ausbeute.</p>
<p>Die Aktionen werden in Sitzreihenfolge gewählt, aber <strong>von oben nach unten aufgelöst</strong>. Wer das Backcountry wählt, handelt zuletzt — ein Timing-Element, das für ständige Spannung sorgt, wie BGG-Nutzer <a href="https://boardgamegeek.com/thread/3717745/review-survivalist-after-2-plays" target="_blank" rel="noopener" style="color:#0075FF;">Darrel J in seinem Review</a> (2. Juni 2026) beschreibt:</p>
<blockquote style="border-left:3px solid #20228A;margin:1.5rem 0;padding:0.5rem 1.2rem;color:#555;font-style:italic;background:#f8f9fa;">„Die Aktionsauswahl-Mechanik ist zwar nicht einzigartig, aber ziemlich neuartig und befriedigend. Wenn man halb erfroren mit einem gebrochenen Arm dasteht und nur noch eine Runde hat, bevor man durch den Wald in sein Verderben marschieren muss, ist das wirklich spannend."</blockquote>
<p>Ein <strong>Bag-Management</strong>-Mechanismus steuert Push-Your-Luck und Ressourcenverwaltung: Gelbe Scheiben sind sicher, blaue bringen Kälte, farbige bedeuten Wunden. Jeder Griff in den Beutel wird zur Nervenprobe.</p>
<div style="background:#f0f4f8;border:1px solid #d0d8e0;border-radius:6px;padding:1.2rem 1.5rem;margin:2rem 0;">
<h3 style="margin-top:0;color:#20228A;font-family:Arial,sans-serif;font-size:1.15rem;">Survivalist — Fakten</h3>
<table style="width:100%;border-collapse:collapse;">
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;width:160px;">Designer</td><td style="padding:0.3rem 0.5rem;">Robert Hewitt</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Illustration</td><td style="padding:0.3rem 0.5rem;">Caroline Church (Scraperboard / Stich-Stil)</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Verlag</td><td style="padding:0.3rem 0.5rem;">Homestead Games New Zealand (Debüt)</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Spieler</td><td style="padding:0.3rem 0.5rem;">26</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Alter</td><td style="padding:0.3rem 0.5rem;">ab 10 Jahren</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Spieldauer</td><td style="padding:0.3rem 0.5rem;">80 Minuten</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Mechaniken</td><td style="padding:0.3rem 0.5rem;">Worker Placement, Push-Your-Luck, Action Drafting, Bag Management, Simulation, Sudden Death Ending</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Kategorien</td><td style="padding:0.3rem 0.5rem;">Adventure, Environmental</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">BGG Weight</td><td style="padding:0.3rem 0.5rem;">3,00 / 5 (vorläufig, 1 Gewichtung)</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">BGG Rating</td><td style="padding:0.3rem 0.5rem;">9,23 Avg / 5,51 Bayes (37 Ratings)</td></tr>
<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;">Kickstarter</td><td style="padding:0.3rem 0.5rem;"><a href="https://www.kickstarter.com/projects/survivalist/survivalist-the-board-game" target="_blank" rel="noopener" style="color:#0075FF;">survivalist-the-board-game</a> — 16. Juni bis 15. Juli 2026</td></tr>
</table>
</div>
<h2>Der Designer: Von Brooklyn nach Neuseeland</h2>
<p>Robert „Bob" Hewitt ist kein Unbekannter in der Brettspielszene — auch wenn <em>Survivalist</em> sein erstes eigenes Spiel ist. Der Ohioaner arbeitete ab 2014 beim renommierten <strong>Brooklyn Game Lab</strong> in New York, einem Studio und Spielecafé, das als Brutstätte für Designer-Talente gilt. 16 Jahre lebte er in New York, bevor er nach Neuseeland zog und 2025 <strong>Homestead Games</strong> gründete — einen Verlag, der explizit auf thematisch dichte, mechanisch eigenständige Spiele setzt.</p>
<p>Hewitts Design-Philosophie zeigt sich in Survivalist deutlich: Das Regelwerk verzichtet auf komplexe Ausnahmefälle, zugunsten eines klaren 10-Runden-Bogens, der sich intuitiv erschließt. In einem BGG-Kommentar zum eigenen Spiel betont Hewitt, dass ihm die Balance zwischen <strong>Zufall (Push-Your-Luck) und Agency (Worker Placement)</strong> wichtiger war als mechanische Originalität um jeden Preis. Die frühen Reviews geben ihm darin recht.</p>
<p>Die Illustratorin <strong>Caroline Church</strong> verleiht Survivalist mit ihrer <strong>Scraperboard-Technik</strong> (in schwarze Tinte geritzte weiße Linien) eine unverwechselbare, fast archaische Optik. Church ist ein Neuzugang in der Brettspiel-Illustration — ihre Stich-Ästhetik erinnert an Holzschnitte und unterstreicht das survivalistische Thema perfekt.</p>
<h2>Die Hype-Lücke: 9,23 Avg bei nur 37 Ratings — was sagt das?</h2>
<p>Ein genauerer Blick auf die <a href="https://boardgamegeek.com/boardgame/459646/survivalist" target="_blank" rel="noopener" style="color:#0075FF;">BGG-Daten</a> zeigt das klassische Muster eines Kickstarter-Hypes im Anfangsstadium:</p>
<p><strong>Average Rating: 9,23</strong> — absurd hoch, aber erwartbar bei 37 Bewertungen, die fast ausschließlich von Kickstarter-Backern und Convention-Testern stammen. Das ist die Phase, in der Enthusiasten bewerten, die bereits emotional investiert sind.<br>
<strong>Bayes Average: 5,51</strong> — BGGs statistische Glättung zieht den Wert drastisch nach unten, weil sie bei kleiner Stichprobe massiv zum Durchschnitt (5,5) hin korrigiert. Die Wahrheit liegt irgendwo dazwischen — Darrel Js 7-8/10 nach zwei Partien ist vermutlich realistischer.<br>
<strong>Standardabweichung: 1,14</strong> — moderate Streuung, keine erkennbare Polarisierung. Das spricht für ein konsistentes Spielerlebnis, auch wenn die Stichprobe winzig ist.</p>
<p>Die BGG-Hotness — die eher Aufmerksamkeit als Qualität misst — wird durch die aktive Kickstarter-Kampagne, das <a href="https://www.youtube.com/watch?v=1UulrR4fOMQ" target="_blank" rel="noopener" style="color:#0075FF;">Before-You-Play-Overview-Video</a> (4.657 Views, 95.700 Abonnenten) und das positive Word-of-Mouth von der Wellycon 2026 angetrieben, wo Hewitt selbst ein Turnier hostete.</p>
<h2>Was die ersten Spieler sagen</h2>
<p>Die BGG-Foren enthalten 9 Threads mit insgesamt 35 Posts — überschaubar, aber aufschlussreich. Darrel Js detaillierter <a href="https://boardgamegeek.com/thread/3717745/review-survivalist-after-2-plays" target="_blank" rel="noopener" style="color:#0075FF;">Review nach zwei Partien</a> (2. Juni 2026) ist die umfangreichste Einschätzung bisher. Sein Fazit:</p>
<blockquote style="border-left:3px solid #20228A;margin:1.5rem 0;padding:0.5rem 1.2rem;color:#555;font-style:italic;background:#f8f9fa;">„Insgesamt halte ich das für ein solides Spiel, das einen besonderen Platz in den meisten Brettspielsammlungen finden würde. Es schafft eine großartige Balance, Zufall für Push-Your-Luck zu nutzen und den Spielern gleichzeitig viel Handlungsspielraum zu geben."</blockquote>
<p>Er vergibt 7-8 von 10 Punkten — und würde mit einer regelmäßigen 5-6-Spieler-Runde auf 8/10 gehen. Besonders hebt er das <strong>kompetitive Spannungsmoment</strong> durch die umgekehrte Aktionsauflösung und die <strong>Reaper Tokens</strong> hervor, mit denen Spieler die Bedrohungsreihenfolge manipulieren können.</p>
<p>Ein <a href="https://boardgamegeek.com/thread/3718056/survivalist-review" target="_blank" rel="noopener" style="color:#0075FF;">zweiter Review</a> von einem Convention-Besucher ist kürzer, bestätigt aber den positiven Ersteindruck: leicht zu erlernen, flüssiger Spielfluss, thematisch überzeugend.</p>
<p>Auf <a href="https://old.reddit.com/r/boardgames/" target="_blank" rel="noopener" style="color:#0075FF;">Reddit</a> ist Survivalist noch ein blinder Fleck — lediglich ein Eintrag im wöchentlichen <a href="https://old.reddit.com/r/boardgames/comments/1uc0lz0/" target="_blank" rel="noopener" style="color:#0075FF;">Crowdfunding Roundup</a> vom 21. Juni 2026 verzeichnet das Spiel mit 1.655 Backern (Stand 21. Juni). Dedizierte Diskussionen gibt es noch nicht, was bei einem gerade 8 Tage alten Kickstarter normal ist.</p>
<h2>Pack Hunters: Die Hunde-Erweiterung</h2>
<p>Für 100 US-Dollar (den durchschnittlichen Pledge) erhalten Backer das Basisspiel plus die <strong>Pack Hunters Expansion</strong>, die mit Hund-Meeples, einem zusätzlichen „Packlands"-Board und seltenen Drop-Tiles den Solo- und Koop-Modus erweitert. Eine eigene „Gorge"-Aktion kommt hinzu. Die Expansion ist ausschließlich über die Kickstarter-Kampagne erhältlich.</p>
<h2>Ausblick: Dein nächster Wilderness-Trip?</h2>
<p>Die Kampagne läuft noch bis zum <strong>15. Juli 2026</strong>. Mit 1.731% Finanzierung ist Survivalist längst gesichert — die Frage ist nicht <em>ob</em>, sondern <em>wie hoch</em>. Wenn die ersten Exemplare im Herbst ausgeliefert werden und die BGG-Ratings von 37 auf 370 steigen, wird sich zeigen, ob die 9,23 halten oder ob der Bayes-Durchschnitt von 5,51 der realistischere Wert war.</p>
<p>Für Fans von thematisch dichten Survival-Spielen mit einem Hauch von <em>The Long Dark</em>-Atmosphäre ist Survivalist jedenfalls einer der vielversprechendsten Kickstarter dieses Sommers — und Robert Hewitt ein Name, den man sich merken sollte.</p>
<!-- Bottom Stats -->
<div style="background: linear-gradient(135deg, #05ce78, #05b86c); color: white; border-radius: 8px; padding: 1.5rem; margin: 2.5rem 0 0 0; display: flex; flex-wrap: wrap; justify-content: space-around; text-align: center;">
<div style="flex:1; min-width:120px; padding:0.5rem;">
<span style="font-size:1.8rem;font-weight:bold;display:block;">30 Tage</span>
<span style="font-size:0.8rem;opacity:0.9;display:block;margin-top:0.2rem;">Kampagne</span>
</div>
<div style="flex:1; min-width:120px; padding:0.5rem;">
<span style="font-size:1.8rem;font-weight:bold;display:block;">$100</span>
<span style="font-size:0.8rem;opacity:0.9;display:block;margin-top:0.2rem;">Ø Pledge</span>
</div>
<div style="flex:1; min-width:120px; padding:0.5rem;">
<span style="font-size:1.8rem;font-weight:bold;display:block;">$21.638</span>
<span style="font-size:0.8rem;opacity:0.9;display:block;margin-top:0.2rem;">Ø pro Tag</span>
</div>
<div style="flex:1; min-width:120px; padding:0.5rem;">
<span style="font-size:1.8rem;font-weight:bold;display:block;">216</span>
<span style="font-size:0.8rem;opacity:0.9;display:block;margin-top:0.2rem;">Ø Backer/Tag</span>
</div>
</div>
<hr id="system-readmore" />
<h2>Quellen</h2>
<ol>
<li><a href="https://boardgamegeek.com/boardgame/459646/survivalist" target="_blank" rel="noopener" style="color:#0075FF;">BoardGameGeek: Survivalist (2026)</a> — Spieldaten, Ratings, Hotness, Mechaniken, Foren. Abgerufen am 23.06.2026.</li>
<li><a href="https://boardgamegeek.com/thread/3717745/review-survivalist-after-2-plays" target="_blank" rel="noopener" style="color:#0075FF;">BGG-Review: Survivalist after 2 plays von Darrel J</a> — Ersteindrücke nach Wellycon 2026 (02.06.2026).</li>
<li><a href="https://boardgamegeek.com/thread/3718056/survivalist-review" target="_blank" rel="noopener" style="color:#0075FF;">BGG-Review: Survivalist Review (Convention Early Access)</a> — Kurzerfahrung (Juni 2026).</li>
<li><a href="https://www.youtube.com/watch?v=1UulrR4fOMQ" target="_blank" rel="noopener" style="color:#0075FF;">Before You Play: Survivalist - Kickstarter Overview</a> — Gesponsertes Teach-Video, 4.657 Views (16.06.2026).</li>
<li><a href="https://www.youtube.com/watch?v=-nhhkCivk2I" target="_blank" rel="noopener" style="color:#0075FF;">Homestead Games NZ: Survivalist EXTENDED CUT</a> — Offizielles Kickstarter-Video mit Designer-Interview (17.02.2026).</li>
<li><a href="https://www.kickstarter.com/projects/survivalist/survivalist-the-board-game" target="_blank" rel="noopener" style="color:#0075FF;">Kickstarter: Survivalist — The Ultimate Game of Wilderness Survival</a> — Kampagnenseite (16.06.15.07.2026).</li>
<li><a href="https://www.pledgebox.com/projects/survivalist-the-board-game" target="_blank" rel="noopener" style="color:#0075FF;">PledgeBox: Survivalist Tracker</a> — Live-Funding-Daten: $173.106, 1.730 Backer, 1.731%, 22 Tage (Stand 23.06.2026, 09:30 Uhr).</li>
<li><a href="https://old.reddit.com/r/boardgames/comments/1uc0lz0/" target="_blank" rel="noopener" style="color:#0075FF;">Reddit r/boardgames: Weekly Crowdfunding Roundup</a> — Erwähnung mit 1.655 Backern (21.06.2026).</li>
<li><a href="https://boardgamegeek.com/video/616573/survivalist/survivalist-kickstarter-video" target="_blank" rel="noopener" style="color:#0075FF;">BGG: Survivalist Kickstarter-Video</a> — Offizielles Kampagnen-Video.</li>
</ol>
<p style="margin-top:2.5rem;padding-top:1rem;border-top:1px solid #ddd;font-size:0.9rem;color:#777;"><em>Recherchestand: 23. Juni 2026, 09:30 Uhr. Kickstarter-Daten via <a href="https://www.pledgebox.com/projects/survivalist-the-board-game" target="_blank" rel="noopener" style="color:#0075FF;">PledgeBox</a> (live), BGG-Daten via <a href="https://boardgamegeek.com/boardgame/459646/survivalist" target="_blank" rel="noopener" style="color:#0075FF;">BoardGameGeek</a>, Community-Diskussionen via <a href="https://boardgamegeek.com/boardgame/459646/survivalist/forums/0" target="_blank" rel="noopener" style="color:#0075FF;">BGG-Foren</a> + <a href="https://old.reddit.com/r/boardgames/" target="_blank" rel="noopener" style="color:#0075FF;">Reddit</a>.</em></p>
<p style="font-size:0.9rem;color:#777;"><em>Dieser Artikel enthält Affiliate-freie redaktionelle Berichterstattung. Die Kickstarter-Links sind keine Affiliate-Links.</em></p>
</article>
+383
View File
@@ -0,0 +1,383 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Anforderungsdefinition: Vollautomatischer Artikel-Workflow</title>
<meta name="description" content="Spezifikation für eine vollautomatische Pipeline von Input (Thema/PDF/URL) → QC-geprüfter HTML-Artikel. Stages: Input Prep → Research → Write → QC → Deliver.">
<style>
:root {
--accent: #20228a;
--accent-light: #3b3da8;
--bg: #f8f9fb;
--surface: #ffffff;
--text: #1a1a2e;
--text-muted: #6b7280;
--border: #e5e7eb;
--radius: 10px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; padding: 2rem 1.5rem; max-width: 900px; margin: 0 auto; }
header { margin-bottom: 2.5rem; padding-bottom: 1.5rem; border-bottom: 3px solid var(--accent); }
h1 { font-size: 1.8rem; color: var(--accent); margin-bottom: 0.4rem; letter-spacing: -0.02em; }
header p { color: var(--text-muted); font-size: 0.95rem; }
h2 { font-size: 1.3rem; color: var(--accent); margin: 2rem 0 0.8rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--border); }
h3 { font-size: 1.1rem; color: var(--accent-light); margin: 1.5rem 0 0.5rem; }
h4 { font-size: 1rem; color: #444; margin: 1rem 0 0.3rem; }
p, li { margin-bottom: 0.5rem; }
ul, ol { padding-left: 1.5rem; margin-bottom: 1rem; }
li { margin-bottom: 0.25rem; }
code { background: #f0f0f5; padding: 0.15em 0.4em; border-radius: 4px; font-size: 0.9em; font-family: 'SF Mono', 'Fira Code', monospace; }
pre { background: #1a1a2e; color: #e0e0e0; padding: 1rem 1.2rem; border-radius: var(--radius); overflow-x: auto; font-size: 0.85rem; line-height: 1.5; margin: 1rem 0; }
pre code { background: none; padding: 0; color: inherit; }
table { width: 100%; border-collapse: collapse; margin: 1rem 0; background: var(--surface); border-radius: var(--radius); overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
th { background: linear-gradient(135deg, var(--accent), var(--accent-light)); color: #fff; font-weight: 600; padding: 0.6rem 0.8rem; text-align: left; font-size: 0.9rem; }
td { padding: 0.5rem 0.8rem; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
tr:last-child td { border-bottom: none; }
tbody tr:nth-child(even) { background: rgba(32,34,138,0.03); }
.pipeline { display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; font-family: monospace; font-size: 0.85rem; margin: 1rem 0; padding: 0.8rem; background: var(--surface); border-radius: var(--radius); overflow-x: auto; }
.stage { background: linear-gradient(135deg, var(--accent), var(--accent-light)); color: #fff; padding: 0.3rem 0.6rem; border-radius: 6px; white-space: nowrap; font-weight: 600; }
.arrow { color: var(--accent); font-weight: bold; }
.badge { display: inline-block; padding: 0.15em 0.5em; border-radius: 4px; font-size: 0.8rem; font-weight: 600; margin-right: 0.3rem; }
.badge-green { background: #d1fae5; color: #065f46; }
.badge-red { background: #fee2e2; color: #991b1b; }
.badge-blue { background: #dbeafe; color: #1e40af; }
.badge-yellow { background: #fef3c7; color: #92400e; }
.checklist { display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem; margin: 1rem 0; }
.checklist-item { display: flex; align-items: flex-start; gap: 0.4rem; padding: 0.3rem 0; font-size: 0.9rem; }
.checklist-item .icon { flex-shrink: 0; width: 1.3em; }
hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border); color: var(--text-muted); font-size: 0.85rem; }
</style>
</head>
<body>
<article>
<header>
<h1>Anforderungsdefinition: Vollautomatischer Artikel-Workflow</h1>
<p>Spezifikation für Cody · 23. Juni 2026 · Stand: final</p>
</header>
<h2>1. Ziel</h2>
<p>Eine Pipeline, die aus einem beliebigen Input (Thema, PDF, URL, Stichwort) einen <strong>QC-geprüften, publikationsfertigen HTML-Artikel</strong> produziert. Basti gibt den Auftrag — der Rest läuft autonom.</p>
<h2>2. Eingabe-Schnittstelle</h2>
<pre><code>Input: {
topic: string, // "Gamegenic wird Partner von Tolarian Community College"
sources: string[], // ["/pfad/pm.pdf", "https://icv2.com/...", "https://gamegenic.com"]
category_id: number, // Joomla-Rubrik (default: 61 = Korrektur)
rubric: string, // "Neuerscheinungen" | "Crowdfunding" | "Nachrichten" | ...
image_urls: string[], // optionale Bild-URLs aus PM
deadline: string | null // optional: ISO-Timestamp für zeitkritische Artikel
}</code></pre>
<h3>Input-Quellen-Erkennung (automatisch)</h3>
<table>
<tr><th>Input</th><th>Quelle</th></tr>
<tr><td>PDF</td><td>Pfad aus <code>~/.hermes/cache/documents/</code></td></tr>
<tr><td>URL</td><td>Direkt übernehmen</td></tr>
<tr><td>Nur Thema</td><td>Sub-Agent recherchiert selbstständig Quellen</td></tr>
</table>
<h2>3. Pipeline-Stufen</h2>
<div class="pipeline">
<span class="stage">STAGE 0<br>Input Prep</span>
<span class="arrow"></span>
<span class="stage">STAGE 1<br>Research</span>
<span class="arrow"></span>
<span class="stage">STAGE 2<br>Write</span>
<span class="arrow"></span>
<span class="stage">STAGE 3<br>QC</span>
<span class="arrow"></span>
<span class="stage">STAGE 4<br>Deliver</span>
</div>
<p style="font-size:0.85rem;color:var(--text-muted);margin-top:0.3rem;">Dauer: ~1015 Minuten von Input bis fertigem Artikel</p>
<h3>STAGE 0: Input Preparation</h3>
<p><span class="badge badge-blue">Basti</span> (nicht Sub-Agent)</p>
<ol>
<li>PDFs OCRen: <code>python3 -c "import pymupdf; doc=pymupdf.open('…'); [print(page.get_text()) …]"</code></li>
<li>Prompt bauen aus Input + OCR-Text + Quellen-URLs</li>
<li>Skills-Array: <code>["journalistic-article-writing", "korrekturschleife", "article-management"]</code></li>
<li>Toolsets: <code>["web", "terminal", "file", "search", "skills"]</code></li>
</ol>
<p><strong>Output:</strong> Fertiger Prompt für Stage 1</p>
<h3>STAGE 1: Research</h3>
<p><span class="badge badge-green">Sub-Agent #1</span> (Leaf, <code>delegate_task</code>)</p>
<table>
<tr><th>Feld</th><th>Wert</th></tr>
<tr><td>Skills</td><td>journalistic-article-writing</td></tr>
<tr><td>Toolsets</td><td>web, terminal, file, search, skills</td></tr>
</table>
<ol>
<li>Alle Quellen abrufen (<code>web_extract</code>, <code>curl</code>, <code>web_search</code>)</li>
<li>Jede Quelle bewerten: <strong>AAA</strong> (Primär) / <strong>AA</strong> (Sekundär) / <strong>A</strong> (schwach)</li>
<li>Fehlende Fakten ergänzen:
<ul>
<li>BGG-Eintrag suchen: <code>web_search site:boardgamegeek.com SPIELNAME</code></li>
<li>Autor/Illustrator/Verlag verifizieren</li>
<li>Preise, Daten, Awards gegenprüfen</li>
</ul>
</li>
</ol>
<p><strong>Output:</strong> <code>research_brief.md</code></p>
<pre><code># Research Brief: THEMA
## Quellenbewertung
| Quelle | Stufe | URL |
|---------|---------|-----------------------|
| PM | AAA | https://gamegenic.com |
## Fakten
- Fakt 1 (Quelle: URL, AAA)
- Fakt 2 (Quelle: URL, AA)
## Offene Punkte
- Punkt X konnte nicht verifiziert werden → STREICHEN</code></pre>
<p><strong>Exit-Codes:</strong> <span class="badge badge-green">0</span> = vollständig · <span class="badge badge-red">1</span> = unvollständig (Abbruch, an Basti melden)</p>
<h3>STAGE 2: Write</h3>
<p><span class="badge badge-green">Sub-Agent #2</span> (Leaf, <code>delegate_task</code>)</p>
<table>
<tr><th>Feld</th><th>Wert</th></tr>
<tr><td>Skills</td><td>journalistic-article-writing, korrekturschleife, article-management</td></tr>
<tr><td>Toolsets</td><td>web, terminal, file, search, skills</td></tr>
<tr><td>Context</td><td>Research Brief + Original-Prompt</td></tr>
</table>
<div class="checklist">
<div class="checklist-item"><span class="icon"></span> KEINE "XML API v2" → klickbare BGG-Links</div>
<div class="checklist-item"><span class="icon"></span> KEIN "Betriebsgeheimnis"</div>
<div class="checklist-item"><span class="icon"></span> Quellen = <code>&lt;a href="…" target="_blank"&gt;</code></div>
<div class="checklist-item"><span class="icon"></span> <code>&lt;span&gt;&lt;strong&gt;Quellen&lt;/strong&gt;&lt;/span&gt;</code></div>
<div class="checklist-item"><span class="icon"></span> KEINE Emojis/Icons</div>
<div class="checklist-item"><span class="icon"></span> Datei endet mit <code>&lt;/html&gt;</code></div>
</div>
<p><strong>Output:</strong> <code>/home/hermes/workspace/artikel_NAME_DATUM.html</code></p>
<p><strong>Pflicht-Elemente im HTML:</strong></p>
<pre><code>&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;title&gt;TITEL&lt;/title&gt;
&lt;meta name="description" content="…"&gt;
&lt;meta name="keywords" content="…"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;article&gt;
&lt;h1&gt;TITEL&lt;/h1&gt;
&lt;p class="meta"&gt;DATUM · Lesezeit: ca. N Minuten&lt;/p&gt;
&lt;!-- Inhalt --&gt;
&lt;hr id="system-readmore" /&gt;
&lt;span style="…"&gt;&lt;strong&gt;Quellen&lt;/strong&gt;&lt;/span&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="URL" target="_blank"&gt;Name&lt;/a&gt; — Beschreibung&lt;/li&gt;
&lt;/ol&gt;
&lt;p class="qc"&gt;&lt;!-- QC: Quelle1=AAA|URL, … --&gt;&lt;/p&gt;
&lt;/article&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><strong>Exit-Codes:</strong> <span class="badge badge-green">0</span> = Artikel geschrieben · <span class="badge badge-red">1</span> = Fehler</p>
<h3>STAGE 3: Quality Control</h3>
<p><span class="badge badge-green">Sub-Agent #2</span> (gleicher Agent, nach dem Schreiben)</p>
<pre><code>bash /home/hermes/workspace/verify_article.sh artikel_NAME.html</code></pre>
<h4>13 Checks (Stand 23.06.2026)</h4>
<table>
<thead>
<tr><th>#</th><th>Check</th><th>Kriterium</th></tr>
</thead>
<tbody>
<tr><td>1</td><td>Read-More</td><td>Genau 1 × <code>&lt;hr id="system-readmore"&gt;</code></td></tr>
<tr><td>2</td><td>br nach Read-More</td><td>Kein <code>&lt;br&gt;</code> direkt danach</td></tr>
<tr><td>3</td><td>p.meta</td><td><code>&lt;p class="meta"&gt;</code> (nicht <code>&lt;div&gt;</code>)</td></tr>
<tr><td>4</td><td>Emojis</td><td>Keine im Body</td></tr>
<tr><td>5</td><td>Dateiende</td><td>Endet mit <code>&lt;/html&gt;</code></td></tr>
<tr><td>6</td><td>Meta im Body</td><td>Keine doppelte Datum·Lesezeit-Zeile</td></tr>
<tr><td>7</td><td>Quellen-Überschrift</td><td>Kein <code>&lt;h2&gt;Quellen&lt;/h2&gt;</code></td></tr>
<tr><td>8</td><td>Links target</td><td>Alle mit <code>target="_blank"</code></td></tr>
<tr><td>9</td><td>Meta-Description</td><td>Vorhanden und nicht leer</td></tr>
<tr><td>10</td><td>QC-Zeile</td><td>In <code>&lt;p class="qc"&gt;</code></td></tr>
<tr><td><strong>11</strong></td><td><strong>BGG-Autorencheck</strong></td><td><strong>Designer gegen BGG verifizieren</strong></td></tr>
<tr><td style="color:#991b1b;font-weight:600;">12</td><td style="color:#991b1b;font-weight:600;">XML-API-Referenzen</td><td style="color:#991b1b;font-weight:600;">Keine "XML API" / "Betriebsgeheimnis"</td></tr>
<tr><td style="color:#991b1b;font-weight:600;">13</td><td style="color:#991b1b;font-weight:600;">Quellen verlinkt</td><td style="color:#991b1b;font-weight:600;">Quellen-Block hat <code>&lt;a href&gt;</code> Links</td></tr>
</tbody>
</table>
<p><strong>Fehlerbehandlung:</strong></p>
<ul>
<li><span class="badge badge-green">Exit 0</span> → Stage 4</li>
<li><span class="badge badge-red">Exit 1</span> → Korrigieren, erneut verify → max 3 Versuche → ABBRUCH, an Basti melden</li>
<li>CHECK 11 (BGG-Autoren) <span class="badge badge-yellow">Warnung</span> → dokumentieren, nicht blocken</li>
</ul>
<h3>STAGE 4: Deliver</h3>
<p><span class="badge badge-blue">Basti</span></p>
<ol>
<li>Git-Commit: <code>git add artikel_NAME.html && git commit -m "artikel: THEMA"</code></li>
<li>Article-Server prüfen: Direkt-URL testen<br>
<code>curl "http://185.162.249.159:8890/artikel_NAME.html?key=…" | head -3</code></li>
<li>Meldung:</li>
</ol>
<pre><code>✅ Artikel THEMA fertig
📄 /home/hermes/workspace/artikel_NAME.html
🔗 http://185.162.249.159:8890/artikel_NAME.html?key=…
📊 verify: 0 Fehler</code></pre>
<h2>4. Fehlermodi & Recovery</h2>
<table>
<tr><th>Fehler</th><th>Erkennung</th><th>Recovery</th></tr>
<tr>
<td>PDF nicht OCR-bar</td>
<td>pymupdf wirft Exception</td>
<td>Basti manuell OCRen, Text in Prompt geben</td>
</tr>
<tr>
<td>Quelle nicht erreichbar</td>
<td>curl/web_extract Timeout/404</td>
<td>5-Stufen-Eskalation:<br>curl → wget+UA → Wayback → Tavily → Google Cache</td>
</tr>
<tr>
<td>Keine AAA-Quelle für Kern-Fakt</td>
<td>Research-Gap</td>
<td>Fakt streichen → "unvollständig" an Basti</td>
</tr>
<tr>
<td>verify_article.sh fail</td>
<td>Exit ≠ 0</td>
<td>3 Korrekturversuche → Abbruch</td>
</tr>
<tr>
<td>BGG-Autorencheck uneindeutig</td>
<td>check_authors.py Warnung</td>
<td>Dokumentieren, Basti entscheidet</td>
</tr>
<tr>
<td>Article-Server down</td>
<td>curl auf :8890 Timeout</td>
<td>Server neu starten via <code>python3 article_server.py &</code></td>
</tr>
</table>
<h2>5. Cronjob-Integration</h2>
<p>Der Workflow läuft als One-Shot-Cronjob mit <code>deliver="local"</code>:</p>
<pre><code>cronjob(
action="create",
schedule="1m",
repeat=1,
deliver="local",
skills=["journalistic-article-writing", "korrekturschleife", "article-management"],
enabled_toolsets=["web", "terminal", "file", "search", "skills", "delegation"],
prompt="…" // Stage-0-Output
)</code></pre>
<p><strong>Wichtig:</strong> <code>deliver="local"</code> — der Cronjob meldet sich NICHT bei Daniel. Basti pollt das Ergebnis.</p>
<h2>6. Batch-Modus</h2>
<p>Für mehrere Artikel: Parallele Cronjobs (max 3 gleichzeitig via <code>delegation.max_concurrent_children</code>). Jeder Cronjob bekommt denselben Prompt-Aufbau, nur mit anderem Input.</p>
<h2>7. Was Cody bauen muss</h2>
<h3>7.1 <code>article_pipeline.py</code> — Orchestrator-Script</h3>
<ul>
<li>Input entgegennimmt (JSON oder CLI-Args)</li>
<li>Stage 0 ausführt (PDF-OCR, Prompt-Bau)</li>
<li>Zwei <code>delegate_task</code>-Aufrufe (Stage 1 Research, Stage 2 Write+QC)</li>
<li>Stage 4 ausführt (Git-Commit, Verify)</li>
<li>Exit-Code zurückgibt</li>
</ul>
<h3>7.2 Integration</h3>
<ul>
<li>Als Cronjob-Script lauffähig (<code>no_agent=false</code> Modus)</li>
<li>ODER: Als Hermes-Skill <code>article-pipeline</code> registrierbar</li>
</ul>
<h3>7.3 Config</h3>
<table>
<tr><th>Element</th><th>Pfad / Wert</th></tr>
<tr><td>Workspace</td><td><code>/home/hermes/workspace/</code></td></tr>
<tr><td>verify_article.sh</td><td><code>/home/hermes/workspace/verify_article.sh</code></td></tr>
<tr><td>article_server.py</td><td><code>/home/hermes/workspace/article_server.py</code></td></tr>
<tr><td>PDF-Cache</td><td><code>~/.hermes/cache/documents/</code></td></tr>
<tr><td>Skills</td><td>journalistic-article-writing, korrekturschleife, article-management</td></tr>
</table>
<h2>8. Aufruf-Beispiele</h2>
<pre><code># Variante A: Thema + PDF
python3 article_pipeline.py \
--topic "Gamegenic wird Partner von Tolarian Community College" \
--pdf "/home/hermes/.hermes/cache/documents/GG_Press_release.pdf" \
--rubric "Nachrichten" \
--category-id 9
# Variante B: Nur Thema (autonome Recherche)
python3 article_pipeline.py \
--topic "Neue Kennerspiel-Nominierungen 2026" \
--rubric "Nachrichten"
# Variante C: Batch
python3 article_pipeline.py --batch topics.json</code></pre>
<h2>9. Erfolgskriterium</h2>
<div style="background:linear-gradient(135deg,#d1fae5,#a7f3d0);border-radius:10px;padding:1.5rem;margin:1.5rem 0;text-align:center;">
<p style="font-size:1.1rem;margin:0;"><strong>🎯 Basti gibt ein Thema ein → nach ~10 Minuten liegt ein Artikel im Workspace mit</strong></p>
<p style="font-size:1.4rem;font-weight:700;color:#065f46;margin:0.3rem 0;">verify_article.sh: 0 Fehler</p>
<p style="font-size:0.9rem;color:#555;margin:0;">Kein manuelles Eingreifen. Kein Nachbessern. Kein "XML API v2".</p>
</div>
<hr>
<h2>Anhang: Skills-Dokumentation</h2>
<p>Folgende Skills werden im Workflow verwendet. Cody muss ihre Schnittstellen kennen:</p>
<table>
<tr><th>Skill</th><th>Typ</th><th>Funktion</th></tr>
<tr>
<td><code>journalistic-article-writing</code></td>
<td>Writing</td>
<td>HTML-Template, Quellenregeln, Sprachregeln, Formatvorgaben</td>
</tr>
<tr>
<td><code>korrekturschleife</code></td>
<td>QC</td>
<td>Zweistufiger Workflow (Write + Fact-Check), Writer-Fehler-Tabelle, Quellenbewertung AAA/AA/A</td>
</tr>
<tr>
<td><code>article-management</code></td>
<td>Deploy</td>
<td>Article-Server (Port 8890), Joomla-API, verify_article.sh, VG-Wort</td>
</tr>
</table>
</article>
<footer>
<p>Spezifikation für Cody · 23. Juni 2026 · brettspiel-news.de Redaktion</p>
</footer>
</body>
</html>
+159
View File
@@ -0,0 +1,159 @@
#!/bin/bash
# verify_article.sh — Prüft Artikel-HTML auf Einhaltung aller redaktionellen Standards
# Usage: ./verify_article.sh <artikel_datei.html>
# Exit 0 = alle Checks bestanden, Exit 1 = Fehler gefunden
#
# Aufruf aus Cronjobs: bash /home/hermes/workspace/verify_article.sh /home/hermes/workspace/artikel_*.html
set -euo pipefail
FILE="${1:-}"
ERRORS=0
WARNINGS=0
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
ok() { echo -e "${GREEN}$1${NC}"; }
fail() { echo -e "${RED}$1${NC}"; ERRORS=$((ERRORS+1)); }
warn() { echo -e "${YELLOW}⚠️ $1${NC}"; WARNINGS=$((WARNINGS+1)); }
if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
echo "Usage: $0 <artikel_datei.html>"
exit 1
fi
echo "=== Verifiziere: $FILE ==="
echo ""
# ── CHECK 1: Exactly one read-more ──────────────────────────────────
# Nur echte HTML-Tags zählen, nicht &lt;hr...&gt; Referenzen in QC-Zeilen
READMORE_COUNT=$(grep -c '<hr id="system-readmore"' "$FILE" || true)
if [ "$READMORE_COUNT" = "1" ]; then
ok "Read-More: genau 1"
elif [ "$READMORE_COUNT" = "0" ]; then
fail "Read-More: KEIN <hr id=\"system-readmore\" /> gefunden"
else
fail "Read-More: $READMORE_COUNT Stück (muss genau 1 sein)"
fi
# ── CHECK 2: No <br> immediately after read-more ────────────────────
if grep -A1 'system-readmore' "$FILE" | grep -q '<br'; then
fail "<br> direkt nach Read-More (erzeugt doppelte Linie)"
else
ok "Kein <br> nach Read-More"
fi
# ── CHECK 3: p.meta not div.meta ────────────────────────────────────
if grep -q '<div class="meta">' "$FILE"; then
fail "<div class=\"meta\"> statt <p class=\"meta\"> (article_server regex schlägt fehl)"
else
ok "<p class=\"meta\"> korrekt"
fi
# ── CHECK 4: No emojis in article body (QC line excluded) ──────────
# Extract body between <article> and </article>, strip everything from QC line onward
BODY=$(sed -n '/<article>/,/<\/article>/p' "$FILE" | sed '/<p class="qc">/,$d')
if echo "$BODY" | python3 -c "import sys,re; t=sys.stdin.read(); sys.exit(0 if re.search(r'[\U0001F300-\U0001F9FF\U00002600-\U000027BF\U0001F600-\U0001F64F]',t) else 1)"; then
fail "Emojis im Artikel-Body gefunden"
else
ok "Keine Emojis"
fi
# ── CHECK 5: File ends with </html> ─────────────────────────────────
LAST_LINE=$(tail -1 "$FILE")
if echo "$LAST_LINE" | grep -q '</html>'; then
ok "Datei endet mit </html>"
else
fail "Letzte Zeile ist nicht </html> (Fremd-Content nach Artikel-Ende?)"
fi
# ── CHECK 6: No meta-line in body (title, date, reading time) ───────
if echo "$BODY" | grep -qP '<p[^>]*>.*\d{1,2}\.\s+\w+\s+\d{4}.*·.*Lesezeit'; then
warn "Meta-Zeile möglicherweise im Body (Datum · Rubrik · Lesezeit)"
fi
# ── CHECK 7: Sources heading uses span-strong, not h2 ───────────────
if echo "$BODY" | grep -q '<h2>Quellen</h2>'; then
fail "<h2>Quellen</h2> statt <span><strong>Quellen</strong></span>"
else
ok "Quellen-Überschrift korrekt (kein <h2>)"
fi
# ── CHECK 8: All links have target="_blank" ─────────────────────────
LINKS_TOTAL=$(echo "$BODY" | grep -o '<a ' | wc -l)
LINKS_BLANK=$(echo "$BODY" | grep -o 'target="_blank"' | wc -l)
if [ "$LINKS_TOTAL" -gt 0 ] && [ "$LINKS_TOTAL" != "$LINKS_BLANK" ]; then
MISSING=$((LINKS_TOTAL - LINKS_BLANK))
fail "$MISSING von $LINKS_TOTAL Links ohne target=\"_blank\""
else
ok "Alle $LINKS_TOTAL Links mit target=\"_blank\""
fi
# ── CHECK 9: Meta description present ───────────────────────────────
if grep -q '<meta name="description" content="[^"]*[a-zA-Z]' "$FILE"; then
ok "Meta-Description vorhanden"
else
fail "Meta-Description fehlt oder leer"
fi
# ── CHECK 10: No QC line outside <p class="qc"> ────────────────────
QC_COUNT=$(echo "$BODY" | grep -c 'class="qc"' || true)
QC_RAW=$(echo "$BODY" | grep -cP 'QC:\s*[✓✔]' || true)
if [ "$QC_RAW" -gt "$QC_COUNT" ]; then
fail "QC-Hinweis nicht in <p class=\"qc\"> (wird nicht gestrippt)"
else
ok "QC-Zeile korrekt in <p class=\"qc\">"
fi
# ── CHECK 11: Autoren gegen BGG verifizieren ─────────────────────────
echo ""
echo "--- BGG Autoren-Faktencheck ---"
AUTHOR_CHECK=$(python3 /home/hermes/workspace/check_authors.py "$FILE" 2>&1)
AUTHOR_EXIT=$?
echo "$AUTHOR_CHECK"
if [ $AUTHOR_EXIT -eq 1 ]; then
ERRORS=$((ERRORS + 1))
elif [ $AUTHOR_EXIT -eq 2 ]; then
WARNINGS=$((WARNINGS + 1))
fi
# ── CHECK 12: Keine XML-API-Referenzen im Artikel ──────────────────
if echo "$BODY" | grep -qiP 'xml\s*api|XMLAPI|xmlapi|betriebsgeheimnis'; then
fail "XML-API-Referenz oder 'Betriebsgeheimnis' im Artikel (nicht leser-tauglich)"
else
ok "Keine XML-API-Referenzen"
fi
# ── CHECK 13: Quellen-Block muss klickbare Links enthalten ──────────
# Sucht Quellen-Überschrift (h2, span, strong) und prüft ob danach Links kommen
SOURCES_SECTION=$(echo "$BODY" | sed -n '/<h2>Quellen<\/h2>/,$p; /<span.*>.*Quellen.*<\/span>/,$p' || echo "")
if [ -n "$SOURCES_SECTION" ]; then
SOURCE_LINKS=$(echo "$SOURCES_SECTION" | grep -c '<a href=' || true)
if [ "$SOURCE_LINKS" -eq 0 ]; then
fail "Quellen-Block ohne klickbare Links (nur Fließtext)"
else
ok "Quellen-Block mit $SOURCE_LINKS klickbaren Links"
fi
else
fail "Kein Quellen-Block gefunden (h2/span>Quellen fehlt)"
fi
# ── SUMMARY ─────────────────────────────────────────────────────────
echo ""
echo "═══════════════════════════════════════"
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
echo -e "${GREEN}✅ ALLE CHECKS BESTANDEN${NC}"
echo "═══════════════════════════════════════"
exit 0
elif [ $ERRORS -eq 0 ]; then
echo -e "${YELLOW}⚠️ $WARNINGS Warnung(en) — KEINE Fehler${NC}"
echo "═══════════════════════════════════════"
exit 0
else
echo -e "${RED}$ERRORS Fehler, $WARNINGS Warnungen${NC}"
echo "═══════════════════════════════════════"
exit 1
fi