# INSTALL.md — Setup-Pipeline für das Agentic Second Brain

Diese Datei ist die Anleitung für den ausführenden Agent (Claude Code oder Codex), nicht für den Mensch-User direkt. Sie beschreibt die vollständige Setup-Pipeline aus dreizehn nummerierten Schritten, die der Agent nach dem Lesen von BOOTSTRAP.md abarbeitet. Schritt 10 enthält eine innere Pipeline mit elf Phasen (0 bis 10), die innerhalb des Staging-Ordners `.tmp_install/` laufen. Erst Schritt 11 verschiebt den fertigen Inhalt atomar in den Ziel-Vault.

## Sicherheitsgarantien (gelten durchgängig)

- Vor Schritt 11 wird **nichts** in den Ziel-Vault geschrieben. Alles passiert in `<Ziel-Vault>/.tmp_install/`.
- Bei Crash zwischen Schritt 8 und Schritt 11: `.tmp_install/` löschen, der Ziel-Vault bleibt unverändert.
- Hash-Mismatch: harter Abbruch, kein Fallback.
- Tool-Version unterhalb Minimum: harter Abbruch mit klarer Fehlermeldung.
- Echte Umlaute (ä ö ü ß) in allen geschriebenen Dateien, kein ae/oe/ue/ss-Ersatz.

## Setup-Pipeline (13 Schritte)

### Schritt 1 — uv-Verfügbarkeit prüfen

Rufe `uv --version` auf. Wenn der Befehl fehlt oder einen Fehler liefert, brich mit folgender Meldung ab:

> uv ist nicht installiert. Siehe BOOTSTRAP.md, Abschnitt "Voraussetzung — uv installiert" für den Single-Liner-Install (macOS/Linux: `curl -sSf https://astral.sh/uv/install.sh | sh`, Windows PowerShell: `irm https://astral.sh/uv/install.ps1 | iex`).

Kein Fallback, kein Auto-Install. Der User installiert uv selbst und startet die Pipeline neu.

### Schritt 2 — User-Antworten einsammeln

**Ziel-Vault-Pfad:** Der Ziel-Vault-Pfad `<Ziel-Vault>` wurde bereits in BOOTSTRAP.md Schritt 2 explizit über `AskUserQuestion` bestätigt (Single Source of Truth) und ist die kanonische Quelle für Schritt 7 (Leer-Prüfung), Schritt 9 (Entpacken) und Schritt 11 (atomares Move). Hier wird er **nicht erneut abgefragt**. Falls der Pfad ausnahmsweise nicht vorliegt (z.B. BOOTSTRAP-Schritt übersprungen), hole ihn jetzt über `AskUserQuestion` nach, bevor du fortfährst.

Sammle die User-Antworten **über das `AskUserQuestion`-Tool, nicht als Markdown-Fließtext-Fragen**. Strukturierte Tool-Calls geben dem User klare Eingabefelder, die freie Markdown-Liste landet sonst als Chat-Nachricht und der Setup-Pause-Status ist nicht sichtbar. Wenn das Tool im aktiven Agent nicht verfügbar ist (Codex, eingeschränkte Tool-Sets): Fallback auf nummerierte Fließtext-Fragen, aber **eine Frage pro Nachricht**, mit explizitem Stop nach jeder, damit der User antworten kann.

Pflicht-Fragen (in dieser Reihenfolge):

- `<USER_NAME>`: Vollständiger Name (für AGENTS.md).
- `<USER_INTRO>`: Ein bis zwei Sätze Selbstbeschreibung (Rolle, Themen, Ziele — landet in AGENTS.md unter "Über mich").
- Theme-Profil `<profil>`: per `AskUserQuestion` abfragen. Alle 7 Profile aus `manifest.json::theme_profiles` sind installierbar (seit v0.23.0 live verifiziert). Weil `AskUserQuestion` maximal 4 Optionen je Frage erlaubt, frage zweistufig: Stufe 1 mit den Optionen `allgemein (empfohlen)`, `karpathy`, `business` und `weitere Profile anzeigen`; wählt der User die letzte Option, folgt Stufe 2 mit `content`, `automation`, `image`, `beratung`. Nutze diese Kurzbeschreibungen als Options-Beschreibungen:

  | Profil | Kurzbeschreibung |
  |---|---|
  | `allgemein` | Breit aufgestelltes Second Brain für gemischte Themen (Default, empfohlen) |
  | `karpathy` | Selbstverbesserndes Vault-System: Wochen-Audit, Curator, Reflexion, Wissensgraph-Analyse |
  | `business` | Buchhaltung und Finanzen: Belege, Rechnungen, vorbereitende Finanzbuchhaltung |
  | `content` | LinkedIn-Content-Workflow: Ideen, Entwürfe, Kommentare, Thumbnails, Carousels |
  | `automation` | Wissensautomatisierung: NotebookLM-Kuratierung, Text-zu-Sprache-Workflows |
  | `image` | KI-Bildgenerierung: Google Nano Banana, OpenAI gpt-image-2, authentische Portraits |
  | `beratung` | Beratungs-Workflows: Präsentationen, Pitch-Decks, Webseiten-Audit-Ergebnisse |

  Das gewählte Profil ist die Variable `<profil>` für die Phasen 2, 3 und 7 (`--profile`-Argument), Phase 4.5 (`external_global_recommendations`-Lookup) und das Installation-Log (Phase 10).

`AskUserQuestion`-Schema-Beispiel:

