Align illustration pipeline and add full body figure set.
Use bilder/ as the only drop zone (q90 convert), ship production JPEGs for all catalog IDs including HTML-rendered S-02…S-07, and trim redundant cover master from assets/cover.
No files matched your search
@@ -12,9 +12,16 @@ CLAUDE.local.md
|
||||
.env.*
|
||||
secrets/
|
||||
|
||||
# Python / todo-cli
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
node_modules/
|
||||
|
||||
# Build
|
||||
build/
|
||||
dist/*.aux
|
||||
dist/*.log
|
||||
@@ -22,3 +29,7 @@ dist/*.out
|
||||
dist/*.toc
|
||||
# PDF-Output optional versionieren — dist/*.pdf nicht pauschal ignorieren
|
||||
kladde
|
||||
|
||||
# Illustration Drop-Zone (Rohquellen; Produktion unter assets/)
|
||||
bilder/
|
||||
pics/
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
description: Übernimmt Bilder aus bilder/ als JPEG q90 nach assets/ (ID-Korrelation)
|
||||
---
|
||||
# update-pics
|
||||
|
||||
Lade und führe den Skill aus: `.grok/skills/update-pics/SKILL.md`.
|
||||
|
||||
Kurz: `python3 scripts/convert_illustrations.py` im Repo-Root.
|
||||
Drop-Zone ist **`bilder/`** (nicht `pics/`). Kein Manuskript-Edit, kein PDF-Bau ohne Auftrag.
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: update-pics
|
||||
description: >
|
||||
Übernimmt Rohbilder aus bilder/ als JPEG (Qualität 90, Navy-Composite) nach
|
||||
assets/illustrations/ und assets/cover/. Mapping über ID-Präfix (K-01, M-03, …)
|
||||
in scripts/convert_illustrations.py / abbildungen.md.
|
||||
Nutzen bei: „update-pics“, „Bilder aus bilder“, „Illustrationen aktualisieren“,
|
||||
„Assets ersetzen“, /update-pics.
|
||||
---
|
||||
|
||||
# update-pics — Illustrationen aus `bilder/` übernehmen
|
||||
|
||||
Drop-Zone `bilder/` → kanonische JPGs unter `assets/`. Kein Manuskript-Edit, kein ungefragter PDF-Bau.
|
||||
|
||||
**Hinweis:** In den Schwesterprojekten heißt die Drop-Zone oft `pics/`. In diesem Repo ist der Kanon **`bilder/`** (siehe `abbildungen.md`).
|
||||
|
||||
## Prinzipien
|
||||
|
||||
1. **Nur Assets** — Kapitel-Markdown und `abbildungen.md` bleiben unberührt.
|
||||
2. **ID-Korrelation** — Dateiname beginnt mit Katalog-ID (`K-02-….png` → `K-02` → Zielname aus dem Convert-Skript).
|
||||
3. **JPEG q90 + Navy-Composite** — RGBA wird auf Night Navy (`#0B1B2B`) gelegt.
|
||||
4. **Quellen belassen** — Dateien in `bilder/` nicht löschen.
|
||||
5. **Deterministisch** — immer `scripts/convert_illustrations.py`, nicht manuell raten.
|
||||
|
||||
## Pfade
|
||||
|
||||
| Rolle | Pfad |
|
||||
|-------|------|
|
||||
| Quelle (Drop) | `bilder/` |
|
||||
| Ziel Illustrationen | `assets/illustrations/` |
|
||||
| Ziel Cover | `assets/cover/` |
|
||||
| Katalog | `abbildungen.md` |
|
||||
| Skript | `scripts/convert_illustrations.py` |
|
||||
|
||||
Erlaubte Quellformate: was Pillow lädt (typisch `.png`, `.jpg`, `.jpeg`, `.webp`).
|
||||
|
||||
## Ablauf
|
||||
|
||||
### 1. Vorbedingungen
|
||||
|
||||
- Repo-Root (mit `abbildungen.md` und `scripts/convert_illustrations.py`).
|
||||
- `bilder/` enthält mindestens eine Bilddatei mit ID-Präfix.
|
||||
- Pillow: `python3 -c "from PIL import Image"` — falls fehlend: User informieren (`pip install Pillow`).
|
||||
|
||||
### 2. Übernehmen
|
||||
|
||||
```bash
|
||||
python3 scripts/convert_illustrations.py
|
||||
```
|
||||
|
||||
Das Skript mappt ID-Präfixe auf kanonische Dateinamen und schreibt JPEGs.
|
||||
|
||||
### 3. Report
|
||||
|
||||
Skript-Ausgabe im Chat zusammenfassen:
|
||||
|
||||
```text
|
||||
## update-pics
|
||||
**Quelle:** bilder/
|
||||
**Übernommen / übersprungen / Fehler:** …
|
||||
**Hinweis:** Zum PDF `/pdf` bzw. `just pdf` (nur auf Wunsch).
|
||||
```
|
||||
|
||||
### 4. Nicht tun
|
||||
|
||||
- Keine `![…]()` in Kapitel einfügen oder Captions ändern
|
||||
- `abbildungen.md` nicht um Status-Spalten „patchen“
|
||||
- Kein `just pdf` / `just assemble` ohne Auftrag
|
||||
- Keine Dateien in `bilder/` löschen
|
||||
- Keine zweite Drop-Zone `pics/` anlegen (Doppelspur vermeiden)
|
||||
|
||||
## Korrelation (kurz)
|
||||
|
||||
| Quelle in `bilder/` | ID | Ziel (Beispiel) |
|
||||
|---------------------|-----|-----------------|
|
||||
| `K-02-irgendwas.png` | K-02 | `assets/illustrations/K-02-surfaces-eine-engine.jpg` |
|
||||
| `COVER-01B-….png` | COVER-01B | `assets/cover/COVER-01B-full.jpg` |
|
||||
| `diagram.png` | — | skip (kein ID-Präfix) |
|
||||
|
||||
Kanonische Namen: `scripts/convert_illustrations.py` (`TARGETS`) und `abbildungen.md`.
|
||||
|
||||
## Häufige Fehler
|
||||
|
||||
| Symptom | Ursache | Nächster Schritt |
|
||||
|---------|---------|------------------|
|
||||
| `bilder/` fehlt / leer | keine Drop-Dateien | Rohbilder mit ID-Präfix ablegen |
|
||||
| skip / kein Match | Dateiname startet nicht mit `K-NN` / `M-NN` / … | umbenennen laut Katalog |
|
||||
| `Pillow fehlt` | Dependency | `pip install Pillow` |
|
||||
| Ziel unverändert im PDF | nur Asset getauscht | PDF neu bauen, wenn gewünscht |
|
||||
|
||||
## Erfolg
|
||||
|
||||
- Erkannte Katalog-IDs aus `bilder/` liegen als JPEG q90 unter den kanonischen Asset-Pfaden
|
||||
- Unbekannte Dateien klar gemeldet
|
||||
- Keine Manuskript-Diffs
|
||||
@@ -1,15 +1,16 @@
|
||||
# Abbildungen und Gemini-Prompts
|
||||
|
||||
**Buch:** *Grok Build – Der umfassende Praxisleitfaden*
|
||||
**Stand:** 2026-07-28
|
||||
**Stand:** 2026-08-02
|
||||
**Zweck:** Inventar sinnvoller Abbildungen mit didaktischem Anker und **englischen Gemini-Prompts** (print-tauglich).
|
||||
|
||||
**Status (2026-07-28):** Inventar + Prompt-Dateien + Manuskript-Einbettungen. **Cover liegt vor** (`assets/cover/COVER-01B-full.jpg`, aus `grok-build-cover.png`). Body-Illustrationen noch offen — generieren über [`prompts/gemini/`](prompts/gemini/), Rohdateien nach `bilder/`, dann `python3 scripts/convert_illustrations.py`.
|
||||
**Status (2026-08-02):** Inventar + Prompt-Dateien + Manuskript-Einbettungen. **Cover:** `assets/cover/COVER-01B-full.jpg` (Master-Roh in `bilder/COVER-01B-master.png`, Drop-Zone gitignored). **Body-Assets:** alle 22 geplanten Figuren als JPEG q90 unter `assets/illustrations/` (S-02…S-07 aus HTML-Render `scripts/_fig_render/` → `bilder/` → convert). Inventar-only (kein Body-Embed): **K-03**, **N-01**.
|
||||
|
||||
- **Im Body geplant:** **22 Figuren** (+ Cover).
|
||||
- **Nur Inventar (nicht im Body):** **K-03** (Redundanz zu K-01), **N-01** (NICE, Text reicht).
|
||||
- Landscape im Body: `{width=90%}` (Vorwort/Einführung: `{width=80%}`); N-03 Portrait: `{width=55%}`.
|
||||
- Copy-paste-Prompts: [`prompts/gemini/`](prompts/gemini/) — Cover + KEY/MUST/SHOULD/NICE.
|
||||
- Copy-paste-Prompts: [`prompts/gemini/`](prompts/gemini/) — Cover + KEY/MUST/SHOULD/NICE.
|
||||
- **Gesamtliste mit Vollprompts:** [`prompts/gemini/LISTE.md`](prompts/gemini/LISTE.md) (Übersicht + pro Figur Anker, Caption, Asset, englischer Gemini-Prompt, Render-Target).
|
||||
|
||||
Dieses Dokument ist die Source of Truth für Inventar, Captions und Didaktik.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 5.5 MiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 119 KiB |
@@ -2,9 +2,20 @@
|
||||
|
||||
Gerüste zum Kopieren und Anpassen. Keine echten Secrets.
|
||||
|
||||
## Hierarchie (nicht 1:1-Kopien)
|
||||
|
||||
| Schicht | Pfad | Rolle |
|
||||
|---------|------|--------|
|
||||
| **Kanon / Superset** | [`../../vorlagen/`](../../vorlagen/) | kopierfertige Minima: Permissions, MCP, Hooks, AGENTS, Plugin, User |
|
||||
| **Snippet** | diese Dateien hier | kurze, kapitelnahe Ausschnitte zum Einfügen |
|
||||
| **Lebend** | [`../todo-cli/`](../todo-cli/) | Mini-Repo mit vollem `.grok/`-Harness — Instantiierung, kann leicht von Vorlagen abweichen |
|
||||
|
||||
Bei Schema-Fragen gilt die Vorlage (und der User-Guide); todo-cli ist Lern- und Demo-Material.
|
||||
|
||||
## Dateien hier
|
||||
|
||||
| Datei | Zweck |
|
||||
|-------|--------|
|
||||
| [`github-actions-grok-review.yml`](github-actions-grok-review.yml) | Headless PR-Review (Kapitel 10 / 15) |
|
||||
| [`mcp.filesystem.beispiel.toml`](mcp.filesystem.beispiel.toml) | Eng gescopter filesystem-MCP |
|
||||
|
||||
Siehe auch [`../../vorlagen/`](../../vorlagen/).
|
||||
| [`config.project.beispiel.toml`](config.project.beispiel.toml) | Schlankes Project-`.grok/config.toml` (permission); ausführlicher: `vorlagen/config.permissions.toml` |
|
||||
| [`mcp.filesystem.beispiel.toml`](mcp.filesystem.beispiel.toml) | Eng gescopter filesystem-MCP; Überblick: `vorlagen/config.mcp.toml` |
|
||||
@@ -18,7 +18,7 @@ deny = [
|
||||
"Bash(wget *)",
|
||||
"Bash(git push --force *)",
|
||||
"Bash(git push -f *)",
|
||||
"Read(.env)",
|
||||
"Read(.env.*)",
|
||||
"Read(secrets/**)",
|
||||
"Read(**/.env)",
|
||||
"Read(**/.env.*)",
|
||||
"Read(**/secrets/**)",
|
||||
]
|
||||
@@ -3,8 +3,9 @@
|
||||
Vollständige englische Prompts für **Gemini / Imagen** zur Bildgenerierung.
|
||||
|
||||
**Buch:** *Grok Build – Der umfassende Praxisleitfaden. Der Coding-Agent von xAI richtig einsetzen*
|
||||
**Autor:** Martin Richardt · **Stand:** 2026-07-28
|
||||
**Source of Truth (Inventar):** [`../../abbildungen.md`](../../abbildungen.md)
|
||||
**Autor:** Martin Richardt · **Stand:** 2026-08-02
|
||||
**Source of Truth (Inventar):** [`../../abbildungen.md`](../../abbildungen.md)
|
||||
**Gesamtliste mit Vollprompts:** [`LISTE.md`](LISTE.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -13,6 +14,7 @@ Vollständige englische Prompts für **Gemini / Imagen** zur Bildgenerierung.
|
||||
```text
|
||||
prompts/gemini/
|
||||
├── README.md ← diese Datei
|
||||
├── LISTE.md ← Gesamtliste: Anker + Caption + englische Vollprompts
|
||||
├── _global-style.txt ← gemeinsamer Stil (optional voranstellen)
|
||||
├── cover/ ← Titelseite / Cover
|
||||
│ ├── COVER-01A-text-free.txt (empfohlen, textfrei)
|
||||
@@ -127,5 +129,6 @@ python3 scripts/convert_illustrations.py
|
||||
|
||||
## Pflege
|
||||
|
||||
- Inhaltliche Änderungen an Prompts: hier **und** in `abbildungen.md` synchron halten.
|
||||
- Inhaltliche Änderungen an Prompts: Einzeldatei unter `key|must|should|nice|cover/` **und** bei Bedarf `LISTE.md` (All-in-one-Spiegel) und `abbildungen.md` (Inventar/Captions).
|
||||
- Neue Figur: Prompt-Datei anlegen + Zeile in `abbildungen.md` + diese README-Tabelle + `convert_illustrations.py` TARGETS.
|
||||
- Drop-Zone ist nur **`bilder/`** (nicht `pics/`); Produktion: `assets/illustrations/` und `assets/cover/`.
|
||||
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="common.css"/>
|
||||
<style>
|
||||
.tree { flex: 1.4; display: flex; flex-direction: column; justify-content: center; }
|
||||
.side { flex: 0.9; display: flex; flex-direction: column; gap: 16px; justify-content: center; }
|
||||
.nest { margin-left: 28px; border-left: 3px solid #c9d4e3; padding-left: 18px; }
|
||||
.nest .nest { margin-left: 20px; }
|
||||
.path { font-family: "SF Mono", Menlo, Consolas, monospace; font-size: 15px; color: var(--muted); }
|
||||
.glow-parent { background: rgba(46,196,255,0.06); border: 1px solid rgba(46,196,255,0.25); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>Nested AGENTS.md discovery</h1>
|
||||
<p class="subtitle">Tighter rules win where the agent works</p>
|
||||
<div class="stage">
|
||||
<div class="card tree">
|
||||
<div class="label" style="margin-bottom:12px">Monorepo tree</div>
|
||||
<div class="file-row glow-parent">
|
||||
<div class="icon-doc"></div>
|
||||
<div>
|
||||
<div class="mono">AGENTS.md</div>
|
||||
<div class="path">repo root</div>
|
||||
</div>
|
||||
<span class="chip" style="margin-left:auto">global Do / Don’t</span>
|
||||
</div>
|
||||
<div class="nest">
|
||||
<div class="path" style="margin:6px 0 4px">apps/web/</div>
|
||||
<div class="file-row glow-parent">
|
||||
<div class="icon-doc"></div>
|
||||
<div>
|
||||
<div class="mono">AGENTS.md</div>
|
||||
<div class="path">apps/web</div>
|
||||
</div>
|
||||
<span class="chip violet" style="margin-left:auto">stack + commands</span>
|
||||
</div>
|
||||
<div class="nest">
|
||||
<div class="path" style="margin:6px 0 4px">apps/web/src/</div>
|
||||
<div class="file-row hot">
|
||||
<div class="icon-doc"></div>
|
||||
<div>
|
||||
<div class="mono">AGENTS.md</div>
|
||||
<div class="path">nearest wins</div>
|
||||
</div>
|
||||
<div class="agent" title="agent" style="margin-left:auto"></div>
|
||||
</div>
|
||||
<div class="file-row dim">
|
||||
<div class="icon-doc" style="opacity:0.5"></div>
|
||||
<div>
|
||||
<div class="mono" style="color:var(--muted)">local.notes</div>
|
||||
<div class="path">ignored</div>
|
||||
</div>
|
||||
<span class="chip deny" style="margin-left:auto">gitignore</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="side">
|
||||
<div class="card">
|
||||
<div class="label">Discovery</div>
|
||||
<p class="note" style="margin-top:12px;font-size:20px;color:var(--ink)">
|
||||
Walk up from the working directory. Load every AGENTS.md; closer files refine the compass.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card" style="display:flex;flex-direction:column;gap:12px">
|
||||
<span class="chip cyan">root = global Do / Don’t</span>
|
||||
<span class="chip violet">package = stack + commands</span>
|
||||
<span class="chip">src = local overrides</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="common.css"/>
|
||||
<style>
|
||||
.map { flex: 1.5; display: flex; gap: 18px; }
|
||||
.rules { flex: 0.85; display: flex; flex-direction: column; gap: 14px; }
|
||||
.spot {
|
||||
position: absolute; inset: 0; border-radius: 14px; pointer-events: none;
|
||||
background: radial-gradient(ellipse at 50% 40%, rgba(46,196,255,0.35), transparent 70%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>Path-scoped rules light up where you work</h1>
|
||||
<p class="subtitle">Spotlight, not a novel — .grok/rules/*.md</p>
|
||||
<div class="stage">
|
||||
<div class="map">
|
||||
<div class="region">
|
||||
<div class="region-title">frontend/</div>
|
||||
<div class="note">UI components · routes</div>
|
||||
</div>
|
||||
<div class="region">
|
||||
<div class="region-title">backend/</div>
|
||||
<div class="note">API · store</div>
|
||||
</div>
|
||||
<div class="region lit">
|
||||
<div class="spot"></div>
|
||||
<div class="region-title">tests/</div>
|
||||
<div class="note">pytest · fixtures</div>
|
||||
<div style="margin-top:28px;display:flex;align-items:center;gap:12px">
|
||||
<div class="agent"></div>
|
||||
<span class="chip cyan">agent here</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rules card">
|
||||
<div class="label" style="margin-bottom:8px">.grok/rules/</div>
|
||||
<div class="rule-card on">
|
||||
<div class="mono">testing.md</div>
|
||||
<div class="note" style="margin-top:4px">spotlight on tests/</div>
|
||||
</div>
|
||||
<div class="rule-card off">
|
||||
<div class="mono">security.md</div>
|
||||
<div class="note" style="margin-top:4px">dim until sensitive paths</div>
|
||||
</div>
|
||||
<div class="rule-card off">
|
||||
<div class="mono">frontend.md</div>
|
||||
<div class="note" style="margin-top:4px">idle</div>
|
||||
</div>
|
||||
<div class="footer-row">
|
||||
<span class="chip">path-scoped</span>
|
||||
<span class="chip cyan">not always-on</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="common.css"/>
|
||||
<style>
|
||||
.lanes { flex: 1; display: flex; flex-direction: column; gap: 18px; }
|
||||
.lane-row { display: flex; gap: 20px; flex: 1; }
|
||||
.trunk {
|
||||
height: 72px; border-radius: 16px; background: var(--navy);
|
||||
color: #fff; display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 28px; font-weight: 700; font-size: 20px;
|
||||
}
|
||||
.pr {
|
||||
background: rgba(46,196,255,0.2); color: #fff; border-radius: 999px;
|
||||
padding: 8px 16px; font-size: 15px; font-weight: 650;
|
||||
}
|
||||
.anti {
|
||||
width: 280px; border: 2px dashed var(--deny); border-radius: 16px;
|
||||
padding: 16px; background: rgba(194,59,74,0.06);
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 10px; text-align: center;
|
||||
}
|
||||
.pill { padding: 10px 14px; border-radius: 10px; background: var(--panel); font-weight: 650; font-size: 16px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>Parallel tracks without collisions</h1>
|
||||
<p class="subtitle">Research parallel · writes isolated · merge with review</p>
|
||||
<div class="stage" style="flex-direction:column">
|
||||
<div class="lane-row">
|
||||
<div class="track a" style="flex:1">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span class="chip cyan">Track A</span>
|
||||
<span class="mono">feat-auth</span>
|
||||
</div>
|
||||
<div class="pill">Session A · worktree</div>
|
||||
<div class="note">isolated files on disk</div>
|
||||
</div>
|
||||
<div class="track b" style="flex:1">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span class="chip violet">Track B</span>
|
||||
<span class="mono">fix-flaky</span>
|
||||
</div>
|
||||
<div class="pill">Session B · worktree</div>
|
||||
<div class="note">separate branch checkout</div>
|
||||
</div>
|
||||
<div class="anti">
|
||||
<span class="x-badge">✕</span>
|
||||
<strong style="font-size:17px">Anti-pattern</strong>
|
||||
<span class="note">two pens, one working tree</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trunk">
|
||||
<span>shared remote · main</span>
|
||||
<div style="display:flex;gap:12px;align-items:center">
|
||||
<span class="pr">PR gate</span>
|
||||
<span class="chip ok" style="background:#fff;color:var(--ok)">human review</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-row">
|
||||
<span class="chip cyan">research parallel</span>
|
||||
<span class="chip violet">writes isolated</span>
|
||||
<span class="chip ok">merge with review</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="common.css"/>
|
||||
<style>
|
||||
.pipe { flex: 1; display: flex; align-items: stretch; gap: 8px; }
|
||||
.gate-title { font-size: 20px; font-weight: 750; }
|
||||
.ex { font-size: 14px; color: var(--muted); font-weight: 600; }
|
||||
.stop {
|
||||
position: absolute; top: 10px; right: 10px;
|
||||
background: var(--deny); color: #fff; font-size: 12px; font-weight: 800;
|
||||
padding: 4px 8px; border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>Authorization pipeline before every tool call</h1>
|
||||
<p class="subtitle">Mode → Rules → Trust → Sandbox → Execute</p>
|
||||
<div class="stage" style="flex-direction:column">
|
||||
<div style="display:flex;align-items:center;gap:16px;margin-bottom:8px">
|
||||
<div class="packet">tool call</div>
|
||||
<span class="note" style="font-size:18px">packet travels left → right; deny stops; ask pauses</span>
|
||||
</div>
|
||||
<div class="pipe">
|
||||
<div class="gate">
|
||||
<div class="gate-box active">
|
||||
<div class="gate-num">1</div>
|
||||
<div class="gate-title">Mode</div>
|
||||
<div class="ex">Plan · Normal · Always</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="gate">
|
||||
<div class="gate-box">
|
||||
<div class="stop">deny</div>
|
||||
<div class="gate-num">2</div>
|
||||
<div class="gate-title">Permission rules</div>
|
||||
<div class="ex">deny rm · allow pytest</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="gate">
|
||||
<div class="gate-box">
|
||||
<div class="gate-num">3</div>
|
||||
<div class="gate-title">Folder trust</div>
|
||||
<div class="ex">trusted?</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="gate">
|
||||
<div class="gate-box">
|
||||
<div class="gate-num">4</div>
|
||||
<div class="gate-title">Sandbox</div>
|
||||
<div class="ex">OS fence</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="gate">
|
||||
<div class="gate-box" style="border-color:var(--ok);box-shadow:0 0 0 4px rgba(42,157,110,0.15)">
|
||||
<div class="gate-num" style="background:var(--ok)">5</div>
|
||||
<div class="gate-title">Execute tool</div>
|
||||
<div class="ex">run</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-row" style="margin-top:28px">
|
||||
<span class="chip deny">any red deny stops</span>
|
||||
<span class="chip warn">ask = human key</span>
|
||||
<span class="chip ok">only then execute</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="common.css"/>
|
||||
<style>
|
||||
.stack { flex: 1; display: flex; flex-direction: column; gap: 18px; justify-content: center; }
|
||||
.layer-body { flex: 1; }
|
||||
.layer-title { font-size: 24px; font-weight: 750; margin-bottom: 6px; }
|
||||
.flow-arrow {
|
||||
text-align: center; color: var(--cyan); font-size: 22px; font-weight: 700; height: 12px; line-height: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>Laptop policy, CI review, human merge</h1>
|
||||
<p class="subtitle">Local rules travel with the repo — ownership stays human</p>
|
||||
<div class="stage">
|
||||
<div class="stack">
|
||||
<div class="layer">
|
||||
<div class="layer-badge" style="background:var(--cyan)">1</div>
|
||||
<div class="layer-body">
|
||||
<div class="layer-title">Developer laptop</div>
|
||||
<div class="note" style="font-size:18px">
|
||||
<span class="mono">.grok/config.toml</span> deny rules · <span class="mono">AGENTS.md</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="chip cyan">local policy</span>
|
||||
</div>
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="layer">
|
||||
<div class="layer-badge" style="background:var(--violet)">2</div>
|
||||
<div class="layer-body">
|
||||
<div class="layer-title">CI runner</div>
|
||||
<div class="note" style="font-size:18px">
|
||||
<span class="mono">grok -p</span> headless · read-only or yolo + deny
|
||||
</div>
|
||||
</div>
|
||||
<span class="chip violet">automated review</span>
|
||||
</div>
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="layer">
|
||||
<div class="layer-badge" style="background:var(--navy)">3</div>
|
||||
<div class="layer-body">
|
||||
<div class="layer-title">Human merge gate</div>
|
||||
<div class="note" style="font-size:18px">PR check · approve or reject</div>
|
||||
</div>
|
||||
<span class="chip ok">human ownership</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="width:360px;display:flex;flex-direction:column;justify-content:center;gap:16px">
|
||||
<span class="chip warn">headless needs a permission plan</span>
|
||||
<p class="note" style="font-size:18px;color:var(--ink)">
|
||||
Same deny baseline on laptop and CI. Headless never invents a safety story.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="common.css"/>
|
||||
<style>
|
||||
.cols { flex: 1; display: flex; gap: 28px; }
|
||||
.col { flex: 1; display: flex; flex-direction: column; gap: 16px; }
|
||||
.flake {
|
||||
display: inline-block; padding: 6px 12px; margin: 4px;
|
||||
border-radius: 8px; background: rgba(90,107,125,0.12);
|
||||
color: var(--muted); font-size: 14px; font-weight: 600;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.survivor {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
padding: 14px 16px; border-radius: 12px;
|
||||
background: #fff; border: 1.5px solid var(--ok);
|
||||
font-weight: 650; font-size: 17px;
|
||||
}
|
||||
.dial {
|
||||
width: 120px; height: 120px; border-radius: 50%;
|
||||
border: 8px solid var(--line); border-top-color: var(--violet);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-weight: 700; font-size: 15px; text-align: center; color: var(--muted);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>Compact saves window — files save decisions</h1>
|
||||
<p class="subtitle">Lossy summary vs. durable notes on disk</p>
|
||||
<div class="stage">
|
||||
<div class="cols">
|
||||
<div class="col card">
|
||||
<div class="label">Context window</div>
|
||||
<div style="margin-top:12px">
|
||||
<div class="note" style="margin-bottom:6px">before</div>
|
||||
<div class="bar"><i style="width:92%"></i></div>
|
||||
</div>
|
||||
<div style="margin-top:16px">
|
||||
<div class="note" style="margin-bottom:6px">after <span class="mono">/compact</span></div>
|
||||
<div class="bar"><i style="width:38%"></i></div>
|
||||
</div>
|
||||
<div style="margin-top:20px">
|
||||
<div class="note" style="margin-bottom:8px">fading detail (lossy)</div>
|
||||
<div>
|
||||
<span class="flake">old tool logs</span>
|
||||
<span class="flake">side paths</span>
|
||||
<span class="flake">verbose diffs</span>
|
||||
<span class="flake">chat asides</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col card">
|
||||
<div class="label">Survivors on disk</div>
|
||||
<div class="survivor"><div class="icon-doc"></div><span class="mono">plan.md</span></div>
|
||||
<div class="survivor"><div class="icon-doc"></div><span class="mono">AGENTS.md</span></div>
|
||||
<div class="survivor"><div class="icon-doc"></div><span>decision notes</span></div>
|
||||
<div class="survivor"><div class="icon-doc"></div><span>test output</span></div>
|
||||
<span class="chip ok" style="align-self:flex-start;margin-top:8px">write it down</span>
|
||||
</div>
|
||||
<div class="col card" style="flex:0.7;align-items:center;justify-content:center;text-align:center;gap:18px">
|
||||
<div class="dial">rewind</div>
|
||||
<div>
|
||||
<strong style="font-size:18px">time travel</strong>
|
||||
<p class="note" style="margin-top:8px">≠ compact summary</p>
|
||||
</div>
|
||||
<span class="chip violet">separate tools</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,323 @@
|
||||
/* Shared book-illustration style: night navy / cyan / soft violet */
|
||||
:root {
|
||||
--navy: #0b1b2b;
|
||||
--navy-mid: #132a40;
|
||||
--cyan: #2ec4ff;
|
||||
--violet: #8b7cff;
|
||||
--panel: #f4f7fb;
|
||||
--card: #ffffff;
|
||||
--ink: #0f1c2a;
|
||||
--muted: #5a6b7d;
|
||||
--line: #d5dde8;
|
||||
--deny: #c23b4a;
|
||||
--ok: #2a9d6e;
|
||||
--soft: #e8eef6;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||
color: var(--ink);
|
||||
background: linear-gradient(135deg, #eef3f9 0%, #f7f9fc 55%, #e8e4f8 100%);
|
||||
}
|
||||
.page {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
padding: 56px 72px 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.page::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(ellipse 40% 30% at 0% 0%, rgba(46,196,255,0.12), transparent 60%),
|
||||
radial-gradient(ellipse 35% 35% at 100% 100%, rgba(139,124,255,0.14), transparent 55%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.page > * { position: relative; z-index: 1; }
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
font-weight: 750;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 36px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.stage {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 28px;
|
||||
align-items: stretch;
|
||||
min-height: 0;
|
||||
}
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 12px 40px rgba(11,27,43,0.08), 0 2px 8px rgba(11,27,43,0.04);
|
||||
border: 1px solid rgba(11,27,43,0.06);
|
||||
padding: 24px 28px;
|
||||
}
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
background: var(--soft);
|
||||
color: var(--navy);
|
||||
}
|
||||
.chip.cyan { background: rgba(46,196,255,0.18); color: #0a4d6b; }
|
||||
.chip.violet { background: rgba(139,124,255,0.18); color: #3d3480; }
|
||||
.chip.ok { background: rgba(42,157,110,0.15); color: #145c3d; }
|
||||
.chip.deny { background: rgba(194,59,74,0.12); color: #8a2030; }
|
||||
.chip.warn { background: rgba(220,160,40,0.18); color: #7a5400; }
|
||||
.label {
|
||||
font-size: 15px;
|
||||
font-weight: 650;
|
||||
color: var(--muted);
|
||||
text-transform: none;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.mono {
|
||||
font-family: "SF Mono", "Menlo", "Consolas", monospace;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.agent {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(145deg, var(--cyan), var(--violet));
|
||||
box-shadow: 0 6px 18px rgba(46,196,255,0.35);
|
||||
position: relative;
|
||||
}
|
||||
.agent::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 14px 14px 18px;
|
||||
border-radius: 50%;
|
||||
background: var(--navy);
|
||||
opacity: 0.85;
|
||||
}
|
||||
.folder {
|
||||
border-left: 3px solid var(--line);
|
||||
padding-left: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.file-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 12px;
|
||||
margin: 8px 0;
|
||||
background: var(--panel);
|
||||
}
|
||||
.file-row.hot {
|
||||
background: rgba(46,196,255,0.16);
|
||||
border: 1.5px solid var(--cyan);
|
||||
box-shadow: 0 0 0 4px rgba(46,196,255,0.12);
|
||||
}
|
||||
.file-row.dim {
|
||||
opacity: 0.45;
|
||||
}
|
||||
.icon-doc {
|
||||
width: 28px;
|
||||
height: 34px;
|
||||
background: #fff;
|
||||
border: 2px solid var(--navy-mid);
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.icon-doc::before {
|
||||
content: "md";
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
color: var(--cyan);
|
||||
}
|
||||
.gate {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.gate-box {
|
||||
width: 100%;
|
||||
min-height: 140px;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
|
||||
border: 2px solid var(--line);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 16px 12px;
|
||||
position: relative;
|
||||
}
|
||||
.gate-box.active {
|
||||
border-color: var(--cyan);
|
||||
box-shadow: 0 0 0 4px rgba(46,196,255,0.15);
|
||||
}
|
||||
.gate-num {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: var(--navy);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.arrow {
|
||||
color: var(--cyan);
|
||||
font-size: 32px;
|
||||
font-weight: 300;
|
||||
align-self: center;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.track {
|
||||
flex: 1;
|
||||
border-radius: 18px;
|
||||
padding: 22px;
|
||||
background: var(--card);
|
||||
border: 2px solid var(--line);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
.track.a { border-top: 5px solid var(--cyan); }
|
||||
.track.b { border-top: 5px solid var(--violet); }
|
||||
.layer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 28px;
|
||||
padding: 22px 28px;
|
||||
border-radius: 18px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
box-shadow: 0 8px 24px rgba(11,27,43,0.06);
|
||||
}
|
||||
.layer-badge {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 800;
|
||||
font-size: 22px;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.bar {
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
background: var(--soft);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.bar > i {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--cyan), var(--violet));
|
||||
}
|
||||
.note {
|
||||
font-size: 16px;
|
||||
color: var(--muted);
|
||||
line-height: 1.35;
|
||||
}
|
||||
.footer-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.region {
|
||||
flex: 1;
|
||||
border-radius: 16px;
|
||||
padding: 18px;
|
||||
background: var(--panel);
|
||||
border: 2px dashed var(--line);
|
||||
min-height: 220px;
|
||||
position: relative;
|
||||
}
|
||||
.region.lit {
|
||||
border-style: solid;
|
||||
border-color: var(--cyan);
|
||||
background: rgba(46,196,255,0.08);
|
||||
box-shadow: inset 0 0 40px rgba(46,196,255,0.12);
|
||||
}
|
||||
.region-title {
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
font-family: "SF Mono", Menlo, Consolas, monospace;
|
||||
}
|
||||
.rule-card {
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.rule-card.on {
|
||||
border-color: var(--cyan);
|
||||
box-shadow: 0 0 0 3px rgba(46,196,255,0.2);
|
||||
background: linear-gradient(180deg, #fff, rgba(46,196,255,0.08));
|
||||
}
|
||||
.rule-card.off {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.x-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--deny);
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
font-size: 14px;
|
||||
}
|
||||
.packet {
|
||||
width: 72px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--navy), var(--navy-mid));
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 6px 16px rgba(11,27,43,0.25);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Render S-02…S-07 HTML diagrams → bilder/ PNGs (Chrome headless).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
OUT="${ROOT}/bilder"
|
||||
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
for id in S-02 S-03 S-04 S-05 S-06 S-07; do
|
||||
html="${DIR}/${id}.html"
|
||||
png="${OUT}/${id}-render.png"
|
||||
echo "render ${id} → ${png}"
|
||||
"$CHROME" --headless=new --disable-gpu --hide-scrollbars \
|
||||
--window-size=1920,1080 \
|
||||
--screenshot="${png}" \
|
||||
"file://${html}" 2>/dev/null
|
||||
# Chrome sometimes writes relative to CWD; normalize
|
||||
if [[ ! -f "$png" && -f "${DIR}/screenshot.png" ]]; then
|
||||
mv "${DIR}/screenshot.png" "$png"
|
||||
fi
|
||||
if [[ ! -f "$png" && -f screenshot.png ]]; then
|
||||
mv screenshot.png "$png"
|
||||
fi
|
||||
ls -la "$png"
|
||||
done
|
||||
|
||||
echo "OK: rendered to ${OUT}"
|
||||
@@ -1,12 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert bilder/ PNGs to production JPEGs (quality 90).
|
||||
"""Convert bilder/ sources to production JPEGs (quality 90).
|
||||
|
||||
Maps ID-prefixed source files to clean asset paths under
|
||||
assets/illustrations/ and assets/cover/ for *Grok Build –
|
||||
Der umfassende Praxisleitfaden*. Composites RGBA onto
|
||||
night navy (#0B1B2B) to match the book illustration palette.
|
||||
|
||||
See abbildungen.md and prompts/gemini/ for inventory and prompts.
|
||||
Drop-Zone-Kanon: bilder/ (nicht pics/). See abbildungen.md and
|
||||
prompts/gemini/ for inventory and prompts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -18,13 +19,22 @@ from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
SRC = REPO / "bilder"
|
||||
ILLUSTRATIONS = REPO / "assets" / "illustrations"
|
||||
COVER = REPO / "assets" / "cover"
|
||||
SRC_DIR = REPO / "bilder"
|
||||
|
||||
NAVY = (0x0B, 0x1B, 0x2B)
|
||||
QUALITY = 90
|
||||
|
||||
|
||||
def _resolve_src() -> Path | None:
|
||||
"""Nur bilder/ (Repo-Kanon)."""
|
||||
if not SRC_DIR.is_dir():
|
||||
return None
|
||||
if any(p.is_file() and not p.name.startswith(".") for p in SRC_DIR.iterdir()):
|
||||
return SRC_DIR
|
||||
return None
|
||||
|
||||
# Target basenames (without .jpg) by ID prefix
|
||||
TARGETS: dict[str, tuple[Path, str]] = {
|
||||
"COVER-01B": (COVER, "COVER-01B-full"),
|
||||
@@ -92,12 +102,17 @@ def convert_one(src: Path, dest_dir: Path, basename: str) -> Path:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not SRC.is_dir():
|
||||
print(f"Fehlt: {SRC}", file=sys.stderr)
|
||||
src = _resolve_src()
|
||||
if src is None:
|
||||
print(
|
||||
f"Fehlt: {SRC_DIR} mit Bilddateien (ID-Präfix, z. B. K-01-….png)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
print(f"Quelle: {src.relative_to(REPO)}/ quality={QUALITY}")
|
||||
seen: set[str] = set()
|
||||
for path in sorted(SRC.iterdir()):
|
||||
for path in sorted(src.iterdir()):
|
||||
if path.name.startswith("."):
|
||||
continue
|
||||
if not path.is_file():
|
||||
@@ -117,10 +132,13 @@ def main() -> int:
|
||||
print(f"{path.name} → {out.relative_to(REPO)} ({size_kb:.0f} KiB)")
|
||||
seen.add(key)
|
||||
|
||||
if not seen:
|
||||
print("Keine Dateien konvertiert.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
missing = set(TARGETS) - seen
|
||||
if missing:
|
||||
print(f"FEHLT: {sorted(missing)}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"Hinweis (nicht im Drop): {sorted(missing)}", file=sys.stderr)
|
||||
print(f"OK: {len(seen)} JPEGs (quality={QUALITY})")
|
||||
return 0
|
||||
|
||||
|
||||