Implement WP-24c v4.2.0: Introduce configurable header names and levels for LLM validation and Note-Scope zones in the chunking system. Update chunking models, parser, and processor to support exclusion of edge zones during chunking. Enhance documentation and configuration files to reflect new environment variables for improved flexibility in Markdown processing.

This commit is contained in:
Lars 2026-01-10 21:46:51 +01:00
parent 39fd15b565
commit 003a270548
7 changed files with 401 additions and 28 deletions

View File

@ -13,6 +13,7 @@ class RawBlock:
level: Optional[int]
section_path: str
section_title: Optional[str]
exclude_from_chunking: bool = False # WP-24c v4.2.0: Flag für Edge-Zonen, die nicht gechunkt werden sollen
@dataclass
class Chunk:

View File

@ -3,8 +3,10 @@ FILE: app/core/chunking/chunking_parser.py
DESCRIPTION: Zerlegt Markdown in logische Einheiten (RawBlocks).
Hält alle Überschriftenebenen (H1-H6) im Stream.
Stellt die Funktion parse_edges_robust zur Verfügung.
WP-24c v4.2.0: Identifiziert Edge-Zonen und markiert sie für Chunking-Ausschluss.
"""
import re
import os
from typing import List, Tuple, Set
from .chunking_models import RawBlock
from .chunking_utils import extract_frontmatter_from_text
@ -20,7 +22,10 @@ def split_sentences(text: str) -> list[str]:
return [p.strip() for p in _SENT_SPLIT.split(text) if p.strip()]
def parse_blocks(md_text: str) -> Tuple[List[RawBlock], str]:
"""Zerlegt Text in logische Einheiten (RawBlocks), inklusive H1-H6."""
"""
Zerlegt Text in logische Einheiten (RawBlocks), inklusive H1-H6.
WP-24c v4.2.0: Identifiziert Edge-Zonen (LLM-Validierung & Note-Scope) und markiert sie für Chunking-Ausschluss.
"""
blocks = []
h1_title = "Dokument"
section_path = "/"
@ -29,6 +34,31 @@ def parse_blocks(md_text: str) -> Tuple[List[RawBlock], str]:
# Frontmatter entfernen
fm, text_without_fm = extract_frontmatter_from_text(md_text)
# WP-24c v4.2.0: Konfigurierbare Header-Namen und -Ebenen
llm_validation_headers = os.getenv(
"MINDNET_LLM_VALIDATION_HEADERS",
"Unzugeordnete Kanten,Edge Pool,Candidates"
)
llm_validation_header_list = [h.strip() for h in llm_validation_headers.split(",") if h.strip()]
if not llm_validation_header_list:
llm_validation_header_list = ["Unzugeordnete Kanten", "Edge Pool", "Candidates"]
note_scope_headers = os.getenv(
"MINDNET_NOTE_SCOPE_ZONE_HEADERS",
"Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen"
)
note_scope_header_list = [h.strip() for h in note_scope_headers.split(",") if h.strip()]
if not note_scope_header_list:
note_scope_header_list = ["Smart Edges", "Relationen", "Global Links", "Note-Level Relations", "Globale Verbindungen"]
# Header-Ebenen konfigurierbar (Default: LLM=3, Note-Scope=2)
llm_validation_level = int(os.getenv("MINDNET_LLM_VALIDATION_HEADER_LEVEL", "3"))
note_scope_level = int(os.getenv("MINDNET_NOTE_SCOPE_HEADER_LEVEL", "2"))
# Status-Tracking für Edge-Zonen
in_exclusion_zone = False
exclusion_zone_type = None # "llm_validation" oder "note_scope"
# H1 für Note-Titel extrahieren (Metadaten-Zweck)
h1_match = re.search(r'^#\s+(.*)', text_without_fm, re.MULTILINE)
if h1_match:
@ -47,20 +77,47 @@ def parse_blocks(md_text: str) -> Tuple[List[RawBlock], str]:
if buffer:
content = "\n".join(buffer).strip()
if content:
blocks.append(RawBlock("paragraph", content, None, section_path, current_section_title))
blocks.append(RawBlock(
"paragraph", content, None, section_path, current_section_title,
exclude_from_chunking=in_exclusion_zone
))
buffer = []
level = len(heading_match.group(1))
title = heading_match.group(2).strip()
# WP-24c v4.2.0: Prüfe, ob dieser Header eine Edge-Zone startet
is_llm_validation_zone = (
level == llm_validation_level and
any(title.lower() == h.lower() for h in llm_validation_header_list)
)
is_note_scope_zone = (
level == note_scope_level and
any(title.lower() == h.lower() for h in note_scope_header_list)
)
if is_llm_validation_zone:
in_exclusion_zone = True
exclusion_zone_type = "llm_validation"
elif is_note_scope_zone:
in_exclusion_zone = True
exclusion_zone_type = "note_scope"
elif in_exclusion_zone:
# Neuer Header gefunden, der keine Edge-Zone ist -> Zone beendet
in_exclusion_zone = False
exclusion_zone_type = None
# Pfad- und Titel-Update für die Metadaten der folgenden Blöcke
if level == 1:
current_section_title = title; section_path = "/"
elif level == 2:
current_section_title = title; section_path = f"/{current_section_title}"
# Die Überschrift selbst als regulären Block hinzufügen
blocks.append(RawBlock("heading", stripped, level, section_path, current_section_title))
# Die Überschrift selbst als regulären Block hinzufügen (auch markiert, wenn in Zone)
blocks.append(RawBlock(
"heading", stripped, level, section_path, current_section_title,
exclude_from_chunking=in_exclusion_zone
))
continue
# Trenner (---) oder Leerzeilen beenden Blöcke, außer innerhalb von Callouts
@ -68,17 +125,26 @@ def parse_blocks(md_text: str) -> Tuple[List[RawBlock], str]:
if buffer:
content = "\n".join(buffer).strip()
if content:
blocks.append(RawBlock("paragraph", content, None, section_path, current_section_title))
blocks.append(RawBlock(
"paragraph", content, None, section_path, current_section_title,
exclude_from_chunking=in_exclusion_zone
))
buffer = []
if stripped == "---":
blocks.append(RawBlock("separator", "---", None, section_path, current_section_title))
blocks.append(RawBlock(
"separator", "---", None, section_path, current_section_title,
exclude_from_chunking=in_exclusion_zone
))
else:
buffer.append(line)
if buffer:
content = "\n".join(buffer).strip()
if content:
blocks.append(RawBlock("paragraph", content, None, section_path, current_section_title))
blocks.append(RawBlock(
"paragraph", content, None, section_path, current_section_title,
exclude_from_chunking=in_exclusion_zone
))
return blocks, h1_title

View File

@ -6,9 +6,11 @@ DESCRIPTION: Der zentrale Orchestrator für das Chunking-System.
- Integriert physikalische Kanten-Injektion (Propagierung).
- Stellt H1-Kontext-Fenster sicher.
- Baut den Candidate-Pool für die WP-15b Ingestion auf.
WP-24c v4.2.0: Konfigurierbare Header-Namen für LLM-Validierung.
"""
import asyncio
import re
import os
import logging
from typing import List, Dict, Optional
from .chunking_models import Chunk
@ -31,6 +33,10 @@ async def assemble_chunks(note_id: str, md_text: str, note_type: str, config: Op
fm, body_text = extract_frontmatter_from_text(md_text)
blocks, doc_title = parse_blocks(md_text)
# WP-24c v4.2.0: Filtere Blöcke aus Edge-Zonen (LLM-Validierung & Note-Scope)
# Diese Bereiche sollen nicht als Chunks angelegt werden, sondern nur die Kanten extrahiert werden
blocks_for_chunking = [b for b in blocks if not getattr(b, 'exclude_from_chunking', False)]
# Vorbereitung des H1-Präfix für die Embedding-Fenster (Breadcrumbs)
h1_prefix = f"# {doc_title}" if doc_title else ""
@ -38,11 +44,11 @@ async def assemble_chunks(note_id: str, md_text: str, note_type: str, config: Op
# Alle Strategien nutzen nun einheitlich context_prefix für die Window-Bildung.
if config.get("strategy") == "by_heading":
chunks = await asyncio.to_thread(
strategy_by_heading, blocks, config, note_id, context_prefix=h1_prefix
strategy_by_heading, blocks_for_chunking, config, note_id, context_prefix=h1_prefix
)
else:
chunks = await asyncio.to_thread(
strategy_sliding_window, blocks, config, note_id, context_prefix=h1_prefix
strategy_sliding_window, blocks_for_chunking, config, note_id, context_prefix=h1_prefix
)
if not chunks:
@ -63,14 +69,29 @@ async def assemble_chunks(note_id: str, md_text: str, note_type: str, config: Op
k, t = parts
ch.candidate_pool.append({"kind": k, "to": t, "provenance": "explicit"})
# 5. Global Pool (Unzugeordnete Kanten aus dem Dokument-Ende)
# Sucht nach dem Edge-Pool Block im Original-Markdown.
pool_match = re.search(
r'###?\s*(?:Unzugeordnete Kanten|Edge Pool|Candidates)\s*\n(.*?)(?:\n#|$)',
body_text,
re.DOTALL | re.IGNORECASE
# 5. Global Pool (Unzugeordnete Kanten - kann mitten im Dokument oder am Ende stehen)
# WP-24c v4.2.0: Konfigurierbare Header-Namen und -Ebene via .env
# Sucht nach ALLEN Edge-Pool Blöcken im Original-Markdown (nicht nur am Ende).
llm_validation_headers = os.getenv(
"MINDNET_LLM_VALIDATION_HEADERS",
"Unzugeordnete Kanten,Edge Pool,Candidates"
)
if pool_match:
header_list = [h.strip() for h in llm_validation_headers.split(",") if h.strip()]
# Fallback auf Defaults, falls leer
if not header_list:
header_list = ["Unzugeordnete Kanten", "Edge Pool", "Candidates"]
# Header-Ebene konfigurierbar (Default: 3 für ###)
llm_validation_level = int(os.getenv("MINDNET_LLM_VALIDATION_HEADER_LEVEL", "3"))
header_level_pattern = "#" * llm_validation_level
# Regex-Pattern mit konfigurierbaren Headern und Ebene
# WP-24c v4.2.0: finditer statt search, um ALLE Zonen zu finden (auch mitten im Dokument)
# Zone endet bei einem neuen Header (jeder Ebene) oder am Dokument-Ende
header_pattern = "|".join(re.escape(h) for h in header_list)
zone_pattern = rf'^{re.escape(header_level_pattern)}\s*(?:{header_pattern})\s*\n(.*?)(?=\n#|$)'
for pool_match in re.finditer(zone_pattern, body_text, re.DOTALL | re.IGNORECASE | re.MULTILINE):
global_edges = parse_edges_robust(pool_match.group(1))
for e_str in global_edges:
parts = e_str.split(':', 1)

View File

@ -23,19 +23,34 @@ from .graph_extractors import (
)
# WP-24c v4.2.0: Header-basierte Identifikation von Note-Scope Zonen
NOTE_SCOPE_ZONE_HEADERS = [
"Smart Edges",
"Relationen",
"Global Links",
"Note-Level Relations",
"Globale Verbindungen"
]
# Konfigurierbar via MINDNET_NOTE_SCOPE_ZONE_HEADERS (komma-separiert)
def get_note_scope_zone_headers() -> List[str]:
"""
Lädt die konfigurierten Header-Namen für Note-Scope Zonen.
Fallback auf Defaults, falls nicht konfiguriert.
"""
import os
headers_env = os.getenv(
"MINDNET_NOTE_SCOPE_ZONE_HEADERS",
"Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen"
)
header_list = [h.strip() for h in headers_env.split(",") if h.strip()]
# Fallback auf Defaults, falls leer
if not header_list:
header_list = [
"Smart Edges",
"Relationen",
"Global Links",
"Note-Level Relations",
"Globale Verbindungen"
]
return header_list
def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
"""
WP-24c v4.2.0: Extrahiert Note-Scope Zonen aus Markdown.
Identifiziert Sektionen mit spezifischen Headern (z.B. "## Smart Edges")
Identifiziert Sektionen mit spezifischen Headern (konfigurierbar via .env)
und extrahiert alle darin enthaltenen Links.
Returns:
@ -46,8 +61,14 @@ def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
edges: List[Tuple[str, str]] = []
# Regex für Header-Erkennung (## oder ###)
header_pattern = r'^#{2,3}\s+(.+?)$'
# WP-24c v4.2.0: Konfigurierbare Header-Ebene
import os
import re
note_scope_level = int(os.getenv("MINDNET_NOTE_SCOPE_HEADER_LEVEL", "2"))
header_level_pattern = "#" * note_scope_level
# Regex für Header-Erkennung (konfigurierbare Ebene)
header_pattern = rf'^{re.escape(header_level_pattern)}\s+(.+?)$'
lines = markdown_body.split('\n')
in_zone = False
@ -60,9 +81,11 @@ def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
header_text = header_match.group(1).strip()
# Prüfe, ob dieser Header eine Note-Scope Zone ist
# WP-24c v4.2.0: Dynamisches Laden der konfigurierten Header
zone_headers = get_note_scope_zone_headers()
is_zone_header = any(
header_text.lower() == zone_header.lower()
for zone_header in NOTE_SCOPE_ZONE_HEADERS
for zone_header in zone_headers
)
if is_zone_header:

View File

@ -45,4 +45,19 @@ MINDNET_VAULT_ROOT=./vault_prod
MINDNET_VOCAB_PATH=/mindnet/vault/mindnet/_system/dictionary/edge_vocabulary.md
# Change Detection für effiziente Re-Imports
MINDNET_CHANGE_DETECTION_MODE=full
MINDNET_CHANGE_DETECTION_MODE=full
# --- WP-24c v4.2.0: Konfigurierbare Markdown-Header für Edge-Zonen ---
# Komma-separierte Liste von Headern für LLM-Validierung
# Format: Header1,Header2,Header3
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates
# Header-Ebene für LLM-Validierung (1-6, Default: 3 für ###)
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
# Komma-separierte Liste von Headern für Note-Scope Zonen
# Format: Header1,Header2,Header3
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen
# Header-Ebene für Note-Scope Zonen (1-6, Default: 2 für ##)
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2

View File

@ -50,6 +50,11 @@ Diese Variablen steuern die Infrastruktur, Pfade und globale Timeouts. Seit der
| `MINDNET_LL_BACKGROUND_LIMIT`| `2` | **Traffic Control:** Max. parallele Hintergrund-Tasks (Semaphore). |
| `MINDNET_CHANGE_DETECTION_MODE` | `full` | `full` (Text + Meta) oder `body` (nur Text). |
| `MINDNET_DEFAULT_RETRIEVER_WEIGHT` | `1.0` | **Neu (WP-22):** Systemweiter Standard für das Retriever-Gewicht einer Notiz. |
| `MINDNET_LLM_VALIDATION_HEADERS` | `Unzugeordnete Kanten,Edge Pool,Candidates` | **Neu (v4.2.0):** Komma-separierte Header-Namen für LLM-Validierung. |
| `MINDNET_LLM_VALIDATION_HEADER_LEVEL` | `3` | **Neu (v4.2.0):** Header-Ebene für LLM-Validierung (1-6, Default: 3 für ###). |
| `MINDNET_NOTE_SCOPE_ZONE_HEADERS` | `Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen` | **Neu (v4.2.0):** Komma-separierte Header-Namen für Note-Scope Zonen. |
| `MINDNET_NOTE_SCOPE_HEADER_LEVEL` | `2` | **Neu (v4.2.0):** Header-Ebene für Note-Scope Zonen (1-6, Default: 2 für ##). |
| `MINDNET_IGNORE_FOLDERS` | *(leer)* | **Neu (v4.1.0):** Komma-separierte Liste von Ordnernamen, die beim Import ignoriert werden. |
---

View File

@ -0,0 +1,242 @@
# Konfiguration von Edge-Zonen Headern (v4.2.0)
**Version:** v4.2.0
**Status:** Aktiv
## Übersicht
Das Mindnet-System unterstützt zwei Arten von speziellen Markdown-Sektionen für Kanten:
1. **LLM-Validierung Zonen** - Links, die vom LLM validiert werden
2. **Note-Scope Zonen** - Links, die der gesamten Note zugeordnet werden
Die Header-Namen für beide Zonen-Typen sind über Umgebungsvariablen konfigurierbar.
## Konfiguration via .env
### LLM-Validierung Header
**Umgebungsvariablen:**
- `MINDNET_LLM_VALIDATION_HEADERS` - Komma-separierte Liste von Header-Namen
- `MINDNET_LLM_VALIDATION_HEADER_LEVEL` - Header-Ebene (1-6, Default: 3 für `###`)
**Format:** Komma-separierte Liste von Header-Namen
**Default:**
```
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
```
**Beispiel:**
```env
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates,Zu prüfende Links
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
```
**Verwendung in Markdown:**
```markdown
### Unzugeordnete Kanten
related_to:Ziel-Notiz
depends_on:Andere Notiz
```
**Wichtig:** Diese Bereiche werden **nicht als Chunks angelegt**, sondern nur die Kanten extrahiert.
### Note-Scope Zone Header
**Umgebungsvariablen:**
- `MINDNET_NOTE_SCOPE_ZONE_HEADERS` - Komma-separierte Liste von Header-Namen
- `MINDNET_NOTE_SCOPE_HEADER_LEVEL` - Header-Ebene (1-6, Default: 2 für `##`)
**Format:** Komma-separierte Liste von Header-Namen
**Default:**
```
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
```
**Beispiel:**
```env
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Globale Verbindungen,Note-Level Links
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
```
**Verwendung in Markdown:**
```markdown
## Smart Edges
[[rel:depends_on|Globale Notiz]]
[[rel:part_of|System-Übersicht]]
```
**Wichtig:** Diese Bereiche werden **nicht als Chunks angelegt**, sondern nur die Kanten extrahiert.
## Konfiguration in prod.env
Fügen Sie die folgenden Zeilen zu Ihrer `.env` oder `config/prod.env` hinzu:
```env
# --- WP-24c v4.2.0: Konfigurierbare Markdown-Header für Edge-Zonen ---
# Komma-separierte Liste von Headern für LLM-Validierung
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates
# Header-Ebene für LLM-Validierung (1-6, Default: 3 für ###)
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
# Komma-separierte Liste von Headern für Note-Scope Zonen
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen
# Header-Ebene für Note-Scope Zonen (1-6, Default: 2 für ##)
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
```
**Wichtig:** Beide Zonen-Typen werden **nicht als Chunks angelegt**. Nur die Kanten werden extrahiert, der Text selbst wird vom Chunking ausgeschlossen.
## Unterschiede
### LLM-Validierung Zonen
- **Header-Ebene:** Konfigurierbar via `MINDNET_LLM_VALIDATION_HEADER_LEVEL` (Default: 3 = `###`)
- **Zweck:** Links werden vom LLM validiert
- **Provenance:** `global_pool`
- **Scope:** `chunk` (wird Chunks zugeordnet)
- **Aktivierung:** Nur wenn `enable_smart_edge_allocation: true`
- **Chunking:****Diese Bereiche werden NICHT als Chunks angelegt** - nur Kanten werden extrahiert
**Beispiel:**
```markdown
### Unzugeordnete Kanten
related_to:Mögliche Verbindung
depends_on:Unsichere Notiz
```
### Note-Scope Zonen
- **Header-Ebene:** Konfigurierbar via `MINDNET_NOTE_SCOPE_HEADER_LEVEL` (Default: 2 = `##`)
- **Zweck:** Links werden der gesamten Note zugeordnet
- **Provenance:** `explicit:note_zone`
- **Scope:** `note` (Note-weite Verbindung)
- **Aktivierung:** Immer aktiv
- **Chunking:****Diese Bereiche werden NICHT als Chunks angelegt** - nur Kanten werden extrahiert
**Beispiel:**
```markdown
## Smart Edges
[[rel:depends_on|Globale Notiz]]
[[rel:part_of|System-Übersicht]]
```
## Best Practices
### ✅ Empfohlen
1. **Konsistente Header-Namen:**
- Nutzen Sie aussagekräftige Namen
- Dokumentieren Sie die verwendeten Header in Ihrem Team
2. **Minimale Konfiguration:**
- Nutzen Sie die Defaults, wenn möglich
- Nur bei Bedarf anpassen
3. **Dokumentation:**
- Dokumentieren Sie benutzerdefinierte Header in Ihrer Projekt-Dokumentation
### ❌ Vermeiden
1. **Zu viele Header:**
- Zu viele Optionen können verwirrend sein
- Beschränken Sie sich auf 3-5 Header pro Typ
2. **Ähnliche Namen:**
- Vermeiden Sie Header, die sich zu ähnlich sind
- Klare Unterscheidung zwischen LLM-Validierung und Note-Scope
## Technische Details
### Code-Referenzen
- **LLM-Validierung:** `app/core/chunking/chunking_processor.py` (Zeile 66-72)
- **Note-Scope Zonen:** `app/core/graph/graph_derive_edges.py``get_note_scope_zone_headers()`
### Fallback-Verhalten
- Wenn die Umgebungsvariable nicht gesetzt ist, werden die Defaults verwendet
- Wenn die Variable leer ist, werden ebenfalls die Defaults verwendet
- Header-Namen werden case-insensitive verglichen
### Regex-Escape
- Header-Namen werden automatisch für Regex escaped
- Sonderzeichen in Header-Namen sind sicher
## Beispiel-Konfiguration
```env
# Eigene Header-Namen für LLM-Validierung (H3)
MINDNET_LLM_VALIDATION_HEADERS=Zu prüfende Links,Kandidaten,Edge Pool
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
# Eigene Header-Namen für Note-Scope Zonen (H2)
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Globale Relationen,Note-Verbindungen,Smart Links
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
```
**Alternative:** Beide auf H2 setzen:
```env
MINDNET_LLM_VALIDATION_HEADER_LEVEL=2
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
```
**Verwendung:**
```markdown
---
type: decision
title: Meine Notiz
---
# Inhalt
## Globale Relationen
[[rel:depends_on|System-Architektur]]
### Zu prüfende Links
related_to:Mögliche Verbindung
```
## FAQ
**Q: Kann ich beide Zonen-Typen in einer Notiz verwenden?**
A: Ja, beide können gleichzeitig verwendet werden.
**Q: Was passiert, wenn ein Header in beiden Listen steht?**
A: Die Note-Scope Zone hat Vorrang (wird als Note-Scope behandelt).
**Q: Können Header-Namen Leerzeichen enthalten?**
A: Ja, Leerzeichen werden beibehalten.
**Q: Werden Header-Namen case-sensitive verglichen?**
A: Nein, der Vergleich ist case-insensitive.
**Q: Kann ich Header-Namen mit Sonderzeichen verwenden?**
A: Ja, Sonderzeichen werden automatisch für Regex escaped.
## Zusammenfassung
- ✅ **LLM-Validierung:**
- `MINDNET_LLM_VALIDATION_HEADERS` (Header-Namen, komma-separiert)
- `MINDNET_LLM_VALIDATION_HEADER_LEVEL` (Header-Ebene 1-6, Default: 3)
- ❌ **Nicht als Chunks angelegt** - nur Kanten werden extrahiert
- ✅ **Note-Scope Zonen:**
- `MINDNET_NOTE_SCOPE_ZONE_HEADERS` (Header-Namen, komma-separiert)
- `MINDNET_NOTE_SCOPE_HEADER_LEVEL` (Header-Ebene 1-6, Default: 2)
- ❌ **Nicht als Chunks angelegt** - nur Kanten werden extrahiert
- ✅ **Format:** Komma-separierte Liste für Header-Namen
- ✅ **Fallback:** Defaults werden verwendet, falls nicht konfiguriert
- ✅ **Case-insensitive:** Header-Namen werden case-insensitive verglichen