```json
{
  "questions": [
    {"question": "Vollständiger Name (für AGENTS.md):", "header": "USER_NAME", "multiSelect": false},
    {"question": "Ein bis zwei Sätze Selbstbeschreibung (Rolle, Themen, Ziele):", "header": "USER_INTRO", "multiSelect": false},
    {"question": "Welches Theme-Profil soll installiert werden?", "header": "Theme-Profil", "multiSelect": false,
     "options": [
       {"label": "allgemein (Empfohlen)", "description": "Breit aufgestelltes Second Brain für gemischte Themen"},
       {"label": "karpathy", "description": "Selbstverbesserndes Vault-System: Wochen-Audit, Curator, Reflexion, Wissensgraph-Analyse"},
       {"label": "business", "description": "Buchhaltung und Finanzen: Belege, Rechnungen, vorbereitende Finanzbuchhaltung"},
       {"label": "weitere Profile anzeigen", "description": "content, automation, image, beratung (Stufe 2)"}
     ]}
  ]
}
```

### Schritt 3 — Migration-Modus abfragen

Frage den User explizit:

> Möchtest du Inhalte aus einem bestehenden Vault übernehmen (Migration)?

Im B.1-MVP ist das Migration-Skill `default_active: false`. Der User wählt also implizit "Nein". Stelle die Frage trotzdem, damit klar ist, dass diese Funktion ab Welle 2 aktiviert wird. Bei "Ja" weise darauf hin, dass die Migration in B.1 noch nicht aktiv ist und ab Welle 2 via `/skill-activate migration` zur Verfügung steht. Setze die Migration-Auswahl auf "Nein" und fahre fort.

### Schritt 4 — Bundle-URL aus BOOTSTRAP.md lesen

BOOTSTRAP.md ist die Single Source of Truth für Bundle-URL und SHA-256. Lies aus der bereits vorhandenen BOOTSTRAP.md im Ziel-Ordner:
- Bundle-URL (z.B. `https://bootstrap.lehnisch.de/<version>/bundle.zip`)
- SHA-256-Hash des Bundles

Keine andere Quelle. Wenn BOOTSTRAP.md fehlt oder die URL nicht enthält, brich ab.

### Schritt 5 — SHA-256 prüfen

Lade das Bundle (`bundle.zip`) von der URL aus Schritt 4. Berechne den SHA-256-Hash der heruntergeladenen Datei. Vergleiche mit dem Hash aus BOOTSTRAP.md.

Bei Mismatch: harter Abbruch mit Meldung
> SHA-256-Mismatch. Erwartet `<hash-aus-bootstrap>`, berechnet `<hash-vom-download>`. Kein Fallback. Bitte BOOTSTRAP.md prüfen oder Bundle erneut laden.

### Schritt 6 — Sidecar-Verifikation (optional, empfohlen)

Versuche, neben dem Bundle die Datei `bundle.zip.sha256` von der gleichen Hosting-URL zu laden (z.B. `https://bootstrap.lehnisch.de/<version>/bundle.zip.sha256`). Wenn vorhanden: vergleiche den Sidecar-Hash gegen den Hash aus BOOTSTRAP.md. Beide müssen identisch sein.

Bei Mismatch zwischen Sidecar und BOOTSTRAP.md: harter Abbruch, weil entweder Hosting oder BOOTSTRAP.md kompromittiert ist. Wenn der Sidecar nicht verfügbar ist (404): kein Fehler, Schritt 6 wird übersprungen. Die SHA-256-Prüfung aus Schritt 5 bleibt verbindlich.

### Schritt 7 — Ziel-Vault-Ordner prüfen

Prüfe, ob der Ziel-Vault-Ordner leer ist oder nur OBSIDIAN-spezifische Dateien enthält (`.obsidian/` ist erlaubt, ebenso die Setup-Artefakte `BOOTSTRAP.md`, `bundle.zip` und `bundle.zip.sha256` aus den Schritten 4–6 — sie werden in Schritt 12b aufgeräumt). Andere Inhalte: harter Abbruch mit Meldung
> Ziel-Vault `<Pfad>` ist nicht leer. Setup nur auf leere Vaults oder auf Ordner mit ausschließlich `.obsidian/` und den Setup-Artefakten (`BOOTSTRAP.md`, `bundle.zip`, `bundle.zip.sha256`). Lege den Vault leer an oder wähle einen anderen Pfad.

Force-Flag (Override) ist im B.1-MVP nicht implementiert.

### Schritt 8 — OneDrive-Pause-Pflicht (gehärtet nach rc1-B+)

Prüfe, ob der Ziel-Vault unterhalb eines OneDrive-, Dropbox- oder iCloud-Pfads liegt (Pfadkomponenten enthalten `OneDrive`, `Dropbox` oder `iCloud`). Wenn ja, verlange vom User explizit eine **aktive Pause-Bestätigung** über das `AskUserQuestion`-Tool, bevor du fortfährst. Eine Verweigerung ist erlaubt, aber der User muss bewusst klicken — keine Markdown-Liste mit impliziter Default-Antwort.

`AskUserQuestion`-Schema-Beispiel:

```json
{
  "questions": [
    {
      "question": "Der Ziel-Vault liegt im Cloud-Sync-Bereich (OneDrive/Dropbox/iCloud). Während des Setups landen viele Dateien in `.tmp_install/`, dazu eine .lock-Datei in der venv — das triggert reproduzierbar Sync-Konflikte (Beleg: B+-Lauf 2026-05-11). Empfehlung: Cloud-Sync jetzt pausieren.",
      "header": "Cloud-Sync pausieren?",
      "multiSelect": false,
      "options": ["pausiert (empfohlen)", "weiter ohne Pause (Konflikte werden geloggt)"]
    }
  ]
}
```

