WP24c - Agentic Edge Validation & Chunk-Aware Multigraph-System (v4.5.8) #22

Merged
Lars merged 71 commits from WP24c into main 2026-01-12 10:53:20 +01:00
7 changed files with 401 additions and 28 deletions
Showing only changes of commit 003a270548 - Show all commits

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