Was macht dieses Tool?

sema-claude ist eine Zsh-Shell-Funktion, die drei Werkzeuge zu einer interaktiven Pipeline verbindet:

Suchbegriff eingeben → sema durchsucht semantisch → fzf Dateiauswahl → Fabric Pattern wählen → Claude Code ausführen

Beispiel: Du tippst sema-claude "Wardley Maps". sema findet 20 Dateien. Du wählst 3 davon aus. Dann wählst du extract_wisdom. Claude Code liest alle 3 Dateien und extrahiert Ideen, Zitate, Erkenntnisse — alles auf Deutsch.

Pipeline-Übersicht

flowchart TD
    A["Suchbegriff eingeben<br/><i>z.B. 'Wardley Maps'</i>"] --> B["sema CLI<br/>Semantische Suche"]
    B --> C["fzf Dateiauswahl<br/>Tab = markieren, Enter = fertig"]
    C --> D{"Welcher Modus?"}
    D -->|"Kuratiertes Pattern"| E["Fabric Pattern waehlen<br/><i>28 Patterns in 7 Kategorien</i>"]
    D -->|"Freie Frage"| F["Eigene Frage eingeben"]
    D -->|"Alle durchsuchen"| G["Alle 239 Patterns<br/>mit fzf durchsuchen"]
    E --> H["system.md laden<br/><i>~/.config/fabric/patterns/</i>"]
    G --> H
    F --> I["Prompt zusammenbauen"]
    H --> I
    I --> J["Claude Code<br/>Dateien lesen + Framework abarbeiten"]
    J --> K["Ausgabe auf Deutsch"]
            

Abb. 1: Pipeline-Übersicht — Vom Suchbegriff zum Ergebnis

Voraussetzungen

Komponentenarchitektur

graph TB
    subgraph Terminal["Terminal (Zsh)"]
        SC["sema-claude()<br/><i>Shell-Funktion</i>"]
    end
    subgraph SemaSystem["sema System"]
        CLI["sema CLI<br/>bun cli.ts"]
        DB["sema.db<br/><i>sqlite-vec</i><br/>38.425 Chunks"]
        EMB["HuggingFace<br/>Embeddings"]
        CLI --> DB
        DB --> EMB
    end
    subgraph FabricSystem["Fabric AI"]
        PDIR["~/.config/fabric/patterns/"]
        P1["extract_wisdom/<br/>system.md"]
        P2["summarize/<br/>system.md"]
        P3["... 237 weitere ..."]
        PDIR --> P1
        PDIR --> P2
        PDIR --> P3
    end
    subgraph Tools["Interaktive Tools"]
        FZF["fzf<br/><i>Fuzzy Finder</i>"]
    end
    subgraph Claude["Claude Code"]
        CC["claude CLI<br/><i>Anthropic API</i>"]
    end
    SC --> CLI
    SC --> FZF
    SC --> PDIR
    SC --> CC
            

Abb. 2: Komponentenarchitektur — Alle beteiligten Systeme

5 Komponenten werden benötigt

# Komponente Beschreibung
1 sema (Semantische Suche) Lokale semantische Suche mit Vektoren. Indexiert dein Obsidian-Vault oder beliebige Markdown-Dateien und findet inhaltlich ähnliche Dokumente — nicht nur nach Stichwort, sondern nach Bedeutung.
2 Fabric AI (Pattern-Bibliothek) 239 vorgefertigte „Patterns“ von Daniel Miessler. Jedes Pattern ist ein system.md-File mit einem präzisen Analyse-Framework, z.B. „extrahiere Weisheit“ oder „finde Logikfehler“.
3 fzf (Fuzzy Finder) Interaktiver Filter fürs Terminal. Ermöglicht dir, aus langen Listen schnell etwas auszuwählen — mit Vorschau, Mehrfachauswahl und Fuzzy-Suche.
4 Claude Code (CLI) Anthropics CLI-Tool. Nimmt einen Prompt entgegen, liest die angegebenen Dateien und liefert eine KI-Analyse.
5 Zsh + Bun Zsh als Shell (macOS-Standard), Bun als JavaScript-Runtime für sema CLI.

Datenfluss

flowchart LR
    subgraph Stufe1["Stufe 1: Semantische Suche"]
        Q["query<br/>'Wardley Maps'"] --> SEMA["sema CLI"]
        SEMA --> RAW["raw_output<br/>1. [0.712] /pfad/datei.md<br/>2. [0.823] /pfad/andere.md"]
        RAW --> SED["sed Parser"]
        SED --> LINES["file_lines<br/>[0.712] /pfad/datei.md<br/>[0.823] /pfad/andere.md"]
    end
    subgraph Stufe1b["Stufe 1b: Dateiauswahl"]
        LINES --> FZF1["fzf --multi"]
        FZF1 --> SELECTED["selected<br/>[0.712] /pfad/datei.md"]
        SELECTED --> SED2["sed Score entfernen"]
        SED2 --> PATHS["paths<br/>/pfad/datei.md"]
    end
    subgraph Stufe2["Stufe 2: Pattern + Prompt"]
        PATHS --> LIST["file_list<br/>- /pfad/datei.md"]
        PATTERN["system.md<br/><i>Fabric Pattern</i>"] --> PROMPT["prompt"]
        LIST --> PROMPT
    end
    PROMPT --> CLAUDE["claude '$prompt'"]
            

Abb. 3: Datenfluss — Vom Suchbegriff über sed-Parsing zum fertigen Prompt

Was ist fzf?

fzf (fuzzy finder) ist ein interaktiver Kommandozeilen-Filter. Stell dir vor, du hast eine lange Liste — z.B. 20 Suchergebnisse — und willst schnell die richtigen auswählen. fzf zeigt dir die Liste im Terminal, du tippst ein paar Buchstaben und die Liste filtert sich in Echtzeit.

Grundprinzip: Irgendein Befehl gibt viele Zeilen aus → | fzf → du wählst interaktiv aus → fzf gibt die Auswahl zurück.

Wichtige fzf-Parameter

Parameter Bedeutung
--multi Mehrere Einträge auswählen erlauben (mit Tab)
--no-multi Nur einen Eintrag auswählen
--prompt="Text > " Text links neben dem Cursor
--header="Text" Überschrift über der Liste
--preview='befehl' Vorschau rechts, {} = aktueller Eintrag
--preview-window=right:50%:wrap Position, Breite, Zeilenumbruch der Vorschau
--height=80% 80% der Terminal-Höhe nutzen
--border=rounded Runder Rahmen um die Auswahl

Shell-Grundlagen (für Anfänger)