Bei „pausiert": auf Bestätigung warten, dass Pause aktiv ist (User-Antwort plus optional Sichtkontrolle, ob OneDrive-Icon „pausiert" zeigt). Bei „weiter ohne Pause": fortfahren, aber im installation-log explizit den Flag `cloud_sync_paused: false` setzen, damit der Drift-Check beim ersten Session-Start weiß, dass mtime-Verschiebungen ein erwartbarer Fall sind. Wenn der Vault nicht unter Cloud-Sync liegt, überspringe Schritt 8 ohne Frage.

Fallback ohne `AskUserQuestion`-Tool (Codex, eingeschränkte Agents): eine einzelne klare Frage als Chat-Nachricht, **eine** Antwort abwarten, kein Fließtext-Fragen-Block.

Dieser Schritt liegt bewusst **vor** Schritt 9 (Entpacken), also vor dem ersten Schreiben auf Disk. So passiert das `.tmp_install/`-Befüllen ohne aktive Sync-Engine.

### Schritt 9 — Bundle nach `.tmp_install/` entpacken

Entpacke das ZIP nach `<Ziel-Vault>/.tmp_install/`. **`.tmp_install/` MUSS IM Ziel-Vault-Ordner liegen**, nicht unter `C:\temp\`, nicht im User-Home-Verzeichnis, nicht woanders.

**UTF-8-sichere Entpack-Mechanik (Pflicht, gehärtet nach v0.19.4-Befund 3):**

Verwende ausschließlich eine UTF-8-flag-respektierende Entpack-Mechanik. Default:

```python
import zipfile
with zipfile.ZipFile(bundle_zip_path) as zf:
    zf.extractall(staging)
```

Python `zipfile` (CPython 3.8+) respektiert den UTF-8-Flag (`0x800`) im ZIP-Eintrag
und entpackt Dateinamen wie `07 Anhänge` korrekt mit echtem `ä`.

Alternativen mit gleicher UTF-8-Sicherheit:
- PowerShell `Expand-Archive` (Windows 10+).
- `7z x bundle.zip` (7-Zip).

**Explizit verboten:** Git-Bash-`unzip` (InfoZIP) auf Windows. Das Tool ignoriert
den UTF-8-Flag und schreibt Dateinamen in der Windows-Codepage (cp1252), wodurch
`07 Anhänge` als `07 Anh+ñnge` auf Disk landet — Vault-Hartregel-Verstoß.

**Volume-Check via Pfad-Identität (Pflicht):** Vor dem Entpacken verifiziere mit Python-Code-Logik:

```python
from pathlib import Path
staging = Path(target_vault) / ".tmp_install"
assert staging.resolve().parent == Path(target_vault).resolve(), \
    "Staging-Ordner muss direkt im Ziel-Vault liegen (atomares Move erfordert gleiches Volume)"
```

Hintergrund: Auf Windows ist `os.rename()` nicht atomar bei Cross-Volume-Operationen. OneDrive-Ordner sind keine eigenen Volumes (deshalb Pfad-Identität statt `os.stat().st_dev`), aber ein Rename von `C:\temp\` in einen Vault auf `C:\Users\...` wäre cross-volume und damit ein impliziter Copy+Delete. Das atomare Move in Schritt 11 würde still brechen.

Bei Volume-Verstoß: harter Abbruch.

### Schritt 10 — Inhalts-Pipeline (Phasen 0 bis 10 IM STAGING-Ordner)

Innerhalb von `.tmp_install/` führe folgende elf Phasen sequenziell aus. Alle Schreiboperationen passieren in `.tmp_install/`, niemals direkt im Ziel-Vault.

#### Phase 0 — Migration anwenden (oder skippen)

Wenn der User in Schritt 3 "Ja" gewählt hätte UND das Migration-Skill aktiv wäre: Migration-Workflow aus `.skills/migration/` ausführen, Kandidaten-Auswahl in `.tmp_install/migration-selection.json` schreiben.

**Im B.1-MVP:** Migration ist `default_active: false`, daher Phase 0 wird übersprungen. Schreibe `migration: skipped` ins spätere Installation-Log (Phase 10).

#### Phase 1 — Frontmatter-Validierung der Starter-Notizen

Validiere ausschließlich die Dateien, die später als Vault-Notizen landen: alle `.md`-Dateien unter `.tmp_install/.templates/<baum>/PARA/` (über alle ausgelieferten Template-Bäume). Prüfe deren Frontmatter gegen die Pflichtfelder (`type`, `description`, `agent_ready`, `schema_version`, `tags`, `created`). Bei Verstoß: harter Abbruch mit Pfadangabe und fehlendem Feld. Das Bundle ist defekt, kein lokaler Fix möglich.

**Explizit NICHT validieren** (kein Abbruchgrund, diese Dateien folgen bewusst anderen Schemata):
- Template-Root-Steuerdateien `.templates/<baum>/AGENTS.md`, `CLAUDE.md`, `INDEX.md` — konstruktionsbedingt ohne Frontmatter.
- Alles unter `.tmp_install/.skills/` — `SKILL.md` folgt dem Skill-Schema, `references/`, `eval/` und Scripts sind Hilfsdateien. Deren Integrität sichert die SHA-256-Kette aus Schritt 5, nicht die Notiz-Validierung.

#### Phase 2 — PARA-Struktur und Starter-Notizen anlegen

Materialisiere die PARA-Ordner und etwaige Profil-Starter-Notizen deterministisch über `sync.py --setup-para`. **Nicht selbst Ordner anlegen oder Dateien kopieren** — der Subcommand ist die Single-Source-Mechanik (atomares Schreiben, identisch zu `--setup-root`).

```bash
uv run .tmp_install/.skills/sync.py \
  --vault .tmp_install \
  --setup-para \
  --profile <profil>
