Add complete Schreibwerkstatt BMAD module
- 7 specialized agents (Dramaturg, Autor, Lektor, Figurenprüfer, Kontinuitätsprüfer, Motivjäger, Stilprüfer) - Bibel-System (Single Source of Truth) - State Management per chapter - Review Pipeline with Human-in-the-Loop - KI-Muster-Erkennung (Perplexity Gate) - Export (EPUB, DOCX, Markdown) - Setup scripts and templates
This commit is contained in:
parent
1f72c07758
commit
22298f1008
35 changed files with 2184 additions and 2 deletions
128
scripts/export-docx.py
Normal file
128
scripts/export-docx.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Schreibwerkstatt — DOCX Export
|
||||
Kombiniert alle Kapitel zu einem formatierten Word-Dokument.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def find_project_root():
|
||||
"""Findet das Projektstammverzeichnis."""
|
||||
current = Path.cwd()
|
||||
while current != current.parent:
|
||||
if (current / "CLAUDE.md").exists() and (current / "bibel").exists():
|
||||
return current
|
||||
current = current.parent
|
||||
return Path.cwd()
|
||||
|
||||
def get_chapters(kapitel_dir):
|
||||
"""Liest alle Kapitel in der richtigen Reihenfolge."""
|
||||
chapters = []
|
||||
for f in sorted(kapitel_dir.glob("*.md")):
|
||||
if f.name.startswith("_"):
|
||||
continue
|
||||
content = f.read_text(encoding="utf-8")
|
||||
lines = content.strip().split("\n")
|
||||
title = lines[0].lstrip("# ").strip() if lines else f.stem
|
||||
body = "\n".join(lines[1:]).strip() if len(lines) > 1 else ""
|
||||
chapters.append({"title": title, "body": body, "file": f.name})
|
||||
return chapters
|
||||
|
||||
def get_metadata(root):
|
||||
"""Liest Metadaten aus der Synopsis."""
|
||||
synopsis = root / "geschichte" / "synopsis.md"
|
||||
metadata = {"title": "Unbenannter Roman", "author": "Unbekannt"}
|
||||
if synopsis.exists():
|
||||
content = synopsis.read_text(encoding="utf-8")
|
||||
title_match = re.search(r"## Arbeitstitel\s*\n+(.+)", content)
|
||||
if title_match:
|
||||
title = title_match.group(1).strip().strip("<!--").strip("-->").strip()
|
||||
if title:
|
||||
metadata["title"] = title
|
||||
return metadata
|
||||
|
||||
def export_docx(root, output_path):
|
||||
"""Erstellt eine DOCX-Datei."""
|
||||
try:
|
||||
from docx import Document
|
||||
from docx.shared import Pt, Cm
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
except ImportError:
|
||||
print("❌ python-docx nicht installiert!")
|
||||
print(" Installiere mit: pip3 install python-docx")
|
||||
sys.exit(1)
|
||||
|
||||
kapitel_dir = root / "_bmad-output" / "kapitel"
|
||||
if not kapitel_dir.exists() or not list(kapitel_dir.glob("*.md")):
|
||||
print("❌ Keine Kapitel gefunden in _bmad-output/kapitel/")
|
||||
sys.exit(1)
|
||||
|
||||
chapters = get_chapters(kapitel_dir)
|
||||
metadata = get_metadata(root)
|
||||
|
||||
doc = Document()
|
||||
|
||||
# Seitenränder
|
||||
for section in doc.sections:
|
||||
section.top_margin = Cm(2.5)
|
||||
section.bottom_margin = Cm(2.5)
|
||||
section.left_margin = Cm(3)
|
||||
section.right_margin = Cm(2.5)
|
||||
|
||||
# Titelseite
|
||||
title_para = doc.add_paragraph()
|
||||
title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
title_run = title_para.add_run(metadata["title"])
|
||||
title_run.font.size = Pt(28)
|
||||
title_run.bold = True
|
||||
|
||||
author_para = doc.add_paragraph()
|
||||
author_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
author_run = author_para.add_run(metadata["author"])
|
||||
author_run.font.size = Pt(16)
|
||||
|
||||
doc.add_page_break()
|
||||
|
||||
# Kapitel
|
||||
for ch in chapters:
|
||||
heading = doc.add_heading(ch["title"], level=1)
|
||||
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
# Absätze verarbeiten
|
||||
paragraphs = ch["body"].split("\n\n")
|
||||
for p_text in paragraphs:
|
||||
p_text = p_text.strip()
|
||||
if not p_text:
|
||||
continue
|
||||
|
||||
# Szenenumbruch
|
||||
if p_text in ("***", "---", "* * *"):
|
||||
scene_break = doc.add_paragraph("* * *")
|
||||
scene_break.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
scene_break.space_before = Pt(18)
|
||||
scene_break.space_after = Pt(18)
|
||||
continue
|
||||
|
||||
para = doc.add_paragraph(p_text)
|
||||
para.paragraph_format.first_line_indent = Cm(1)
|
||||
para.paragraph_format.line_spacing = 1.5
|
||||
for run in para.runs:
|
||||
run.font.size = Pt(12)
|
||||
run.font.name = "Georgia"
|
||||
|
||||
doc.add_page_break()
|
||||
|
||||
doc.save(str(output_path))
|
||||
print(f"✅ DOCX erstellt: {output_path}")
|
||||
print(f" {len(chapters)} Kapitel, {metadata['title']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = find_project_root()
|
||||
metadata = get_metadata(root)
|
||||
filename = metadata["title"].lower().replace(" ", "-") + ".docx"
|
||||
output = root / "_bmad-output" / "export" / filename
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
export_docx(root, output)
|
||||
125
scripts/export-epub.py
Normal file
125
scripts/export-epub.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Schreibwerkstatt — EPUB Export
|
||||
Kombiniert alle Kapitel zu einem EPUB E-Book.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def find_project_root():
|
||||
"""Findet das Projektstammverzeichnis."""
|
||||
current = Path.cwd()
|
||||
while current != current.parent:
|
||||
if (current / "CLAUDE.md").exists() and (current / "bibel").exists():
|
||||
return current
|
||||
current = current.parent
|
||||
return Path.cwd()
|
||||
|
||||
def get_chapters(kapitel_dir):
|
||||
"""Liest alle Kapitel in der richtigen Reihenfolge."""
|
||||
chapters = []
|
||||
for f in sorted(kapitel_dir.glob("*.md")):
|
||||
if f.name.startswith("_"):
|
||||
continue
|
||||
content = f.read_text(encoding="utf-8")
|
||||
# Extrahiere Titel aus erster Zeile
|
||||
lines = content.strip().split("\n")
|
||||
title = lines[0].lstrip("# ").strip() if lines else f.stem
|
||||
body = "\n".join(lines[1:]).strip() if len(lines) > 1 else ""
|
||||
chapters.append({"title": title, "body": body, "file": f.name})
|
||||
return chapters
|
||||
|
||||
def get_metadata(root):
|
||||
"""Liest Metadaten aus der Synopsis."""
|
||||
synopsis = root / "geschichte" / "synopsis.md"
|
||||
metadata = {
|
||||
"title": "Unbenannter Roman",
|
||||
"author": "Unbekannt",
|
||||
"language": "de"
|
||||
}
|
||||
if synopsis.exists():
|
||||
content = synopsis.read_text(encoding="utf-8")
|
||||
# Versuche Titel zu extrahieren
|
||||
title_match = re.search(r"## Arbeitstitel\s*\n+(.+)", content)
|
||||
if title_match:
|
||||
title = title_match.group(1).strip().strip("<!--").strip("-->").strip()
|
||||
if title:
|
||||
metadata["title"] = title
|
||||
return metadata
|
||||
|
||||
def export_epub(root, output_path):
|
||||
"""Erstellt eine EPUB-Datei."""
|
||||
try:
|
||||
from ebooklib import epub
|
||||
except ImportError:
|
||||
print("❌ ebooklib nicht installiert!")
|
||||
print(" Installiere mit: pip3 install ebooklib")
|
||||
sys.exit(1)
|
||||
|
||||
kapitel_dir = root / "_bmad-output" / "kapitel"
|
||||
if not kapitel_dir.exists() or not list(kapitel_dir.glob("*.md")):
|
||||
print("❌ Keine Kapitel gefunden in _bmad-output/kapitel/")
|
||||
sys.exit(1)
|
||||
|
||||
chapters = get_chapters(kapitel_dir)
|
||||
metadata = get_metadata(root)
|
||||
|
||||
book = epub.EpubBook()
|
||||
book.set_identifier("schreibwerkstatt-" + metadata["title"].lower().replace(" ", "-"))
|
||||
book.set_title(metadata["title"])
|
||||
book.set_language(metadata["language"])
|
||||
book.add_author(metadata["author"])
|
||||
|
||||
# CSS
|
||||
style = epub.EpubItem(
|
||||
uid="style",
|
||||
file_name="style/default.css",
|
||||
media_type="text/css",
|
||||
content="""
|
||||
body { font-family: Georgia, serif; line-height: 1.6; margin: 1em; }
|
||||
h1 { text-align: center; margin-top: 2em; }
|
||||
p { text-indent: 1.5em; margin: 0.3em 0; }
|
||||
p:first-of-type { text-indent: 0; }
|
||||
.scene-break { text-align: center; margin: 1.5em 0; }
|
||||
""".encode("utf-8")
|
||||
)
|
||||
book.add_item(style)
|
||||
|
||||
epub_chapters = []
|
||||
for i, ch in enumerate(chapters, 1):
|
||||
c = epub.EpubHtml(
|
||||
title=ch["title"],
|
||||
file_name=f"kapitel_{i:02d}.xhtml",
|
||||
lang=metadata["language"]
|
||||
)
|
||||
# Konvertiere Markdown zu einfachem HTML
|
||||
html_body = ch["body"]
|
||||
html_body = re.sub(r"\*\*\*|---", '<p class="scene-break">* * *</p>', html_body)
|
||||
paragraphs = html_body.split("\n\n")
|
||||
html_body = "\n".join(f"<p>{p.strip()}</p>" for p in paragraphs if p.strip())
|
||||
|
||||
c.content = f"<h1>{ch['title']}</h1>\n{html_body}".encode("utf-8")
|
||||
c.add_item(style)
|
||||
book.add_item(c)
|
||||
epub_chapters.append(c)
|
||||
|
||||
# Inhaltsverzeichnis
|
||||
book.toc = [(c, c.title) for c in epub_chapters]
|
||||
book.add_item(epub.EpubNcx())
|
||||
book.add_item(epub.EpubNav())
|
||||
book.spine = ["nav"] + epub_chapters
|
||||
|
||||
epub.write_epub(str(output_path), book)
|
||||
print(f"✅ EPUB erstellt: {output_path}")
|
||||
print(f" {len(chapters)} Kapitel, {metadata['title']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = find_project_root()
|
||||
metadata = get_metadata(root)
|
||||
filename = metadata["title"].lower().replace(" ", "-") + ".epub"
|
||||
output = root / "_bmad-output" / "export" / filename
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
export_epub(root, output)
|
||||
85
scripts/export-markdown.py
Normal file
85
scripts/export-markdown.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Schreibwerkstatt — Markdown Export
|
||||
Kombiniert alle Kapitel in eine einzelne Markdown-Datei.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def find_project_root():
|
||||
current = Path.cwd()
|
||||
while current != current.parent:
|
||||
if (current / "CLAUDE.md").exists() and (current / "bibel").exists():
|
||||
return current
|
||||
current = current.parent
|
||||
return Path.cwd()
|
||||
|
||||
def get_chapters(kapitel_dir):
|
||||
chapters = []
|
||||
for f in sorted(kapitel_dir.glob("*.md")):
|
||||
if f.name.startswith("_"):
|
||||
continue
|
||||
content = f.read_text(encoding="utf-8")
|
||||
chapters.append({"content": content, "file": f.name})
|
||||
return chapters
|
||||
|
||||
def get_metadata(root):
|
||||
synopsis = root / "geschichte" / "synopsis.md"
|
||||
metadata = {"title": "Unbenannter Roman", "author": "Unbekannt"}
|
||||
if synopsis.exists():
|
||||
content = synopsis.read_text(encoding="utf-8")
|
||||
title_match = re.search(r"## Arbeitstitel\s*\n+(.+)", content)
|
||||
if title_match:
|
||||
title = title_match.group(1).strip().strip("<!--").strip("-->").strip()
|
||||
if title:
|
||||
metadata["title"] = title
|
||||
return metadata
|
||||
|
||||
def export_markdown(root, output_path):
|
||||
kapitel_dir = root / "_bmad-output" / "kapitel"
|
||||
if not kapitel_dir.exists() or not list(kapitel_dir.glob("*.md")):
|
||||
print("Keine Kapitel gefunden in _bmad-output/kapitel/")
|
||||
sys.exit(1)
|
||||
|
||||
chapters = get_chapters(kapitel_dir)
|
||||
metadata = get_metadata(root)
|
||||
|
||||
lines = []
|
||||
lines.append(f"# {metadata['title']}")
|
||||
lines.append("")
|
||||
lines.append(f"*{metadata['author']}*")
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
# Inhaltsverzeichnis
|
||||
lines.append("## Inhaltsverzeichnis")
|
||||
lines.append("")
|
||||
for i, ch in enumerate(chapters, 1):
|
||||
first_line = ch["content"].strip().split("\n")[0]
|
||||
title = first_line.lstrip("# ").strip()
|
||||
lines.append(f"{i}. {title}")
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
# Kapitel
|
||||
for ch in chapters:
|
||||
lines.append(ch["content"])
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
output_path.write_text("\n".join(lines), encoding="utf-8")
|
||||
print(f"Markdown erstellt: {output_path}")
|
||||
print(f" {len(chapters)} Kapitel, {metadata['title']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = find_project_root()
|
||||
metadata = get_metadata(root)
|
||||
filename = metadata["title"].lower().replace(" ", "-") + ".md"
|
||||
output = root / "_bmad-output" / "export" / filename
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
export_markdown(root, output)
|
||||
75
scripts/setup.sh
Normal file
75
scripts/setup.sh
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#!/bin/bash
|
||||
# Schreibwerkstatt Setup Script
|
||||
# Erstellt die Projektstruktur und installiert Abhängigkeiten
|
||||
|
||||
set -e
|
||||
|
||||
echo "╔══════════════════════════════════════╗"
|
||||
echo "║ 📖 Schreibwerkstatt Setup ║"
|
||||
echo "║ BMAD Modul für Roman-Entwicklung ║"
|
||||
echo "╚══════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Prüfe Python
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "⚠️ Python 3 nicht gefunden. Export-Funktionen benötigen Python 3.8+"
|
||||
echo " Installiere Python: sudo apt install python3 python3-pip"
|
||||
else
|
||||
PYTHON_VERSION=$(python3 --version 2>&1 | cut -d' ' -f2)
|
||||
echo "✅ Python $PYTHON_VERSION gefunden"
|
||||
fi
|
||||
|
||||
# Prüfe Claude Code
|
||||
if command -v claude &> /dev/null; then
|
||||
CLAUDE_VERSION=$(claude --version 2>&1 || echo "unbekannt")
|
||||
echo "✅ Claude Code gefunden: $CLAUDE_VERSION"
|
||||
else
|
||||
echo "⚠️ Claude Code nicht gefunden."
|
||||
echo " Installiere: npm install -g @anthropic/claude-code"
|
||||
fi
|
||||
|
||||
# Erstelle Ausgabe-Verzeichnisse
|
||||
echo ""
|
||||
echo "📁 Erstelle Verzeichnisse..."
|
||||
mkdir -p _bmad-output/kapitel
|
||||
mkdir -p _bmad-output/export
|
||||
mkdir -p zustand/kapitel
|
||||
echo " ✅ _bmad-output/kapitel/"
|
||||
echo " ✅ _bmad-output/export/"
|
||||
echo " ✅ zustand/kapitel/"
|
||||
|
||||
# Installiere Python-Abhängigkeiten (optional)
|
||||
echo ""
|
||||
read -p "📦 Python-Pakete für Export installieren? (EPUB/DOCX) [j/N] " install_deps
|
||||
if [[ "$install_deps" =~ ^[jJyY]$ ]]; then
|
||||
echo " Installiere python-docx und ebooklib..."
|
||||
pip3 install python-docx ebooklib --break-system-packages 2>/dev/null || \
|
||||
pip3 install python-docx ebooklib 2>/dev/null || \
|
||||
echo " ⚠️ Installation fehlgeschlagen. Versuche: pip3 install python-docx ebooklib"
|
||||
echo " ✅ Python-Pakete installiert"
|
||||
else
|
||||
echo " ⏭️ Übersprungen (kann später mit pip3 install python-docx ebooklib nachgeholt werden)"
|
||||
fi
|
||||
|
||||
# Git initialisieren falls nötig
|
||||
echo ""
|
||||
if [ ! -d ".git" ]; then
|
||||
read -p "📋 Git-Repository initialisieren? [J/n] " init_git
|
||||
if [[ ! "$init_git" =~ ^[nN]$ ]]; then
|
||||
git init
|
||||
echo " ✅ Git-Repository initialisiert"
|
||||
fi
|
||||
else
|
||||
echo "✅ Git-Repository existiert bereits"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════"
|
||||
echo "✅ Setup abgeschlossen!"
|
||||
echo ""
|
||||
echo "Nächste Schritte:"
|
||||
echo " 1. Starte Claude Code: claude"
|
||||
echo " 2. Sage: Hilfe"
|
||||
echo " 3. Fülle die Bibel: bibel/stil.md, bibel/figuren/, bibel/orte/"
|
||||
echo " 4. Erstelle Synopsis: 'Synopsis entwickeln'"
|
||||
echo "════════════════════════════════════════"
|
||||
Loading…
Add table
Add a link
Reference in a new issue