Wenn du noch nie ein Shell-Script geschrieben hast, erklärt dieser Abschnitt alle Konzepte, die im sema-claude-Code vorkommen. Jedes Konzept wird mit einem eigenen Beispiel gezeigt.

Variablen

Eine Variable speichert einen Wert. In Zsh schreibst du den Namen ohne Leerzeichen um das =:

# Einfache Variable
name="Holger"
echo "$name"     # Gibt: Holger

# Lokale Variable (nur in der Funktion sichtbar)
local query="Wardley Maps"

# Lokales Array
local -a SEMA_CLI=(bun /pfad/cli.ts)
local bedeutet: Diese Variable existiert nur innerhalb der Funktion. Ohne local wäre sie global und könnte andere Funktionen stören.
local -a erstellt ein lokales Array (eine Liste von Werten).

Funktions-Definition

Eine Funktion ist ein wiederverwendbarer Codeblock mit einem Namen:

# Funktion definieren
sema-claude() {
    echo "Hallo von sema-claude"
    # ... hier kommt der Code
}

# Funktion aufrufen
sema-claude

Der Code zwischen { und } wird erst ausgeführt, wenn du den Funktionsnamen tippst. Die Klammern () nach dem Namen sind Pflicht — sie sagen der Shell: „Das ist eine Funktion.“

Parameter und Argumente

Wenn du einer Funktion Werte mitgibst, landen sie in $1, $2, etc.:

greet() {
    echo "Hallo, $1!"     # $1 = erstes Argument
}

greet "Welt"               # Gibt: Hallo, Welt!
greet "Claude"             # Gibt: Hallo, Claude!

${1:-} ist ein sicherer Zugriff: Wenn $1 leer ist, wird ein leerer String zurückgegeben statt eines Fehlers. Variante mit Default-Wert:

local query="${1:-}"           # Leer, wenn nichts uebergeben
local top="${2:-20}"           # Default: 20, wenn $2 fehlt

Bedingte Ausführung

Die doppelten eckigen Klammern [[ ]] prüfen Bedingungen:

# -z = "ist leer?" (zero length)
[[ -z "$query" ]]      # wahr, wenn $query leer ist

# -n = "ist NICHT leer?" (non-zero)
[[ -n "$query" ]]      # wahr, wenn $query einen Wert hat

# -f = "existiert die Datei?"
[[ -f "$pattern_file" ]]  # wahr, wenn Datei existiert

# Vergleiche
[[ "$name" == "Holger" ]]  # String-Vergleich
[[ "$count" -gt 10 ]]     # Zahl groesser als 10

if / then / fi

Die klassische Verzweigung:

if [[ -z "$query" ]]; then
    echo "Kein Suchbegriff!"
    return 1
fi

# Mit else:
if [[ -f "$file" ]]; then
    echo "Datei gefunden"
else
    echo "Datei nicht vorhanden"
fi
return 1 beendet die Funktion mit einem Fehlercode. return 0 = Erfolg, alles andere = Fehler.

Kurzform mit &&

&& bedeutet „UND — führe den rechten Befehl nur aus, wenn der linke erfolgreich war“:

# Kurzform statt if/then/fi:
[[ -z "$selected" ]] && { echo "Abgebrochen."; return 1; }

# Das ist identisch mit:
if [[ -z "$selected" ]]; then
    echo "Abgebrochen."
    return 1
fi

Die geschweiften Klammern { } gruppieren mehrere Befehle. Wichtig: Das Semikolon vor } ist Pflicht!

Kurzform mit $DEBUG

local DEBUG=false

# $DEBUG wird "ausgefuehrt" - false ist ein gueltiger Befehl (gibt immer Fehler)
# && fuehrt printf nur aus, wenn $DEBUG=true
$DEBUG && printf "[DEBUG] Wert: %s\n" "$var"

# Aequivalent zu:
if $DEBUG; then
    printf "[DEBUG] Wert: %s\n" "$var"
fi
Trick: true und false sind echte Programme in Unix. true gibt Exit-Code 0 (Erfolg), false gibt Exit-Code 1 (Fehler). Deshalb funktioniert $DEBUG && ... als bedingter Befehl.

Command Substitution $( )

Fängt die Ausgabe eines Befehls in einer Variable auf:

# Die Ausgabe von sema CLI wird in raw_output gespeichert
local raw_output
raw_output=$("${SEMA_CLI[@]}" search "$query" --top "$TOP" 2>&1)

# Einfacheres Beispiel:
local today=$(date +%Y-%m-%d)   # z.B. "2026-02-18"
local files=$(ls *.md)          # Alle .md-Dateien

Alles zwischen $( und ) wird ausgeführt und das Ergebnis ersetzt den gesamten Ausdruck.

Arrays mit [@]

# Array definieren
local -a SEMA_CLI=(bun /pfad/cli.ts)

# Alle Elemente des Arrays einsetzen
"${SEMA_CLI[@]}"
# wird zu: bun /pfad/cli.ts

# Warum Array statt String?
local SEMA_CLI_STRING="bun /pfad/cli.ts"
$SEMA_CLI_STRING     # PROBLEM: Zsh zerlegt am Leerzeichen falsch

# Mit Array:
"${SEMA_CLI[@]}"     # KORREKT: Jedes Element wird einzeln uebergeben
Wichtig: In Zsh (anders als Bash) kann Word-Splitting bei Strings zu schwer findenbaren Fehlern führen. Arrays sind sicherer, weil jedes Element exakt als ein Argument übergeben wird.

Pipe |

Die Pipe leitet die Ausgabe eines Befehls als Eingabe an den nächsten:

# Ausgabe von echo geht an sed:
echo "Hallo Welt" | sed 's/Welt/Claude/'
# Ergebnis: Hallo Claude

# Mehrere Pipes hintereinander:
echo "$raw_output" | sed -n 's/^pattern/\1/p' | wc -l | tr -d ' '
#    Ausgabe      → parsen         → Zeilen zaehlen → Leerzeichen weg

Jeder | nimmt die Standardausgabe (stdout) des linken Befehls und füttert sie als Standardeingabe (stdin) in den rechten.

sed (Stream Editor)

sed verarbeitet Text zeilenweise mit Regulären Ausdrücken. Zentral für sema-claude, weil wir damit die sema-Ausgabe parsen:

# Der wichtigste sed-Befehl im Script:
sed -n 's/^[[:space:]]*[0-9]*\.[[:space:]]*\(\[.*\]\)/\1/p'

Das sieht auf den ersten Blick verwirrend aus. Zerlegen wir es:

Teil Bedeutung
sed -n Unterdrückt Standard-Ausgabe. Nur Zeilen mit /p werden gedruckt.
's/MUSTER/ERSETZUNG/p' Suchen und Ersetzen. /p = Ergebnis ausgeben.
^ Zeilenanfang
[[:space:]]* Beliebig viele Leerzeichen/Tabs
[0-9]* Beliebig viele Ziffern (die Nummerierung: 1, 2, 3...)
\. Wortwörtlicher Punkt (nach der Nummer)
\(\[.*\]\) Capture Group: Fängt alles ab [Score] /pfad
\1 Gibt die Capture Group aus (den eingefangenen Teil)

Vorher → Nachher:

# Eingabe (sema-Ausgabe):
"  1.  [0.712] /pfad/wardley-maps.md"
"  2.  [0.823] /pfad/strategie-als-hypothese.md"

# Nach sed:
"[0.712] /pfad/wardley-maps.md"
"[0.823] /pfad/strategie-als-hypothese.md"

Ein zweiter, einfacherer sed-Befehl entfernt später den Score:

sed 's/^\[.*\] //'
# Vorher: [0.712] /pfad/wardley-maps.md
# Nachher: /pfad/wardley-maps.md

awk

awk ist ein Textverarbeitungs-Tool, das Zeilen in Spalten zerlegt:

# Erste Spalte extrahieren (Trennzeichen: Leerzeichen)
echo "extract_wisdom  │ Weisheit extrahieren" | awk '{print $1}'
# Ergebnis: extract_wisdom

# Im Script: Pattern-Name aus der fzf-Auswahl extrahieren
local pattern
pattern=$(echo "$choice" | awk '{print $1}')

$1 bei awk bedeutet „erste Spalte“, $2 die zweite, etc. Standardmäßig trennt awk an Leerzeichen.

printf

printf gibt formatierten Text aus — zuverlässiger als echo:

# Formatierter Text mit Platzhaltern
printf "Suche nach \"%s\" ...\n" "$query"
# Gibt: Suche nach "Wardley Maps" ...

# Farbige Ausgabe mit ANSI-Codes
printf "\033[1;36m🔍 Semantic Search\033[0m\n"
printf "\033[0;33m⏳ Suche ...\033[0m\n"
printf "\033[0;32m✅ %s Treffer\033[0m\n" "$count"

ANSI-Farbcodes