```

Erwartetes Verhalten: Der Subcommand löst das Profil über `theme_profiles.<profil>.templates` in `manifest.json` auf seinen Template-Baum auf (z.B. `allgemein` → `default`, `beratung` → `beratung`) und kopiert den kompletten Baum aus `.tmp_install/.templates/<baum>/PARA/` (Ordner und Dateien) ins Staging-Root `.tmp_install/`. Für Profile ohne Starter-Notizen (z.B. `allgemein`) entstehen die zehn Wurzelordner (PARA 00–08 plus `TaskNotes/`) mit je einer `.gitkeep`. Für Profile mit Starter-Notizen (z.B. `beratung`) kommen die Notizen unter ihren PARA-Ordnern mit. Echte Umlaute beachten — `07 Anhänge` exakt mit ä.

Erwartete Stdout-Zeile: `setup-para: written=[...]` mit der Liste der geschriebenen relativen Pfade.

#### Phase 3 — AGENTS.md, CLAUDE.md, INDEX.md schreiben

Schreibe die Vault-Root-Steuerdateien deterministisch über `sync.py --setup-root`. **Nicht selbst Templates kopieren oder substituieren** — der Subcommand ist die Single-Source-Mechanik für Substitution + atomares Schreiben.

1. **Antworten-Datei anlegen** unter `.tmp_install/.docs/answers.json` aus den Antworten aus Schritt 2 (das Verzeichnis `.tmp_install/.docs/` dabei anlegen, falls es noch nicht existiert — Phase 4 legt es sonst später an):

   ```json
   {"USER_NAME": "<aus Schritt 2>", "USER_INTRO": "<aus Schritt 2>"}
   ```

2. **`sync.py --setup-root` aufrufen:**

   ```bash
   uv run .tmp_install/.skills/sync.py \
     --vault .tmp_install \
     --setup-root \
     --profile <profil> \
     --answers .tmp_install/.docs/answers.json
   ```

   Erwartetes Verhalten: Der Subcommand liest die Templates aus dem aufgelösten Template-Baum (`theme_profiles.<profil>.templates`) — `.tmp_install/.templates/<baum>/AGENTS.md` (substituiert `<USER_NAME>` und `<USER_INTRO>`), `CLAUDE.md` (ohne Substitution, Stub mit Verweis auf AGENTS.md) und `INDEX.md` (ohne Substitution, Seed-Initialzustand; wird in Phase 7.5 durch ein echtes generiertes INDEX ersetzt) und schreibt sie atomar in die Vault-Wurzel `.tmp_install/`.

   Erwartete Stdout-Zeile: `setup-root: written=['AGENTS.md', 'CLAUDE.md', 'INDEX.md']`.

   Fehlerfälle:
   - Exit-Code 1 mit `install_root_files: Pflicht-Marker <USER_NAME> fehlt im Template …` → Template ist drift, harter Abbruch.
   - Exit-Code 2 mit `BLOCKED: root files already exist: …` → eine der drei Dateien existiert bereits im Staging. Tritt seit Build-Test in v0.19.4 nicht mehr auf (Maintainer-Variante der Root-`AGENTS.md` ist via `.bundleignore` aus dem Bundle gesperrt); wenn doch, ist `.tmp_install/` nicht sauber oder der User hat manuell eine Datei am Ziel angelegt. `--force` erst auf explizite User-Bestätigung setzen.

Der hier geschriebene INDEX.md ist nur ein Seed. Er wird im Setup automatisch durch ein echtes generiertes INDEX ersetzt: Phase 7.5 ruft `build_index.py --vault .tmp_install` direkt auf. Der `/index-rebuild`-Slash-Command für spätere manuelle Regenerierung kommt zusätzlich in Welle 2.

Keine `README.md` im Vault-Root — es gibt kein Template dafür und der Vault braucht keine. Repo-README (`README.md` im Bundle-Repo) ist seit F6 in `.bundleignore` und kommt nicht mehr ins Bundle.

#### Phase 4 — Hilfsverzeichnisse anlegen

Lege folgende leere Verzeichnisse in `.tmp_install/` an:

- `.docs/` (für Installation-Log und spätere Sync-State-Marker; existiert ggf. schon aus Phase 3 — dann nichts tun)
- `.backups/` (für künftige Auto-Backups vor Updates, FIFO max 5)

#### Phase 4.5 — Bundle-Install-Konflikt-Check (Globale Skills)

Bevor das Bundle einen empfohlenen Skill als Vault-Skill installiert, prüft der Setup-Agent, ob er bereits global vorhanden ist.

**Ablauf:**

1. Bundle-Manifest lesen: Feld `external_global_recommendations` aus `bundle.json` pro aktivem Profil.
2. Globale Skills auflisten: `ls ~/.claude/skills/` (bzw. `dir %USERPROFILE%\.claude\skills\` auf Windows).
3. Pro empfohlenem Skill:
   - Wenn global vorhanden: **keine Vault-Installation**, keine Vault-Kopie. Im `installation-log.md` vermerken: `<name> global gefunden, referenziert, nicht installiert`.
   - Wenn global nicht vorhanden, unterscheide zwei Fälle:
     - **Bundle-eigener anwender-Skill** (der Name existiert als Ordner unter `.tmp_install/.skills/<name>/` — im Bundle: `elevenlabs-api`, `nano-banana-image`, `openai-image-gen`): User per `AskUserQuestion` fragen, ob der Skill aus dem Bundle auf User-Ebene installiert werden soll. Bei „Ja" merkt sich der Setup-Agent den Namen für Phase 9. Log-Eintrag.
     - **Externe Empfehlung** (kein Bundle-Ordner, z.B. `firecrawl`, `notebooklm`): User per `AskUserQuestion` zeigen, dass der Skill empfohlen ist (Marketplace-Link, Beispiel-Befehl). User entscheidet manuell oder weiter ohne. Log-Eintrag.

**Warum:** Doppel-Installation eines Skills (global plus Vault-Scope) führt zu kollidierenden Triggern.

**Konflikt-Resolution-Tabelle:**

| Skill-Typ | Global vorhanden | Global fehlt |
|---|---|---|
| Eimer 1/2 (Vault-Skill) | Drift-Check meldet, User via `/skill-update` | Bundle installiert als Vault-Skill |
| anwender-Skill (Bundle-eigen) | Bundle referenziert nur | `AskUserQuestion` → bei „Ja" Installation in Phase 9 |
| Eimer 4 (empfohlener globaler) | Bundle referenziert nur | Installations-Hinweis, User entscheidet |
| External Plugin | Bundle referenziert nur | Marketplace-Link |

#### Phase 5 — `.skills/` ins Vault kopieren

Kopiere das komplette `.skills/`-Verzeichnis aus dem Bundle nach `.tmp_install/.skills/`. Inklusive `manifest.json`, `manifest.schema.json`, `sync.py`, `new.py` und allen Skill- und Hook-Unterordnern (Bestand laut `manifest.json`).

#### Phase 6 — Migration-Selection anwenden (oder skippen)

Wenn Phase 0 eine `migration-selection.json` erzeugt hat: kopiere die ausgewählten Dateien aus dem alten Vault in die entsprechenden PARA-Ordner unter `.tmp_install/`.

**Im B.1-MVP:** Phase 0 wurde übersprungen, daher Phase 6 wird übersprungen.

#### Phase 7 — sync.py einmalig ausführen

Rufe `uv run .tmp_install/.skills/sync.py --vault .tmp_install --profile <profil>` auf. **Das `--profile`-Argument ist Pflicht:** ohne dieses fällt `sync.py` still auf das alphabetisch erste Profil (`allgemein`) zurück und der Vault bekäme die falsche Skill-Aktivierung. Das Skript liest `manifest.json`, bestimmt die für `<profil>` aktiven Skills und für jeden Skill mit `scope=vault` die Spiegel-Ziele (`.tmp_install/.claude/skills/<name>/` für Claude Code und `.tmp_install/.agents/skills/<name>/` für Codex) und kopiert SKILL.md plus Skript-Dateien in beide. Drift-Check via mtime. Bei manipuliertem Spiegel: BLOCK mit returncode 2.

Nach Phase 7 sind `.tmp_install/.claude/skills/` (Claude Code) und `.tmp_install/.agents/skills/` (Codex) mit den vault-scope-Skills gefüllt — inhaltsgleich aus derselben SSoT `.skills/`. Codex triggert Skills automatisch per Description aus `.agents/skills/`. Die `.codex/`-Hook-Configs werden in Phase 8 separat versorgt.

#### Phase 7.5 — INDEX.md generieren

Rufe `uv run .tmp_install/.skills/index-rebuild/build_index.py --vault .tmp_install` auf. Das ersetzt den in Phase 3 geschriebenen Seed-INDEX durch ein echtes, aus dem Vault-Stand generiertes INDEX.md: Kontext, Projekte, Bereiche, Ressourcen und Routinen aus dem PARA-Baum (Phase 2), Skills aus dem in Phase 7 gefüllten `.claude/skills/`-Spiegel.

**Warum nach Phase 7:** Der Generator liest die Skills aus dem Spiegel-Verzeichnis. Liefe er vorher, bliebe die Skills-Sektion leer. PARA-Baum (Phase 2) und Root-Files (Phase 3) liegen zu diesem Zeitpunkt bereits im Staging, `bundle.json` ebenfalls — `{system}` löst korrekt auf `08 Systemsteuerung` auf.

Erwartete Stdout-Zeile: `INDEX.md written: <Pfad>/.tmp_install/INDEX.md`.

Damit startet auch ein Fremd-Nutzer mit einem populierten INDEX statt mit dem leeren Seed. Das INDEX liegt noch im Staging und wird erst in Schritt 11 atomar ins Vault-Root verschoben.

#### Phase 8 — Tool-Configs schreiben (BEIDE Tools immer)

Die Tool-Konfigurationen schreibt **`sync.py`**, nicht der Setup-Agent manuell — und zwar bereits im Phase-7-Lauf: `aggregate_hooks` konvertiert die `hook.yaml`-Definitionen aller Hooks aus `manifest.json::hooks_index`, `aggregate_mcp` aggregiert die MCP-Configs der aktiven Skills. Geschrieben werden:

- `.tmp_install/.claude/settings.json` — Hook-Definitionen ins Claude-Code-Format konvertiert. Pro Hook: `events`, `matcher`, `command`. Die Commands tragen den Präfix `"$CLAUDE_PROJECT_DIR/"` vor dem Skript-Pfad — Claude Code setzt diese Variable beim Hook-Spawn auf den absoluten Vault-Pfad; relative Pfade würden bei abweichendem Spawn-Arbeitsverzeichnis still nicht feuern. Nicht „korrigieren“.
- `.tmp_install/.codex/config.toml` — gleiche Hook-Definitionen ins Codex-TOML-Format; Hooks mit `codex: false` in der hook.yaml erscheinen dort nicht als aktive Einträge.

Hinweis: `.codex/config.toml` trägt an dieser Stelle bewusst absolute Staging-Pfade (`…/.tmp_install/…`) — das ist KEIN Fehler und wird nicht hier korrigiert. Schritt 12c regeneriert die Datei nach dem atomaren Move mit dem echten Vault-Pfad.

Phase 8 ist deshalb ein **Verifikations-Schritt**: prüfe, dass beide Dateien existieren und nicht leer sind. Fehlt eine, wiederhole Phase 7 (`sync.py --vault .tmp_install --profile <profil>`) — kein manuelles Nachschreiben, kein eigenes Hook→TOML-Konvertierungs-Skript.

**Hinweis-Pflicht:** Hooks werden erst beim NÄCHSTEN Session-Start aktiv (nicht in der laufenden Setup-Session). Hook-Implementierungen sind erstinstall-fest: bei nicht-existentem Spiegelpfad (`.claude/skills/`) oder fehlendem `.docs/sync-state.json` greifen die Hooks mit Skip plus Log-Eintrag, kein Block. Das verhindert False-Positives beim ersten Session-Start nach Setup.

Die Config-Erzeugung passiert im selben `sync.py`-Lauf wie die Spiegelung (Phase 7) — der erste Drift-Check beim nächsten Session-Start trifft damit auf einen bereits gefüllten Spiegel.

#### Phase 9 — anwender-Skills auf User-Ebene installieren

Skills mit `scope: anwender` (im Bundle: `elevenlabs-api`, `nano-banana-image`, `openai-image-gen`) werden von `sync.py` **nicht** in die Vault-Spiegel kopiert — `mirror_skills` spiegelt ausschließlich `scope: vault`. Sie gehören auf User-Ebene nach `~/.claude/skills/<name>/` (bzw. `%USERPROFILE%\.claude\skills\<name>\` auf Windows).

Ablauf: Für jeden anwender-Skill, den der User in Phase 4.5 mit „Ja" zur Installation bestätigt hat, kopiere `.tmp_install/.skills/<name>/` komplett nach `~/.claude/skills/<name>/`. Detect-on-Install: existiert dort schon ein Skill mit gleichem Namen, frage den User, ob überschrieben werden soll. Wenn kein anwender-Skill zu installieren ist (User hat abgelehnt oder das aktive Profil aktiviert keinen): Phase 9 überspringen, Log-Eintrag.

**Warum die Kette 4.5 → 9 Pflicht ist:** `sync.py` schreibt die profil-aktiven Skills (inklusive anwender-Scope) nach `.docs/sync-state.json::active_skills`, und der `trigger_enforcer`-Hook kündigt deren Trigger-Wörter beim Arbeiten an. Ohne User-Ebenen-Installation würde der Hook Skills ankündigen, die das Skill-Tool nicht laden kann (Review-Dossier v0.23.0, Befund 4). Die Profil-zu-Skill-Zuordnung liefert `external_global_recommendations` in `bundle.json` — der Pin-Test `tests/unit/test_anwender_scope_recommendations.py` erzwingt, dass jeder profil-aktive anwender-Skill dort gelistet ist.

#### Phase 10 — Installation-Log schreiben

Schreibe `.tmp_install/.docs/installation-log.md` mit folgenden Feldern:

```markdown
---
type: log
description: "Installation-Log des Agentic Second Brain Bundles. Dokumentiert Bundle-Version, Profil, Migration-Auswahl und Datum der Erstinstallation."
tags: [installation, log]
created: <YYYY-MM-DD>
updated: <YYYY-MM-DD>
agent_ready: true
schema_version: 1
---

