394 lines
11 KiB
Markdown
394 lines
11 KiB
Markdown
|
|
# Chain-Identifikation und Template Matching
|
|||
|
|
|
|||
|
|
## Übersicht
|
|||
|
|
|
|||
|
|
Das System identifiziert und füllt Chains durch einen mehrstufigen Prozess:
|
|||
|
|
|
|||
|
|
1. **Graph-Indexierung**: Erfassung aller Edges (Kanten) im lokalen Subgraph
|
|||
|
|
2. **Candidate-Node-Sammlung**: Identifikation potenzieller Knoten für Template-Slots
|
|||
|
|
3. **Template Matching**: Backtracking-Algorithmus zur optimalen Slot-Zuordnung
|
|||
|
|
4. **Link-Validierung**: Prüfung, ob Edges zwischen zugewiesenen Slots existieren
|
|||
|
|
5. **Scoring & Confidence**: Bewertung der Match-Qualität
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Graph-Indexierung
|
|||
|
|
|
|||
|
|
### Edge-Erfassung
|
|||
|
|
|
|||
|
|
Der **Chain Inspector** (`src/analysis/chainInspector.ts`) erfasst alle Edges im lokalen Subgraph:
|
|||
|
|
|
|||
|
|
- **Current Section**: Die aktuelle Section (file + heading)
|
|||
|
|
- **Incoming Edges**: Edges, die zur aktuellen Section zeigen
|
|||
|
|
- **Outgoing Edges**: Edges, die von der aktuellen Section ausgehen
|
|||
|
|
- **Neighbor Notes**: Verbundene Notes werden geladen, um den Subgraph zu erweitern
|
|||
|
|
|
|||
|
|
### Edge-Scopes
|
|||
|
|
|
|||
|
|
Edges können drei Scopes haben:
|
|||
|
|
|
|||
|
|
- **`section`**: Section-spezifisch (`[[Note#Heading]]`)
|
|||
|
|
- **`note`**: Note-weit (`[[Note]]` ohne Heading)
|
|||
|
|
- **`candidate`**: In "## Kandidaten" Zone
|
|||
|
|
|
|||
|
|
### Filterung
|
|||
|
|
|
|||
|
|
Edges werden basierend auf `InspectorOptions` gefiltert:
|
|||
|
|
|
|||
|
|
- `includeNoteLinks`: Ob Note-Scope Edges einbezogen werden
|
|||
|
|
- `includeCandidates`: Ob Candidate Edges einbezogen werden
|
|||
|
|
- `maxDepth`: Maximale Tiefe für Path-Traversal
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Candidate-Node-Sammlung
|
|||
|
|
|
|||
|
|
### Prozess (`buildCandidateNodes`)
|
|||
|
|
|
|||
|
|
Für jeden Edge im Subgraph werden **Candidate Nodes** erstellt:
|
|||
|
|
|
|||
|
|
1. **Source Nodes**: Alle Knoten, von denen Edges ausgehen
|
|||
|
|
2. **Target Nodes**: Alle Knoten, zu denen Edges zeigen
|
|||
|
|
3. **Note-Type-Extraktion**: Frontmatter `type:` wird aus jeder Note extrahiert
|
|||
|
|
4. **Deduplizierung**: Gleiche Knoten (file + heading) werden nur einmal erfasst
|
|||
|
|
|
|||
|
|
### Node-Repräsentation
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
interface CandidateNode {
|
|||
|
|
nodeKey: {
|
|||
|
|
file: string;
|
|||
|
|
heading: string | null;
|
|||
|
|
};
|
|||
|
|
noteType: string; // z.B. "insight", "decision", "experience"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Maximale Anzahl
|
|||
|
|
|
|||
|
|
Standardmäßig werden maximal **30 Candidate Nodes** gesammelt (konfigurierbar).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Template Matching
|
|||
|
|
|
|||
|
|
### Template-Definition (`chain_templates.yaml`)
|
|||
|
|
|
|||
|
|
Jedes Template definiert:
|
|||
|
|
|
|||
|
|
- **Slots**: Positionen in der Chain mit erlaubten Note-Types
|
|||
|
|
- **Links**: Erwartete Verbindungen zwischen Slots mit erlaubten Edge-Roles
|
|||
|
|
|
|||
|
|
**Beispiel: `loop_learning`**
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
slots:
|
|||
|
|
- id: experience
|
|||
|
|
allowed_node_types: [experience, journal, event]
|
|||
|
|
- id: learning
|
|||
|
|
allowed_node_types: [insight, principle, value, belief, skill, trait]
|
|||
|
|
- id: behavior
|
|||
|
|
allowed_node_types: [habit, decision, task]
|
|||
|
|
- id: feedback
|
|||
|
|
allowed_node_types: [experience, journal, event, state]
|
|||
|
|
|
|||
|
|
links:
|
|||
|
|
- from: experience
|
|||
|
|
to: learning
|
|||
|
|
allowed_edge_roles: [causal, provenance, influences]
|
|||
|
|
- from: learning
|
|||
|
|
to: behavior
|
|||
|
|
allowed_edge_roles: [influences, enables_constraints, causal]
|
|||
|
|
- from: behavior
|
|||
|
|
to: feedback
|
|||
|
|
allowed_edge_roles: [causal, influences]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Backtracking-Algorithmus (`findBestAssignment`)
|
|||
|
|
|
|||
|
|
Der Algorithmus findet die **beste Slot-Zuordnung** durch systematisches Ausprobieren:
|
|||
|
|
|
|||
|
|
1. **Slot-Filterung**: Für jeden Slot werden nur Candidate Nodes gefiltert, die den `allowed_node_types` entsprechen
|
|||
|
|
2. **Backtracking**: Rekursives Durchprobieren aller möglichen Zuordnungen
|
|||
|
|
3. **Distinct Nodes**: Jeder Knoten kann nur einmal zugeordnet werden (wenn `distinct_nodes: true`)
|
|||
|
|
4. **Scoring**: Jede vollständige Zuordnung wird bewertet
|
|||
|
|
5. **Best Match**: Die Zuordnung mit dem höchsten Score wird zurückgegeben
|
|||
|
|
|
|||
|
|
### Slot-Zuordnung
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
function backtrack(assignment: Map<string, CandidateNode>, slotIndex: number) {
|
|||
|
|
// Wenn alle Slots zugeordnet sind:
|
|||
|
|
if (slotIndex >= slots.length) {
|
|||
|
|
const result = scoreAssignment(...); // Bewerte Zuordnung
|
|||
|
|
if (result.score > bestScore) {
|
|||
|
|
bestMatch = result; // Speichere besten Match
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Probiere jeden passenden Candidate für diesen Slot
|
|||
|
|
for (const candidate of slotCandidates.get(slot.id)) {
|
|||
|
|
if (!alreadyAssigned(candidate)) {
|
|||
|
|
assignment.set(slot.id, candidate);
|
|||
|
|
backtrack(assignment, slotIndex + 1); // Rekursiv weiter
|
|||
|
|
assignment.delete(slot.id); // Backtrack
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Auch: Slot leer lassen (für unvollständige Chains)
|
|||
|
|
backtrack(assignment, slotIndex + 1);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Link-Validierung
|
|||
|
|
|
|||
|
|
### Edge-Suche (`findEdgeBetween`)
|
|||
|
|
|
|||
|
|
Für jedes Template-Link wird geprüft, ob ein Edge zwischen den zugewiesenen Slots existiert:
|
|||
|
|
|
|||
|
|
1. **Node-Keys**: `fromKey = "file:heading"`, `toKey = "file:heading"`
|
|||
|
|
2. **Edge-Suche**: Durchsuche `allEdges` nach passendem Edge
|
|||
|
|
3. **Canonicalisierung**: Edge-Typ wird auf Canonical gemappt (via `edge_vocabulary.md`)
|
|||
|
|
4. **Role-Mapping**: Canonical Edge-Typ wird auf Role gemappt (via `chain_roles.yaml`)
|
|||
|
|
5. **Role-Validierung**: Prüfe, ob Edge-Role in `allowed_edge_roles` enthalten ist
|
|||
|
|
|
|||
|
|
### Role-Evidence
|
|||
|
|
|
|||
|
|
Wenn ein Link erfüllt ist, wird **Role Evidence** gespeichert:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
roleEvidence.push({
|
|||
|
|
from: "learning", // Slot-ID (nicht Dateipfad!)
|
|||
|
|
to: "behavior", // Slot-ID
|
|||
|
|
edgeRole: "causal", // Role aus chain_roles.yaml
|
|||
|
|
rawEdgeType: "resulted_in" // Original Edge-Typ im Vault
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Wichtig**: `roleEvidence` verwendet **Slot-IDs**, nicht Dateipfade!
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Scoring & Confidence
|
|||
|
|
|
|||
|
|
### Scoring (`scoreAssignment`)
|
|||
|
|
|
|||
|
|
**Slot-Scoring**:
|
|||
|
|
- Jeder zugewiesene Slot: **+2 Punkte**
|
|||
|
|
|
|||
|
|
**Link-Scoring**:
|
|||
|
|
- Erfüllter Link (mit erlaubter Role): **+10 Punkte**
|
|||
|
|
- Fehlender Link (wenn `required_links: true`): **-5 Punkte**
|
|||
|
|
- Falsche Role (wenn `required_links: true`): **-5 Punkte**
|
|||
|
|
|
|||
|
|
### Confidence-Berechnung
|
|||
|
|
|
|||
|
|
Die **Confidence** wird nach dem Matching berechnet:
|
|||
|
|
|
|||
|
|
1. **`weak`**: Wenn `slotsComplete === false` (fehlende Slots)
|
|||
|
|
2. **`confirmed`**: Wenn `slotsComplete === true` UND `linksComplete === true` UND mindestens eine causal-ish Role Evidence
|
|||
|
|
3. **`plausible`**: Sonst (Slots vollständig, aber Links unvollständig oder keine causal Roles)
|
|||
|
|
|
|||
|
|
**Causal-ish Roles**: `["causal", "influences", "enables_constraints"]` (konfigurierbar in `chain_templates.yaml`)
|
|||
|
|
|
|||
|
|
### Completeness
|
|||
|
|
|
|||
|
|
- **`slotsComplete`**: `missingSlots.length === 0`
|
|||
|
|
- **`linksComplete`**: `satisfiedLinks === requiredLinks`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Template-Matching-Profile
|
|||
|
|
|
|||
|
|
### Profile-Definition
|
|||
|
|
|
|||
|
|
Profile steuern die Matching-Strenge:
|
|||
|
|
|
|||
|
|
**`discovery`** (schreibfreundlich):
|
|||
|
|
```yaml
|
|||
|
|
required_links: false # Links sind optional
|
|||
|
|
min_slots_filled_for_gap_findings: 2
|
|||
|
|
min_score_for_gap_findings: 8
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**`decisioning`** (strikt):
|
|||
|
|
```yaml
|
|||
|
|
required_links: true # Links sind Pflicht
|
|||
|
|
min_slots_filled_for_gap_findings: 3
|
|||
|
|
min_score_for_gap_findings: 18
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Profile-Auflösung
|
|||
|
|
|
|||
|
|
1. **Settings**: Plugin-Einstellung `templateMatchingProfile`
|
|||
|
|
2. **Template**: Template-spezifische `matching.required_links`
|
|||
|
|
3. **Defaults**: `defaults.matching.required_links`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Beispiel: Loop Learning Match
|
|||
|
|
|
|||
|
|
### Input
|
|||
|
|
|
|||
|
|
- **Current Section**: `Tests/03_insight_transformation.md#Kern`
|
|||
|
|
- **Edges im Subgraph**: 8 Edges (1 current, 7 neighbors)
|
|||
|
|
- **Candidate Nodes**: 4 Nodes (experience, learning, behavior, feedback)
|
|||
|
|
|
|||
|
|
### Template
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
name: loop_learning
|
|||
|
|
slots: [experience, learning, behavior, feedback]
|
|||
|
|
links:
|
|||
|
|
- experience → learning
|
|||
|
|
- learning → behavior
|
|||
|
|
- behavior → feedback
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Matching-Prozess
|
|||
|
|
|
|||
|
|
1. **Slot-Zuordnung** (Backtracking):
|
|||
|
|
- `experience` → `Tests/02_event_trigger_detail.md#Detail` (noteType: `event`)
|
|||
|
|
- `learning` → `Tests/03_insight_transformation.md#Kern` (noteType: `insight`)
|
|||
|
|
- `behavior` → `Tests/04_decision_outcome.md#Entscheidung` (noteType: `decision`)
|
|||
|
|
- `feedback` → `Tests/01_experience_trigger.md#Kontext` (noteType: `experience`)
|
|||
|
|
|
|||
|
|
2. **Link-Validierung**:
|
|||
|
|
- `experience → learning`: ❌ Kein Edge gefunden
|
|||
|
|
- `learning → behavior`: ✅ Edge `resulted_in` mit Role `causal` gefunden
|
|||
|
|
- `behavior → feedback`: ❌ Kein Edge gefunden
|
|||
|
|
|
|||
|
|
3. **Scoring**:
|
|||
|
|
- Slots: 4 × 2 = **8 Punkte**
|
|||
|
|
- Links: 1 × 10 = **10 Punkte**
|
|||
|
|
- **Gesamt: 18 Punkte**
|
|||
|
|
|
|||
|
|
4. **Resultat**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"templateName": "loop_learning",
|
|||
|
|
"score": 18,
|
|||
|
|
"slotsComplete": true,
|
|||
|
|
"linksComplete": false,
|
|||
|
|
"satisfiedLinks": 1,
|
|||
|
|
"requiredLinks": 3,
|
|||
|
|
"confidence": "plausible",
|
|||
|
|
"roleEvidence": [
|
|||
|
|
{
|
|||
|
|
"from": "learning",
|
|||
|
|
"to": "behavior",
|
|||
|
|
"edgeRole": "causal",
|
|||
|
|
"rawEdgeType": "resulted_in"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Wichtige Konzepte
|
|||
|
|
|
|||
|
|
### Slot-IDs vs. Dateipfade
|
|||
|
|
|
|||
|
|
- **Slot-IDs**: Template-interne Bezeichner (`"learning"`, `"behavior"`)
|
|||
|
|
- **Dateipfade**: Vault-Pfade (`"Tests/03_insight_transformation.md"`)
|
|||
|
|
- **roleEvidence** verwendet Slot-IDs, nicht Dateipfade!
|
|||
|
|
|
|||
|
|
### Canonicalisierung
|
|||
|
|
|
|||
|
|
- **Intern**: Edge-Typen werden auf Canonical gemappt (für Analyse)
|
|||
|
|
- **Vault**: Original Edge-Typen (Aliase) bleiben unverändert
|
|||
|
|
- **Schreiben**: Plugin schreibt keine Canonicals, nur User-gewählte Typen
|
|||
|
|
|
|||
|
|
### Distinct Nodes
|
|||
|
|
|
|||
|
|
Wenn `distinct_nodes: true`:
|
|||
|
|
- Jeder Knoten kann nur **einmal** pro Template zugeordnet werden
|
|||
|
|
- Verhindert zirkuläre Zuordnungen
|
|||
|
|
|
|||
|
|
### Edge-Target-Resolution
|
|||
|
|
|
|||
|
|
Edges können verschiedene Pfad-Formate verwenden:
|
|||
|
|
- Vollständig: `Tests/03_insight_transformation.md`
|
|||
|
|
- Basename: `03_insight_transformation`
|
|||
|
|
- Wikilink: `[[03_insight_transformation]]`
|
|||
|
|
|
|||
|
|
Das System normalisiert alle Formate für konsistente Matching.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. Integration mit Chain Workbench
|
|||
|
|
|
|||
|
|
Der **Chain Workbench** nutzt die Template Matches, um:
|
|||
|
|
|
|||
|
|
1. **Todos zu generieren**:
|
|||
|
|
- `missing_slot`: Fehlende Slot-Zuordnungen
|
|||
|
|
- `missing_link`: Fehlende Links zwischen Slots
|
|||
|
|
- `weak_roles`: Links mit schwachen (nicht-causalen) Roles
|
|||
|
|
|
|||
|
|
2. **Status zu berechnen**:
|
|||
|
|
- `complete`: Slots + Links vollständig
|
|||
|
|
- `near_complete`: 1-2 Links fehlen oder 1 Slot fehlt
|
|||
|
|
- `partial`: Mehrere Lücken
|
|||
|
|
- `weak`: Nur structural/temporal Roles, keine causal Roles
|
|||
|
|
|
|||
|
|
3. **Actions anzubieten**:
|
|||
|
|
- `insert_edge_forward`: Edge einfügen (von Slot A nach Slot B)
|
|||
|
|
- `link_existing`: Bestehenden Knoten verlinken
|
|||
|
|
- `create_note_via_interview`: Neue Note für Slot erstellen
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. Konfigurationsdateien
|
|||
|
|
|
|||
|
|
### `chain_templates.yaml`
|
|||
|
|
|
|||
|
|
Definiert Template-Strukturen:
|
|||
|
|
- Slots mit erlaubten Note-Types
|
|||
|
|
- Links mit erlaubten Edge-Roles
|
|||
|
|
- Profile (discovery, decisioning)
|
|||
|
|
|
|||
|
|
### `chain_roles.yaml`
|
|||
|
|
|
|||
|
|
Mappt Edge-Typen auf Roles:
|
|||
|
|
- `causal`: Direkte Kausalität
|
|||
|
|
- `influences`: Indirekte Einflussnahme
|
|||
|
|
- `enables_constraints`: Ermöglicht/Einschränkt
|
|||
|
|
- `structural`: Strukturelle Beziehung
|
|||
|
|
- `temporal`: Zeitliche Beziehung
|
|||
|
|
|
|||
|
|
### `edge_vocabulary.md`
|
|||
|
|
|
|||
|
|
Definiert Canonical Edge-Typen und Aliase:
|
|||
|
|
- Canonical: `resulted_in`
|
|||
|
|
- Aliase: `führt_zu`, `resultiert_in`, etc.
|
|||
|
|
|
|||
|
|
### `graph_schema.md`
|
|||
|
|
|
|||
|
|
Definiert Note-Type-Kompatibilität:
|
|||
|
|
- Welche Edge-Typen sind typisch für `insight → decision`?
|
|||
|
|
- Welche Edge-Typen sind verboten?
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Zusammenfassung
|
|||
|
|
|
|||
|
|
Das System identifiziert Chains durch:
|
|||
|
|
|
|||
|
|
1. **Graph-Indexierung**: Erfassung aller Edges im lokalen Subgraph
|
|||
|
|
2. **Candidate-Sammlung**: Identifikation potenzieller Knoten
|
|||
|
|
3. **Backtracking-Matching**: Optimale Slot-Zuordnung
|
|||
|
|
4. **Link-Validierung**: Prüfung vorhandener Edges
|
|||
|
|
5. **Scoring**: Bewertung der Match-Qualität
|
|||
|
|
|
|||
|
|
Das Ergebnis sind **Template Matches** mit:
|
|||
|
|
- Slot-Zuordnungen (welche Knoten in welchen Slots)
|
|||
|
|
- Link-Status (welche Links erfüllt/fehlend)
|
|||
|
|
- Confidence (confirmed/plausible/weak)
|
|||
|
|
- Role Evidence (welche Edges welche Roles haben)
|
|||
|
|
|
|||
|
|
Diese Matches werden dann im **Chain Workbench** verwendet, um konkrete Todos und Actions zu generieren.
|