Code Farbe Verwendung im Script
\033[0m Reset (Normal) Farbe zurücksetzen
\033[1;36m Fett + Cyan Überschriften, wichtige Info
\033[0;33m Gelb Warnungen, Status-Infos
\033[0;32m Grün Erfolg, Bestätigungen
\033[1;32m Fett + Grün „Enter = Starten“
\033[1;31m Fett + Rot „Ctrl+C = Abbrechen“
\033[0;90m Grau Nebensächliche Infos

Das Format: \033[ startet eine Escape-Sequenz, die Zahl bestimmt die Farbe, m beendet sie.

read (Benutzereingabe)

# Eingabe vom Benutzer lesen
printf "Suchbegriff: "
read -r query

# -r = "raw mode" - verhindert, dass Backslash als Escape interpretiert wird
# $query enthaelt jetzt die Eingabe

Im Script wird read -r an drei Stellen verwendet:

  1. Suchbegriff abfragen (wenn keiner als Argument übergeben)
  2. Freie Frage eingeben (bei FREIE_FRAGE Pattern)
  3. Bestätigung vor dem Start (Enter drücken)

Here-String <<<

# Here-String: Text als stdin uebergeben
while IFS= read -r p; do
    file_list+="- $p"$'\n'
done <<< "$paths"

# <<< "$paths" fuettert den Inhalt von $paths Zeile fuer Zeile
# in die while-Schleife
<<< (Here-String) übergibt eine Variable als Eingabe, als wäre es eine Datei. Alternative wäre echo "$paths" | while ..., aber Here-Strings sind in Zsh effizienter.

2>&1 (stderr umleiten)

# Normalerweise gibt es zwei Ausgabe-Kanaele:
# 1 = stdout (normale Ausgabe)
# 2 = stderr (Fehlermeldungen)

# 2>&1 bedeutet: stderr (2) nach stdout (1) umleiten
raw_output=$("${SEMA_CLI[@]}" search "$query" --top "$TOP" 2>&1)

# Ohne 2>&1: Fehlermeldungen erscheinen im Terminal statt in der Variable
# Mit 2>&1:  ALLES landet in $raw_output

Wichtig für sema-claude, weil manche CLI-Tools Warnungen auf stderr schreiben, die sonst verloren gehen.

$? (Exit-Code)

# Jeder Befehl gibt einen Exit-Code zurueck:
# 0 = Erfolg
# 1-255 = Fehler

ls /existiert/nicht
echo $?    # Gibt: 1 (Fehler)

ls /tmp
echo $?    # Gibt: 0 (Erfolg)

# Im Script:
local exit_code=$?
# Speichert den Exit-Code des vorherigen Befehls

String-Länge und Substring

# Laenge eines Strings:
echo ${#prompt}          # z.B. 2847 (Zeichen)

# Substring (erste 300 Zeichen):
echo "${prompt:0:300}"   # Ab Position 0, 300 Zeichen

# Letzte 150 Zeichen:
echo "${prompt: -150}"   # Leerzeichen vor dem Minus ist Pflicht!

# Im Script: Langen Prompt abkuerzen
if [[ ${#prompt} -gt 500 ]]; then
    echo "${prompt:0:300}"
    printf "\n... (%s Zeichen) ...\n" "${#prompt}"
    echo "${prompt: -150}"
fi

wc -l | tr -d ' '

# wc -l = Zeilen zaehlen (word count, lines)
echo "$file_lines" | wc -l
# Gibt z.B.: "      20" (mit fuehrenden Leerzeichen!)

# tr -d ' ' = Leerzeichen loeschen (translate, delete)
echo "$file_lines" | wc -l | tr -d ' '
# Gibt: "20" (sauber)

# Im Script:
local count=$(echo "$file_lines" | wc -l | tr -d ' ')

cat (Datei lesen)

# cat gibt den Inhalt einer Datei aus:
cat system.md

# Im Script: Pattern-Datei einlesen
local system_prompt
system_prompt=$(cat "$pattern_file")
# $system_prompt enthaelt jetzt den gesamten Dateiinhalt

cat steht für „concatenate“ (verknüpfen) und kann auch mehrere Dateien zusammenfügen: cat file1.txt file2.txt.

Installation

Schritt 1: Datei anlegen

mkdir -p ~/.zsh/functions
# Kopiere den Code (siehe Anhang) in diese Datei:
# ~/.zsh/functions/sema-claude.zsh

Schritt 2: In .zshrc laden

Füge diese Zeile am Ende deiner ~/.zshrc hinzu:

source ~/.zsh/functions/sema-claude.zsh

Schritt 3: Pfade anpassen

In der Datei sema-claude.zsh, passe diese zwei Variablen an:

# Pfad zu deiner sema CLI (Zeile 11):
local -a SEMA_CLI=(bun /dein/pfad/zu/sema/cli.ts)

# Pfad zu deinen Fabric Patterns (Zeile 14):
local PATTERNS_DIR="$HOME/.config/fabric/patterns"

Schritt 4: Shell neu laden und testen

source ~/.zshrc

# Test:
sema-claude "suchbegriff"

# Mit Debug-Ausgabe:
sema-claude --debug "suchbegriff"

Der Code Block für Block erklärt

Die Funktion besteht aus 13 logischen Blöcken. Jeder Block wird hier einzeln erklärt.

Block 1: Konfiguration (Zeile 10–15)

sema-claude() {
  local -a SEMA_CLI=(bun /Users/holgergelhausen/PAI/PAI_DIRECTORY/tools/sema/cli.ts)
  local TOP=20
  local DEBUG=false
  local PATTERNS_DIR="$HOME/.config/fabric/patterns"
  [[ "$1" == "--debug" ]] && { DEBUG=true; shift; }

Was passiert hier:

  • SEMA_CLI — Array mit dem sema-Aufruf. Array statt String, damit Zsh die Argumente korrekt trennt.
  • TOP=20 — Maximale Anzahl Suchergebnisse von sema.
  • DEBUG=false — Debug-Modus standardmäßig aus.
  • PATTERNS_DIR — Wo Fabric seine system.md-Dateien speichert.
  • [[ "$1" == "--debug" ]] — Prüft, ob --debug als erstes Argument übergeben wurde. shift entfernt es, damit $1 danach der Suchbegriff ist.

Block 2: Suchbegriff abfragen (Zeile 17–24)

  local query="${1:-}"
  if [[ -z "$query" ]]; then
    printf "\033[1;36m🔍 Semantic Search → Claude Code (Fabric AI)\033[0m\n"
    printf "Suchbegriff: "
    read -r query
    [[ -z "$query" ]] && { echo "Abgebrochen."; return 1; }
  fi

Was passiert hier:

  • ${1:-} — Nimmt den ersten Parameter oder leeren String.
  • Wenn kein Suchbegriff übergeben: Interaktive Eingabe mit read.
  • Wenn auch dann nichts eingegeben wird: Abbruch mit return 1.

Block 3: sema-Suche ausführen (Zeile 26–40)

  printf "\033[0;33m⏳ Suche nach \"%s\" ...\033[0m\n" "$query"
  local raw_output
  raw_output=$("${SEMA_CLI[@]}" search "$query" --top "$TOP" 2>&1)
  local exit_code=$?

  $DEBUG && printf "[DEBUG] exit_code=%s, output_length=%s\n" "$exit_code" "${#raw_output}"
  $DEBUG && echo "[DEBUG] First 3 lines:" && echo "$raw_output" | head -3

  if [[ -z "$raw_output" ]] || [[ ${#raw_output} -lt 10 ]]; then
    echo "❌ Keine Ausgabe von sema CLI (exit: $exit_code)"
    return 1
  fi

Was passiert hier:

  • "${SEMA_CLI[@]}" — Führt bun /pfad/cli.ts search "query" --top 20 aus.
  • 2>&1 — Fehlermeldungen ebenfalls auffangen.
  • $? — Exit-Code speichern für Debug-Ausgabe.
  • Prüfung: Wenn die Ausgabe leer oder kürzer als 10 Zeichen ist, bricht die Funktion ab.

Block 4: Ergebnisse parsen mit sed (Zeile 42–57)

  local file_lines
  file_lines=$(echo "$raw_output" | sed -n 's/^[[:space:]]*[0-9]*\.[[:space:]]*\(\[.*\]\)/\1/p')

  if [[ -z "$file_lines" ]]; then
    echo "❌ Konnte Ergebnisse nicht parsen."
    return 1
  fi

  local count=$(echo "$file_lines" | wc -l | tr -d ' ')
  printf "\033[0;32m✅ %s Treffer\033[0m\n\n" "$count"

Was passiert hier:

  • Die sema-Ausgabe hat das Format 1. [0.712] /pfad/datei.md.
  • sed entfernt die Nummerierung und behält [Score] Pfad.
  • wc -l | tr -d ' ' zählt die Treffer (sauber ohne Leerzeichen).
  • Wenn sed nichts findet (unerwartetes Format): Abbruch.

Block 5: fzf Dateiauswahl (Zeile 62–71)

  local selected
  selected=$(echo "$file_lines" | fzf \
    --multi \
    --prompt="Tab=markieren, Enter=fertig > " \
    --header="🔍 \"$query\" — $count Treffer" \
    --preview='f=$(echo {} | sed "s/^\[.*\] //"); head -40 "$f" 2>/dev/null || echo "Keine Preview"' \
    --preview-window=right:50%:wrap \
    --height=80% \
    --border=rounded)

Was passiert hier:

  • --multi — Mehrere Dateien mit Tab markieren.
  • --preview — Zeigt die ersten 40 Zeilen der aktuellen Datei rechts an. Der sed entfernt den Score aus dem Pfad.
  • 2>/dev/null — Fehlermeldungen bei nicht lesbaren Dateien unterdrücken.
  • Wenn der Benutzer Escape drückt, ist $selected leer → Abbruch.

Block 6: Score entfernen (Zeile 75–79)

  local paths
  paths=$(echo "$selected" | sed 's/^\[.*\] //')
  local selected_count=$(echo "$paths" | wc -l | tr -d ' ')
  printf "\n\033[0;32m📄 %s Datei(en) gewählt\033[0m\n\n" "$selected_count"

Was passiert hier:

  • sed 's/^\[.*\] //' — Entfernt den Score [0.712] vom Anfang jeder Zeile.
  • Übrig bleiben nur die reinen Dateipfade.
  • Zählt und zeigt an, wie viele Dateien gewählt wurden.

Block 7: Pattern-Liste definieren (Zeile 82–120)

  local -a CURATED=(
    "── ANALYSIEREN & ERKLÄREN ───│"
    "extract_wisdom               │ Weisheit, Ideen, Zitate, Gewohnheiten extrahieren"
    "extract_insights             │ Die 10 überraschendsten Kernerkenntnisse"
    ...
    "── CUSTOM ──────────────────-│"
    "FREIE_FRAGE                  │ Eigene Frage an Claude Code"
    "ALLE_PATTERNS                │ Alle 239 Fabric Patterns durchsuchen"
  )

Was passiert hier:

  • Ein Array CURATED enthält die 28 handverlesenen Patterns plus Kategorie-Header.
  • Jeder Eintrag hat das Format: pattern_name │ Beschreibung.
  • Die Kategorie-Header beginnen mit ── und dienen als visuelle Trenner in fzf.
  • Zwei Sondereinträge: FREIE_FRAGE und ALLE_PATTERNS.

Block 8: fzf Pattern-Auswahl mit Preview (Zeile 122–137)

  local choice
  choice=$(printf '%s\n' "${CURATED[@]}" | fzf \
    --prompt="Pattern wählen > " \
    --header="🧠 Fabric AI Pattern für Claude Code" \
    --preview='p=$(echo {} | awk "{print \$1}");
              f="'"$PATTERNS_DIR"'/$p/system.md";
              if [[ -f "$f" ]]; then
                printf "\033[1;33m── %s ──\033[0m\n\n" "$p"
                head -30 "$f"
              else
                echo "📂 Kategorie-Header"
              fi' \
    --preview-window=right:50%:wrap \
    --height=80% \
    --border=rounded \
    --no-multi)

Was passiert hier:

  • printf '%s\n' "${CURATED[@]}" — Jedes Array-Element als eigene Zeile.
  • --preview — Zeigt die system.md des gerade fokussierten Patterns rechts an.
  • awk '{print $1}' — Extrahiert den Pattern-Namen (erste Spalte).
  • --no-multi — Nur ein Pattern auswählen (keine Mehrfachauswahl).

Block 9: Pattern-Name extrahieren (Zeile 141–151)

  local pattern
  pattern=$(echo "$choice" | awk '{print $1}')

  if [[ "$pattern" == "──" ]]; then
    echo "⚠️ Kategorie-Header, kein Pattern."
    return 1
  fi

Was passiert hier:

  • awk '{print $1}' — Nimmt nur das erste Wort der Auswahl, z.B. extract_wisdom.
  • Sicherheitsprüfung: Wenn jemand einen Kategorie-Header auswählt (beginnt mit ──), wird abgebrochen.

Block 10: Sonderfälle (Zeile 153–178)

  local action=""
  if [[ "$pattern" == "FREIE_FRAGE" ]]; then
    printf "\033[1;36mDeine Frage:\033[0m "
    read -r action
    [[ -z "$action" ]] && { echo "Abgebrochen."; return 1; }
  fi

  if [[ "$pattern" == "ALLE_PATTERNS" ]]; then
    pattern=$(ls "$PATTERNS_DIR" | fzf \
      --prompt="Pattern suchen > " \
      --header="🔍 Alle Fabric Patterns durchsuchen" \
      --preview='f="'"$PATTERNS_DIR"'/{}/system.md";
                if [[ -f "$f" ]]; then
                  printf "\033[1;33m── {} ──\033[0m\n\n"
                  head -30 "$f"
                else
                  echo "Keine system.md gefunden"
                fi' \
      --preview-window=right:50%:wrap \
      --height=80% \
      --border=rounded \
      --no-multi)
    [[ -z "$pattern" ]] && { echo "Abgebrochen."; return 1; }
  fi

Was passiert hier:

  • FREIE_FRAGE: Statt eines Fabric Patterns gibt der Benutzer eine eigene Frage ein. Diese wird später direkt als Prompt verwendet.
  • ALLE_PATTERNS: Öffnet ein zweites fzf-Menü, das alle 239 Fabric-Verzeichnisse auflistet. So kann man auch Patterns außerhalb der kuratierten 28 nutzen.

Block 11: Datei-Liste aufbauen (Zeile 180–184)

  local file_list=""
  while IFS= read -r p; do
    file_list+="- $p"$'\n'
  done <<< "$paths"

Was passiert hier:

  • Jeder Dateipfad wird mit einem - präfixiert (Markdown-Listenformat).
  • IFS= — Verhindert, dass führende/nachfolgende Leerzeichen abgeschnitten werden.
  • <<< "$paths" — Here-String füttert die Pfade in die Schleife.
  • $'\n' — Literal Newline (Zsh-Syntax).
  • Ergebnis z.B.: - /pfad/wardley.md\n- /pfad/strategie.md\n

Block 12: Prompt zusammenbauen (Zeile 186–221)

  local prompt=""

  if [[ -n "$action" ]]; then
    # Freie Frage: Einfacher Prompt
    prompt="$action

Basierend auf diesen Dateien (semantische Suche: \"$query\"):

$file_list
Lies bitte zuerst alle Dateien und arbeite dann die Aufgabe ab. Antworte auf Deutsch."
  else
    # Fabric Pattern: system.md laden
    local pattern_file="$PATTERNS_DIR/$pattern/system.md"
    if [[ ! -f "$pattern_file" ]]; then
      echo "❌ Pattern-Datei nicht gefunden: $pattern_file"
      return 1
    fi

    local system_prompt
    system_prompt=$(cat "$pattern_file")

    prompt="SPRACHE: Deine gesamte Ausgabe MUSS auf Deutsch sein. ...

Du folgst diesem Analyse-Framework:

---
$system_prompt
---

Wende dieses Framework auf folgende Dateien an (semantische Suche: \"$query\"):

$file_list
Lies bitte zuerst alle Dateien vollstaendig und arbeite dann das Framework systematisch auf Deutsch ab."
  fi

Was passiert hier:

  • Zwei Pfade: Freie Frage (einfacher Text-Prompt) vs. Fabric Pattern (system.md wird eingebettet).
  • Bei Fabric: Die gesamte system.md wird in den Prompt eingebaut, eingeklammert von ---.
  • Die Sprach-Anweisung steht ganz oben — damit Claude auf Deutsch antwortet.
  • Die Dateiliste sagt Claude, welche Dateien gelesen werden sollen.

Block 13: Bestätigung und Start (Zeile 223–251)

  printf "\n\033[1;36m━━━ Claude Code Prompt ━━━\033[0m\n"
  if [[ -n "$action" ]]; then
    printf "\033[0;33mModus:\033[0m Freie Frage\n"
    printf "\033[0;33mFrage:\033[0m %s\n" "$action"
  else
    printf "\033[0;33mPattern:\033[0m %s\n" "$pattern"
    printf "\033[0;33mQuelle:\033[0m %s\n" "$PATTERNS_DIR/$pattern/system.md"
  fi
  printf "\033[0;33mDateien:\033[0m %s\n" "$selected_count"
  printf "\033[0;33mSuche:\033[0m \"%s\"\n" "$query"
  printf "\033[1;36m━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n\n"

  if [[ ${#prompt} -gt 500 ]]; then
    echo "${prompt:0:300}"
    printf "\n\033[0;90m... (%s Zeichen, inkl. system.md) ...\033[0m\n\n" "${#prompt}"
    echo "${prompt: -150}"
  else
    echo "$prompt"
  fi

  printf "\n\033[1;32mEnter\033[0m = Starten, \033[1;31mCtrl+C\033[0m = Abbrechen "
  read -r

  printf "\n\033[0;33m🚀 Starte Claude Code mit Fabric Pattern \"%s\"...\033[0m\n" "${pattern:-custom}"
  claude "$prompt"
}

Was passiert hier:

  • Eine Übersicht wird angezeigt: Pattern-Name, Dateien, Suchbegriff.
  • Bei langen Prompts (>500 Zeichen): Nur Anfang und Ende zeigen, Mitte abkürzen.
  • read -r — Wartet auf Enter. Ctrl+C bricht ab.
  • claude "$prompt" — Der finale Aufruf! Claude Code erhält den kompletten Prompt mit Framework + Dateiliste.

Alle 28 kuratierten Patterns

Die 28 handverlesenen Fabric Patterns sind in 7 Kategorien organisiert. Jedes Pattern hat eine system.md-Datei mit einem präzisen Analyse-Framework.

Analysieren & Erklären (11 Patterns)

Pattern Beschreibung
extract_wisdom Weisheit, Ideen, Zitate, Gewohnheiten extrahieren
extract_insights Die 10 überraschendsten Kernerkenntnisse
analyze_prose Schreibqualität bewerten + Verbesserungsvorschläge
analyze_claims Behauptungen auf Wahrheit prüfen (A–F Rating)
analyze_paper Wissenschaftliche Rigor-Analyse
analyze_presentation Präsentation reviewen und kritisch bewerten
analyze_tech_impact Technologie-Impact auf Gesellschaft analysieren
find_logical_fallacies Logikfehler und Fehlschlüsse finden
explain_docs Dokumentation verständlich erklären
explain_terms Glossar aller wichtigen Begriffe erstellen
rate_content Inhalt bewerten + Qualitäts-Rating (1–10)

Zusammenfassen (4 Patterns)

Pattern Beschreibung
summarize 1-Satz-Summary + 10 Hauptpunkte + 5 Takeaways
create_summary Strukturierte Markdown-Zusammenfassung
create_5_sentence_summary 5-Satz-Zusammenfassung
extract_core_message Kernbotschaft auf den Punkt

Vergleichen (1 Pattern)

Pattern Beschreibung
compare_and_contrast Markdown-Tabelle: Gemeinsamkeiten & Unterschiede

Content Erstellen (6 Patterns)

Pattern Beschreibung
write_essay Essay im Paul-Graham-Stil
create_keynote Keynote/Präsentation erstellen
write_micro_essay Micro-Essay (< 300 Wörter)
improve_writing Text überarbeiten: Klarheit, Kohärenz, Stil
enrich_blog_post Blog-Post anreichern und verbessern
create_newsletter_entry Newsletter-Abschnitt erstellen

Extrahieren (4 Patterns)

Pattern Beschreibung
extract_ideas 20–50 überraschende Ideen
extract_recommendations Handlungsempfehlungen
extract_questions Offene Fragen identifizieren
create_tags Tags und Kategorien generieren

Lernen (2 Patterns)

Pattern Beschreibung
create_flash_cards Lernkarten für Schlüsselkonzepte
create_quiz Quiz-Fragen zum Inhalt generieren

Fehlerbehebung

Problem Ursache Lösung
sema-claude: command not found source in .zshrc fehlt source ~/.zsh/functions/sema-claude.zsh hinzufügen und Shell neu laden
„Keine Ausgabe von sema CLI“ sema nicht installiert oder falscher Pfad SEMA_CLI-Pfad in der Funktion prüfen. Mit --debug testen.
„Konnte Ergebnisse nicht parsen“ sema-Ausgabeformat hat sich geändert sema manuell aufrufen (bun cli.ts search "test") und Format prüfen
fzf zeigt keine Preview Dateipfade stimmen nicht / Dateien nicht lesbar Pfade in der fzf-Ausgabe manuell prüfen
„Pattern-Datei nicht gefunden“ Fabric nicht installiert oder PATTERNS_DIR falsch ls ~/.config/fabric/patterns/ prüfen. fabric --update ausführen.
Claude gibt englische Ausgabe Selten bei kurzen Texten Prompt beginnt mit SPRACHE: Deine gesamte Ausgabe MUSS auf Deutsch sein. — normalerweise reicht das.
claude: command not found Claude Code CLI nicht installiert npm install -g @anthropic-ai/claude-code
Word-Splitting-Fehler SEMA_CLI als String statt Array Muss local -a SEMA_CLI=(...) sein (Array!)
Tipp: Bei jedem Problem zuerst mit sema-claude --debug "test" starten. Die Debug-Ausgabe zeigt Exit-Code, Output-Länge und die ersten 3 Zeilen der sema-Antwort.

Claude Code Skill: obsidian-vault-query

Neben der Shell-Funktion sema-claude gibt es einen zweiten Weg, sema zu nutzen: als Claude Code Skill. Dieser Skill wird innerhalb von Claude Code aktiviert, nicht im Terminal.

Was ist ein Claude Code Skill?

Ein Skill ist eine SKILL.md-Datei im Verzeichnis .claude/skills/. Sie enthält Anweisungen, die Claude Code automatisch befolgt, wenn bestimmte Trigger-Wörter erkannt werden. Der Skill obsidian-vault-query bringt Claude Code bei, sema direkt aufzurufen.

Skill-Dateien und Pfade

Datei Pfad
Skill-Definition .claude/skills/obsidian-vault-query/SKILL.md
sema CLI tools/sema/cli.ts
sema Datenbank ~/.local/share/sema/sema.db

Trigger-Wörter

Der Skill wird automatisch aktiviert, wenn du in Claude Code sagst:

Verfügbare Befehle

Befehl Beschreibung
sema search "query" Semantische Suche (Standard)
sema search "query" --hybrid Hybride Suche (Vektor + FTS5). Achtung: Bindestriche in Suchbegriffen können FTS5-Fehler verursachen.
sema related /pfad/datei.md Findet ähnliche Dateien zu einer gegebenen Datei
sema status Zeigt Index-Status (Dateien, Chunks, DB-Größe)

Score-Logik

Niedriger Score = besser! Der Score ist eine Distanz, nicht eine Ähnlichkeit. < 0.85 = sehr relevant, 0.85–1.0 = relevant, > 1.0 = möglicherweise nicht relevant.

Workflow

  1. Du fragst Claude Code: „Was haben wir zu Wardley Maps?“
  2. Claude erkennt den Trigger und ruft sema search "Wardley Maps" auf
  3. sema liefert Ergebnisse mit Scores und Pfaden
  4. Claude liest die relevantesten Dateien und fasst zusammen

Shell-Funktion vs. Claude Code Skill

flowchart TD
    USER["Du"] --> CHOICE{"Wie willst du<br/>sema nutzen?"}
    CHOICE -->|"Interaktiv im Terminal<br/>mit Dateiauswahl + Fabric"| SHELL["sema-claude<br/><i>Shell-Funktion</i>"]
    CHOICE -->|"Direkt in Claude Code<br/>per Sprache"| SKILL["obsidian-vault-query<br/><i>Claude Code Skill</i>"]
    SHELL --> FZF["fzf: Dateien waehlen"]
    FZF --> FABRIC["Fabric Pattern waehlen"]
    FABRIC --> CLAUDE1["Claude Code<br/>mit vollem Prompt"]
    SKILL --> TRIGGER["Trigger: 'was haben wir zu...'"]
    TRIGGER --> SEMA2["Claude ruft sema CLI auf"]
    SEMA2 --> CLAUDE2["Claude zeigt Ergebnisse<br/>+ analysiert direkt"]
            

Abb. 4: Shell-Funktion vs. Claude Code Skill — Zwei Wege, ein Werkzeug

Aspekt Shell-Funktion (sema-claude) Claude Code Skill
Aufruf Im Terminal: sema-claude "query" In Claude Code: „Was haben wir zu...“
Dateiauswahl Interaktiv mit fzf (Tab + Enter) Claude wählt automatisch
Fabric Patterns 28 kuratierte + alle 239 durchsuchbar Nicht verfügbar (direkte Analyse)
Kontrolle Volle Kontrolle über Dateien + Pattern Claude entscheidet automatisch
Geschwindigkeit 3–4 Interaktionsschritte Ein Satz genügt
Ideal für Gezielte Analyse mit bestimmtem Framework Schnelle Recherche, Überblick

Anhang: Vollständiger Quellcode

Der komplette Quellcode der sema-claude-Funktion (Version 2.0.0, 251 Zeilen).

sema-claude.zsh — Vollständiger Quellcode (251 Zeilen)
# sema-claude - Semantic Search → Claude Code Pipeline (Fabric-Powered)
# Version: 2.0.0
# Changelog:
#   v2.0.0 (18.02.2026) - Fabric AI Patterns statt simple Aktionen, 28 kuratierte Patterns in 7 Kategorien, system.md als Claude-Prompt
#   v1.3.0 (18.02.2026) - Fix: SEMA_CLI als Array statt String (zsh word-splitting)
#   v1.2.0 (18.02.2026) - Debug-Modus, stderr→stdout, robustere Erkennung
#   v1.1.0 (18.02.2026) - Fix: robusteres Parsing, bessere Preview
#   v1.0.0 (18.02.2026) - Initial: 2-stufiger Workflow sema → fzf → claude

sema-claude() {
  local -a SEMA_CLI=(bun /Users/holgergelhausen/PAI/PAI_DIRECTORY/tools/sema/cli.ts)
  local TOP=20
  local DEBUG=false
  local PATTERNS_DIR="$HOME/.config/fabric/patterns"
  [[ "$1" == "--debug" ]] && { DEBUG=true; shift; }

  # Stufe 1: Suchbegriff
  local query="${1:-}"
  if [[ -z "$query" ]]; then
    printf "\033[1;36m🔍 Semantic Search → Claude Code (Fabric AI)\033[0m\n"
    printf "Suchbegriff: "
    read -r query
    [[ -z "$query" ]] && { echo "Abgebrochen."; return 1; }
  fi

  # Suche ausfuehren
  printf "\033[0;33m⏳ Suche nach \"%s\" ...\033[0m\n" "$query"
  local raw_output
  raw_output=$("${SEMA_CLI[@]}" search "$query" --top "$TOP" 2>&1)
  local exit_code=$?

  $DEBUG && printf "[DEBUG] exit_code=%s, output_length=%s\n" "$exit_code" "${#raw_output}"
  $DEBUG && echo "[DEBUG] First 3 lines:" && echo "$raw_output" | head -3

  if [[ -z "$raw_output" ]] || [[ ${#raw_output} -lt 10 ]]; then
    echo "❌ Keine Ausgabe von sema CLI (exit: $exit_code)"
    return 1
  fi

  local file_lines
  file_lines=$(echo "$raw_output" | sed -n 's/^[[:space:]]*[0-9]*\.[[:space:]]*\(\[.*\]\)/\1/p')

  if [[ -z "$file_lines" ]]; then
    echo "❌ Konnte Ergebnisse nicht parsen."
    return 1
  fi

  local count=$(echo "$file_lines" | wc -l | tr -d ' ')
  printf "\033[0;32m✅ %s Treffer\033[0m\n\n" "$count"

  # Stufe 1b: Dateien waehlen mit fzf
  local selected
  selected=$(echo "$file_lines" | fzf \
    --multi \
    --prompt="Tab=markieren, Enter=fertig > " \
    --header="🔍 \"$query\" — $count Treffer" \
    --preview='f=$(echo {} | sed "s/^\[.*\] //"); head -40 "$f" 2>/dev/null || echo "Keine Preview"' \
    --preview-window=right:50%:wrap \
    --height=80% \
    --border=rounded)

  [[ -z "$selected" ]] && { echo "Abgebrochen."; return 1; }

  local paths
  paths=$(echo "$selected" | sed 's/^\[.*\] //')
  local selected_count=$(echo "$paths" | wc -l | tr -d ' ')
  printf "\n\033[0;32m📄 %s Datei(en) gewählt\033[0m\n\n" "$selected_count"

  # Stufe 2: Fabric Pattern waehlen
  local -a CURATED=(
    "── ANALYSIEREN & ERKLÄREN ───│"
    "extract_wisdom               │ Weisheit, Ideen, Zitate, Gewohnheiten extrahieren"
    "extract_insights             │ Die 10 überraschendsten Kernerkenntnisse"
    "analyze_prose                │ Schreibqualität bewerten + Verbesserungsvorschläge"
    "analyze_claims               │ Behauptungen auf Wahrheit prüfen (A-F Rating)"
    "analyze_paper                │ Wissenschaftliche Rigor-Analyse"
    "analyze_presentation         │ Präsentation reviewen und kritisch bewerten"
    "analyze_tech_impact          │ Technologie-Impact auf Gesellschaft analysieren"
    "find_logical_fallacies       │ Logikfehler und Fehlschlüsse finden"
    "explain_docs                 │ Dokumentation verständlich erklären"
    "explain_terms                │ Glossar aller wichtigen Begriffe erstellen"
    "rate_content                 │ Inhalt bewerten + Qualitäts-Rating (1-10)"
    "── ZUSAMMENFASSEN ───────────│"
    "summarize                    │ 1-Satz-Summary + 10 Hauptpunkte + 5 Takeaways"
    "create_summary               │ Strukturierte Markdown-Zusammenfassung"
    "create_5_sentence_summary    │ 5-Satz-Zusammenfassung"
    "extract_core_message         │ Kernbotschaft auf den Punkt"
    "── VERGLEICHEN ──────────────│"
    "compare_and_contrast         │ Markdown-Tabelle: Gemeinsamkeiten & Unterschiede"
    "── CONTENT ERSTELLEN ────────│"
    "write_essay                  │ Essay im Paul-Graham-Stil"
    "create_keynote               │ Keynote/Präsentation erstellen"
    "write_micro_essay            │ Micro-Essay (< 300 Wörter)"
    "improve_writing              │ Text überarbeiten: Klarheit, Kohärenz, Stil"
    "enrich_blog_post             │ Blog-Post anreichern und verbessern"
    "create_newsletter_entry      │ Newsletter-Abschnitt erstellen"
    "── EXTRAHIEREN ──────────────│"
    "extract_ideas                │ 20-50 überraschende Ideen"
    "extract_recommendations      │ Handlungsempfehlungen"
    "extract_questions            │ Offene Fragen identifizieren"
    "create_tags                  │ Tags und Kategorien generieren"
    "── LERNEN ───────────────────│"
    "create_flash_cards           │ Lernkarten für Schlüsselkonzepte"
    "create_quiz                  │ Quiz-Fragen zum Inhalt generieren"
    "── CUSTOM ──────────────────-│"
    "FREIE_FRAGE                  │ Eigene Frage an Claude Code"
    "ALLE_PATTERNS                │ Alle 239 Fabric Patterns durchsuchen"
  )

  local choice
  choice=$(printf '%s\n' "${CURATED[@]}" | fzf \
    --prompt="Pattern wählen > " \
    --header="🧠 Fabric AI Pattern für Claude Code" \
    --preview='p=$(echo {} | awk "{print \$1}");
              f="'"$PATTERNS_DIR"'/$p/system.md";
              if [[ -f "$f" ]]; then
                printf "\033[1;33m── %s ──\033[0m\n\n" "$p"
                head -30 "$f"
              else
                echo "📂 Kategorie-Header"
              fi' \
    --preview-window=right:50%:wrap \
    --height=80% \
    --border=rounded \
    --no-multi)

  [[ -z "$choice" ]] && { echo "Abgebrochen."; return 1; }

  local pattern
  pattern=$(echo "$choice" | awk '{print $1}')

  if [[ "$pattern" == "──" ]]; then
    echo "⚠️ Kategorie-Header, kein Pattern."
    return 1
  fi

  local action=""
  if [[ "$pattern" == "FREIE_FRAGE" ]]; then
    printf "\033[1;36mDeine Frage:\033[0m "
    read -r action
    [[ -z "$action" ]] && { echo "Abgebrochen."; return 1; }
  fi

  if [[ "$pattern" == "ALLE_PATTERNS" ]]; then
    pattern=$(ls "$PATTERNS_DIR" | fzf \
      --prompt="Pattern suchen > " \
      --header="🔍 Alle Fabric Patterns durchsuchen" \
      --preview='f="'"$PATTERNS_DIR"'/{}/system.md";
                if [[ -f "$f" ]]; then
                  printf "\033[1;33m── {} ──\033[0m\n\n"
                  head -30 "$f"
                else
                  echo "Keine system.md gefunden"
                fi' \
      --preview-window=right:50%:wrap \
      --height=80% \
      --border=rounded \
      --no-multi)
    [[ -z "$pattern" ]] && { echo "Abgebrochen."; return 1; }
  fi

  local file_list=""
  while IFS= read -r p; do
    file_list+="- $p"$'\n'
  done <<< "$paths"

  local prompt=""

  if [[ -n "$action" ]]; then
    prompt="$action

Basierend auf diesen Dateien (semantische Suche: \"$query\"):

$file_list
Lies bitte zuerst alle Dateien und arbeite dann die Aufgabe ab. Antworte auf Deutsch."
  else
    local pattern_file="$PATTERNS_DIR/$pattern/system.md"
    if [[ ! -f "$pattern_file" ]]; then
      echo "❌ Pattern-Datei nicht gefunden: $pattern_file"
      return 1
    fi

    local system_prompt
    system_prompt=$(cat "$pattern_file")

    prompt="SPRACHE: Deine gesamte Ausgabe MUSS auf Deutsch sein. Uebersetze ALLE englischen Section-Header ins Deutsche.

Du folgst diesem Analyse-Framework:

---
$system_prompt
---

Wende dieses Framework auf folgende Dateien an (semantische Suche: \"$query\"):

$file_list
Lies bitte zuerst alle Dateien vollstaendig und arbeite dann das Framework systematisch auf Deutsch ab."
  fi

  printf "\n\033[1;36m━━━ Claude Code Prompt ━━━\033[0m\n"
  if [[ -n "$action" ]]; then
    printf "\033[0;33mModus:\033[0m Freie Frage\n"
    printf "\033[0;33mFrage:\033[0m %s\n" "$action"
  else
    printf "\033[0;33mPattern:\033[0m %s\n" "$pattern"
    printf "\033[0;33mQuelle:\033[0m %s\n" "$PATTERNS_DIR/$pattern/system.md"
  fi
  printf "\033[0;33mDateien:\033[0m %s\n" "$selected_count"
  printf "\033[0;33mSuche:\033[0m \"%s\"\n" "$query"
  printf "\033[1;36m━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n\n"

  if [[ ${#prompt} -gt 500 ]]; then
    echo "${prompt:0:300}"
    printf "\n\033[0;90m... (%s Zeichen, inkl. system.md) ...\033[0m\n\n" "${#prompt}"
    echo "${prompt: -150}"
  else
    echo "$prompt"
  fi

  printf "\n\033[1;32mEnter\033[0m = Starten, \033[1;31mCtrl+C\033[0m = Abbrechen "
  read -r

  printf "\n\033[0;33m🚀 Starte Claude Code mit Fabric Pattern \"%s\"...\033[0m\n" "${pattern:-custom}"
  claude "$prompt"
}