# Installation-Log

- **Bundle-Version:** <Feld `bundle.version` aus manifest.json>
- **Profil:** <profil aus Schritt 2>
- **Migration:** skipped (B.1-MVP, default_active: false)
- **Datum:** <YYYY-MM-DD HH:MM>
- **SHA-256:** <hash aus BOOTSTRAP.md>
- **uv-Version:** <output von uv --version>
- **Hooks aktiv:** erst beim nächsten Session-Start

## Verknüpfungen

- [[AGENTS]]
- Bundle-Quelle: `https://bootstrap.lehnisch.de/v<version>/bundle.zip`
```

Es gibt bewusst KEINE Wikilinks auf BOOTSTRAP oder INSTALL — beide werden in Phase 10b aus dem Vault-Root gelöscht (siehe F6 + Folge-Entscheidung „Option C"). Die Bundle-Quelle (`https://bootstrap.lehnisch.de/v<version>/bundle.zip`) bleibt öffentlich abrufbar; die im Bundle enthaltene INSTALL.md dokumentiert die Setup-Pipeline, falls ein User sie später nachvollziehen will.

#### Phase 10b — Setup-Artefakte aus dem Vault-Root entfernen (F6, Option C)

Lösche BOOTSTRAP.md und INSTALL.md aus dem Staging-Ordner, BEVOR Schritt 11 den atomaren Move macht. Beide Dateien sind nach Setup tot — der Vault-Root soll nach Setup ausschließlich Vault-Inhalt zeigen (AGENTS.md, CLAUDE.md, INDEX.md, bundle.json) und keine Setup-Mechanik.

1. Lösche `.tmp_install/BOOTSTRAP.md`. Falls die Datei fehlt (z.B. User hat sie schon manuell entfernt): Hinweis auf stderr loggen, kein Crash.
2. Lösche `.tmp_install/INSTALL.md`. Selbe Fehler-Toleranz.

Begründung: Setup-Doku gehört nicht in den User-Vault. Sie taucht sonst im Obsidian-Graph auf, der User editiert sie versehentlich, Wikilinks aus anderen Notizen brechen, sobald die Versionen veralten. Wer die Pipeline später nachschlagen will, findet sie auf GitHub im versionierten Repo. Eine lokale Kopie unter `.docs/setup/` wäre Theater gewesen, weil Obsidian versteckte Ordner (`.docs/`) standardmäßig nicht indexed — Wikilinks darauf hätten als "unresolved" gerendert.

Diese Lösch-Operation ist NICHT umkehrbar. Sie greift erst, wenn alle vorherigen Phasen erfolgreich waren — bei Crash davor liegt das tmp_install noch komplett im Staging-Ordner und kann erneut bearbeitet werden.

Das Original-`BOOTSTRAP.md` im Vault-Root sowie `bundle.zip` und `bundle.zip.sha256` bleiben hier bewusst unangetastet — die räumt Schritt 12b nach dem atomaren Move auf.

### Schritt 11 — Atomares Move

Verschiebe alle Inhalte aus `<Ziel-Vault>/.tmp_install/` nach `<Ziel-Vault>/`. Konkret: jeder Top-Level-Eintrag in `.tmp_install/` wird per `os.rename()` (Python `pathlib.Path.rename()`) in den Ziel-Vault verschoben. Reihenfolge: zuerst Ordner, dann Dateien.

Weil `.tmp_install/` direkt im Ziel-Vault liegt (Schritt 9 hat das verifiziert), ist jeder einzelne Rename atomar (gleiches Volume). Bei Crash mitten im Move-Loop bleibt der Vault inkonsistent, aber kein einzelner Rename ist halbfertig.

### Schritt 12 — `.tmp_install/` löschen

Lösche das (jetzt leere) Verzeichnis `<Ziel-Vault>/.tmp_install/`. Wenn das Verzeichnis nicht leer ist (Move in Schritt 11 unvollständig): brich ab und melde dem User, welche Dateien noch in `.tmp_install/` liegen. Kein Auto-Fix.

### Schritt 12b — Download-Artefakte aus dem Vault-Root entfernen

Nach dem erfolgreichen Move liegen im Vault-Root noch die Setup-Downloads aus den Schritten 4–6: das Original-`BOOTSTRAP.md` (die Startdatei des Users), `bundle.zip` und `bundle.zip.sha256`. Lösche alle drei:

1. Lösche `<Ziel-Vault>/BOOTSTRAP.md`. Falls die Datei fehlt (z.B. User hat sie schon manuell entfernt): Hinweis auf stderr loggen, kein Crash.
2. Lösche `<Ziel-Vault>/bundle.zip`. Selbe Fehler-Toleranz.
3. Lösche `<Ziel-Vault>/bundle.zip.sha256`. Selbe Fehler-Toleranz (der Sidecar ist optional, siehe Schritt 6).

Liegt ein Artefakt an einem abweichenden Ablageort (der User hat in BOOTSTRAP.md Schritt 2 einen anderen Ziel-Vault als den Download-Ordner gewählt), lösche es ausschließlich am Download-Ort aus Schritt 4 — also dort, wo die in Schritt 4 gelesene BOOTSTRAP.md liegt. Keine Dateisystem-Suche nach gleichnamigen Dateien an anderen Orten.

Begründung der Platzierung: Schritt 4 liest BOOTSTRAP.md als Single Source of Truth für Bundle-URL und SHA-256. Die Artefakte dürfen deshalb erst NACH dem erfolgreichen atomaren Move (Schritt 11) gelöscht werden — bei einem Crash davor bleibt der Retry ohne erneuten Download möglich. Erst mit diesem Schritt zeigt der Vault-Root ausschließlich Vault-Inhalt (Gegenstück zu Phase 10b, die nur die Staging-Kopien löscht).

### Schritt 12c — Finaler sync.py-Lauf gegen den Vault-Root

Die Tool-Configs aus Phase 7/8 wurden im Staging geschrieben. `.codex/config.toml` trägt deshalb absolute Hook-Pfade mit `…/.tmp_install/…` — nach dem atomaren Move (Schritt 11) zeigen sie ins gelöschte Staging. Claude Code ist nicht betroffen (`$CLAUDE_PROJECT_DIR`), aber Codex-Hooks würden bis zum nächsten sync-Lauf still nicht feuern. Repariere das jetzt mit einem finalen sync-Lauf gegen den Vault-Root:

```
uv run "<Ziel-Vault>/.skills/sync.py" --vault "<Ziel-Vault>" --profile <profil>
```

Das `--profile`-Argument ist Pflicht — gleiche Begründung wie in Phase 7: ohne fällt `sync.py` still auf das alphabetisch erste Profil zurück. Der Lauf ist idempotent: die Spiegel sind frisch und driftfrei; neu geschrieben werden `.claude/settings.json`, `.codex/config.toml` und `.docs/sync-state.json`. Der Korrekturfall ist allein `.codex/config.toml` (absoluter Pfad): sie trägt danach den echten Vault-Pfad statt des Staging-Pfads. `.claude/settings.json` war nie defekt (`$CLAUDE_PROJECT_DIR` ist pfad-unabhängig) und wird lediglich identisch neu geschrieben.

Verifikation: `.codex/config.toml` enthält danach keinen `.tmp_install`-Treffer mehr (Treffer-Zahl 0).

Liefert der Lauf returncode 2 (BLOCK, „mirror has been manipulated"): genau EINMAL mit `--force` wiederholen — `uv run "<Ziel-Vault>/.skills/sync.py" --vault "<Ziel-Vault>" --profile <profil> --force`. Das ist an dieser Stelle sicher: die Spiegel stammen sekundenfrisch aus dem Bundle, User-Edits sind noch nicht möglich; ein rc2 hier heißt praktisch immer, dass ein Cloud-Sync zwischen Schritt 11 und 12c die Datei-Zeitstempel der frisch gemovten Spiegel angefasst hat (die Drift-Erkennung vergleicht Zeitstempel), keine echte Manipulation. Bleibt es bei returncode 2: abbrechen und dem User melden, welche Spiegel-Abweichung gemeldet wurde.

Liefert der Lauf returncode 1 (Manifest-/Profil-Fehler oder Abbruch durch einen unerwarteten Fehler): NICHT mit `--force` wiederholen — abbrechen und die Fehlermeldung dem User zeigen. In beiden Fehlerfällen gilt: kein manuelles Nachschreiben der Configs, kein eigenes Hook-Konvertierungs-Skript.

### Schritt 13 — Erfolgsmeldung an User

Zeige dem User folgende Bedienungsanleitung:

> Setup abgeschlossen. Dein Agentic Second Brain liegt jetzt unter `<Ziel-Vault>/`.
>
> **Wichtig:** Die Hooks (Frontmatter-Validierung, Trigger-Erzwingung, Output-Filter, Drift-Check) werden erst beim NÄCHSTEN Session-Start aktiv. Schließe das aktuelle Tool-Fenster und starte Claude Code oder Codex neu im Vault-Verzeichnis.
>
> **Erste Schritte nach Restart:**
> 1. Tool im Vault-Verzeichnis öffnen.
> 2. AGENTS.md wird automatisch geladen (Pflicht-Checks aktiv).
> 3. Inbox prüfen: `01 Inbox/` ist leer.
> 4. Falls du OneDrive in Schritt 8 pausiert hast: jetzt wieder starten.
>
> Der sichtbare Ordner `templates/` ist Absicht: generische Inhalts-Vorlagen (Schreibstil, Glossar u.a.), die du nach und nach mit eigenen Inhalten in deine Vault-Ordner überträgst. Was welcher Skill braucht, steht in `templates/README.md`.
>
> Installation-Log unter `.docs/installation-log.md`. Sync-State-Marker `.docs/sync-state.json` wird zuletzt vom finalen `sync.py`-Lauf in Schritt 12c geschrieben — er aktiviert den `sync_drift_check`-Hook beim nächsten Session-Start.
>
> **Codex-Hinweis (Vault-Umzug):** Die Hook-Pfade in `.codex/config.toml` sind maschinenspezifisch (absolute Pfade). Nach einem Vault-Umzug oder einer Ordner-Umbenennung regeneriert der nächste `sync.py`-Lauf (spätestens der nächste Session-Start mit Drift) die Datei automatisch; Codex fragt danach einmalig den Hook-Trust neu ab. Bis dahin feuern Codex-Hooks nicht — kein Defekt, einfach eine Session starten.
>
> **Multi-Device-Hinweis (OneDrive, Dropbox, iCloud):** Wenn du den Vault auf mehreren Geräten gleichzeitig nutzt, kann der `sync_drift_check`-Hook bei Sync-bedingten mtime-Verschiebungen einen False-Positive-Block beim Session-Start melden. In dem Fall: tippe `/sync-repair` im aktiven Tool, das gleicht die Spiegelung sauber an. Edits direkt in `.claude/skills/<name>/` werden dabei überschrieben (Spiegel ist nicht für Bearbeitung gedacht).

Bei OneDrive-Pause aus Schritt 8: erinnere den User aktiv daran, die Synchronisierung wieder zu aktivieren.

## API-Schlüssel für Bild-Skills

Die Skills `nano-banana-image` und `openai-image-gen` (Profil `image`, ab Bundle v0.15.0) brauchen API-Schlüssel zur Laufzeit. Der Bundle-Code liest den Schlüssel ausschließlich aus einer Umgebungsvariable — wie der Anwender diese Variable setzt, ist seine Entscheidung. Drei Wege, sortiert nach Sicherheitsniveau:

1. **1Password CLI + `op-run.sh`-Wrapper (empfohlen):** Der Schlüssel bleibt in 1Password, ein Wrapper-Skript injiziert ihn nur für die Lebensdauer des Subprozesses in die Umgebung. Referenz-Implementation und Wrapper-Pattern liefert der globale `setup-1password-key`-Skill (`~/.claude/skills/setup-1password-key/`). Vorteile: Schlüssel nie im Klartext auf Disk, nie in der Shell-Historie, lokale Rotation in 1Password reicht aus. Validierung ausschließlich über die Länge des gespeicherten Eintrags, z.B. `op read "op://VaultName/ItemName/FieldName" 2>/dev/null | wc -c` (eigenen Vault-/Item-/Feld-Namen einsetzen). **Niemals** den Wert ausgeben — die Skill-Bodies enthalten dazu eine explizite Sicherheits-Klausel.
2. **Shell-Profil oder `.env`-Datei:** Ausreichend für isolierte Entwicklungs-Setups (`export GEMINI_API_KEY=...` in `~/.bashrc`/`~/.zshrc` oder `GEMINI_API_KEY=...` in `.env` im Projekt-Root). Schlüssel liegt im Klartext auf Disk — File-Permissions (`chmod 600`) prüfen, Datei aus `.gitignore` halten.
3. **Skill-Config (`.skills/<skill>/skill.config.yaml`):** Ändert ausschließlich den **Namen** der ENV-Variable über `features.api_key_env_var` (Default-Namen: `GEMINI_API_KEY` bzw. `OPENAI_API_KEY`). Der Wert wird NIE in einer Config-Datei abgelegt — Skill-Configs landen sonst beim Setup-Backup mit im Bundle-Asset-Speicher.

Die Skills brechen mit klarer Fehlermeldung ab, wenn die ENV-Variable nicht gesetzt ist. Kein Auto-Prompt nach dem Schlüssel zur Laufzeit, kein Fallback auf andere Quellen, die der LLM-Kontext sehen würde. Pflicht-Sicherheits-Klausel in beiden Skill-Bodies: API-Schlüssel dürfen niemals an das LLM weitergegeben oder per `cat`/`echo`/`print` ausgegeben werden.

## Verknüpfungen

- [[AGENTS]]
