# Mindnet Causal Assistant - Entwicklerhandbuch > **Version:** 1.0.0 > **Stand:** 2025-01-XX > **Zielgruppe:** Entwickler, die am Plugin arbeiten oder es erweitern --- ## Inhaltsverzeichnis 1. [Überblick](#überblick) 2. [Projekt-Struktur](#projekt-struktur) 3. [Entwicklungsumgebung](#entwicklungsumgebung) 4. [Code-Architektur](#code-architektur) 5. [Hauptmodule](#hauptmodule) 6. [Erweiterungen entwickeln](#erweiterungen-entwickeln) 7. [Testing](#testing) 8. [Build & Deployment](#build--deployment) --- ## Überblick Das Mindnet Causal Assistant Plugin ist ein **Obsidian Community Plugin** geschrieben in **TypeScript**, gebündelt mit **esbuild** zu einer einzelnen `main.js` Datei. ### Technologie-Stack - **Sprache:** TypeScript (strict mode) - **Bundler:** esbuild - **Package Manager:** npm - **Testing:** Vitest - **Linting:** ESLint mit obsidianmd Plugin ### Projekt-Typ - **Obsidian Community Plugin** - **Entry Point:** `src/main.ts` → `main.js` - **Release-Artefakte:** `main.js`, `manifest.json`, optional `styles.css` --- ## Projekt-Struktur ``` mindnet_obsidian/ ├── src/ # TypeScript Source Code │ ├── main.ts # Plugin Entry Point │ ├── settings.ts # Settings Interface & Defaults │ ├── analysis/ # Chain Inspector & Analysis │ │ ├── chainInspector.ts # Haupt-Analyse-Engine │ │ ├── templateMatching.ts # Template Matching Algorithmus │ │ ├── graphIndex.ts # Graph-Indexierung │ │ ├── sectionContext.ts # Section-Context-Handling │ │ └── severityPolicy.ts # Severity-Policy-Logik │ ├── commands/ # Command Implementations │ │ ├── inspectChainsCommand.ts │ │ └── fixFindingsCommand.ts │ ├── dictionary/ # Config Loader & Parser │ │ ├── ChainRolesLoader.ts │ │ ├── ChainTemplatesLoader.ts │ │ ├── DictionaryLoader.ts │ │ ├── parseChainRoles.ts │ │ ├── parseChainTemplates.ts │ │ └── types.ts │ ├── entityPicker/ # Entity Selection UI │ │ ├── noteIndex.ts │ │ ├── folderTree.ts │ │ ├── filters.ts │ │ ├── wikilink.ts │ │ └── types.ts │ ├── export/ # Graph Export │ │ ├── exportGraph.ts │ │ └── types.ts │ ├── graph/ # Graph Building & Traversal │ │ ├── GraphBuilder.ts │ │ ├── GraphIndex.ts │ │ ├── renderChainReport.ts │ │ ├── resolveTarget.ts │ │ └── traverse.ts │ ├── interview/ # Interview Wizard │ │ ├── InterviewConfigLoader.ts │ │ ├── parseInterviewConfig.ts │ │ ├── wizardState.ts │ │ ├── loopState.ts │ │ └── renderer.ts │ ├── lint/ # Linting Engine │ │ ├── LintEngine.ts │ │ └── rules/ │ ├── mapping/ # Semantic Mapping │ │ ├── semanticMappingBuilder.ts │ │ ├── mappingExtractor.ts │ │ ├── mappingBuilder.ts │ │ └── edgeTypeSelector.ts │ ├── parser/ # Markdown Parsing │ │ ├── parseEdgesFromCallouts.ts │ │ ├── parseFrontmatter.ts │ │ └── parseRelLinks.ts │ ├── schema/ # Graph Schema │ ├── ui/ # UI Components │ │ ├── InterviewWizardModal.ts │ │ ├── ProfileSelectionModal.ts │ │ ├── MindnetSettingTab.ts │ │ └── ... │ ├── unresolvedLink/ # Unresolved Link Handling │ ├── vocab/ # Edge Vocabulary │ └── tests/ # Test Files ├── docs/ # Dokumentation ├── scripts/ # Build Scripts │ └── deploy-local.ps1 ├── package.json # Dependencies & Scripts ├── tsconfig.json # TypeScript Config ├── esbuild.config.mjs # Build Config ├── manifest.json # Plugin Manifest └── main.js # Generated Bundle (nicht committen) ``` --- ## Entwicklungsumgebung ### Voraussetzungen - **Node.js:** LTS Version (18+ empfohlen) - **npm:** Inkludiert mit Node.js - **Git:** Für Versionierung - **Obsidian Desktop:** Für Testing ### Setup ```bash # Repository klonen git clone mindnet_obsidian cd mindnet_obsidian # Dependencies installieren npm install # Development Build (Watch Mode) npm run dev # Production Build npm run build # Tests ausführen npm run test # Linting npm run lint ``` ### Development Workflow 1. **Code ändern** in `src/` 2. **Watch Mode** läuft (`npm.cmd run dev`) 3. **Automatisches Rebuild** bei Dateiänderungen 4. **Deploy lokal** (`npm.cmd run deploy:local` oder `npm.cmd run build:deploy`) 5. **Obsidian Plugin reload** (disable/enable) 6. **Testen** --- ## Code-Architektur ### Plugin Lifecycle **Entry Point:** `src/main.ts` ```typescript export default class MindnetCausalAssistantPlugin extends Plugin { settings: MindnetSettings; async onload(): Promise { await this.loadSettings(); this.addSettingTab(new MindnetSettingTab(this.app, this)); // Register commands, event handlers, etc. } onunload(): void { // Cleanup } } ``` ### Settings Management **Datei:** `src/settings.ts` ```typescript export interface MindnetSettings { edgeVocabularyPath: string; graphSchemaPath: string; // ... weitere Settings } export const DEFAULT_SETTINGS: MindnetSettings = { edgeVocabularyPath: "_system/dictionary/edge_vocabulary.md", // ... Defaults }; ``` **Laden/Speichern:** ```typescript async loadSettings(): Promise { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } async saveSettings(): Promise { await this.saveData(this.settings); } ``` ### Command Registration **Pattern:** ```typescript this.addCommand({ id: "mindnet-command-id", name: "Mindnet: Command Name", callback: async () => { // Implementation }, }); ``` **Editor Callbacks:** ```typescript this.addCommand({ id: "mindnet-editor-command", name: "Mindnet: Editor Command", editorCallback: async (editor) => { // Editor-basierte Implementation }, }); ``` --- ## Hauptmodule ### 1. Analysis (`src/analysis/`) **Zweck:** Chain Inspector & Template Matching **Hauptdateien:** - `chainInspector.ts` - Haupt-Analyse-Engine - `templateMatching.ts` - Template Matching Algorithmus - `graphIndex.ts` - Graph-Indexierung für Traversal - `sectionContext.ts` - Section-Context-Extraktion - `severityPolicy.ts` - Severity-Policy-Logik **Verwendung:** ```typescript import { executeInspectChains } from "./commands/inspectChainsCommand"; await executeInspectChains( app, editor, activeFile.path, chainRoles, settings, {}, chainTemplates, templatesLoadResult ); ``` ### 2. Dictionary (`src/dictionary/`) **Zweck:** Config Loading & Parsing **Hauptdateien:** - `ChainRolesLoader.ts` - Lädt chain_roles.yaml - `ChainTemplatesLoader.ts` - Lädt chain_templates.yaml - `DictionaryLoader.ts` - Basis-Loader mit Error-Handling - `parseChainRoles.ts` - YAML → ChainRolesConfig Parser - `parseChainTemplates.ts` - YAML → ChainTemplatesConfig Parser **Pattern:** ```typescript const result = await ChainRolesLoader.load( app, settings.chainRolesPath, lastKnownGood ); if (result.data !== null) { // Use result.data } ``` **Last-Known-Good:** - Bei Fehlern wird letzte gültige Config verwendet - `lastKnownGood` Parameter für Fallback ### 3. Graph (`src/graph/`) **Zweck:** Graph Building & Traversal **Hauptdateien:** - `GraphBuilder.ts` - Baut Graph aus Vault - `GraphIndex.ts` - Indexiert Edges für Traversal - `traverse.ts` - BFS/DFS Traversal - `resolveTarget.ts` - Link-Target-Auflösung - `renderChainReport.ts` - Report-Generierung **Verwendung:** ```typescript const graph = await buildGraph(app, vocabulary); const index = buildIndex(graph.edges); const paths = traverseForward(index, startId, maxHops, maxPaths); ``` ### 4. Interview (`src/interview/`) **Zweck:** Interview Wizard für Note-Erstellung **Hauptdateien:** - `InterviewConfigLoader.ts` - Lädt interview_config.yaml - `parseInterviewConfig.ts` - YAML → InterviewConfig Parser - `wizardState.ts` - Wizard State Management (Steps, Loops, Nested Loops) - `loopState.ts` - Loop State Management (nested loops) - `renderer.ts` - Output-Rendering (Section-basiert) - `writeFrontmatter.ts` - Frontmatter-Generierung - `sectionKeyResolver.ts` - Section-Key-Auflösung - `extractTargetFromAnchor.ts` - Target-Extraktion aus Anchors - `slugify.ts` - Slug-Generierung für Dateinamen - `types.ts` - Interview-Types **Verwendung:** ```typescript import { InterviewConfigLoader } from "./interview/InterviewConfigLoader"; import { writeFrontmatter } from "./interview/writeFrontmatter"; import { InterviewWizardModal } from "./ui/InterviewWizardModal"; // Config laden const result = await InterviewConfigLoader.loadConfig(app, configPath); const config = result.config; // Frontmatter schreiben const frontmatter = writeFrontmatter({ id: "note_123", title: "Meine Note", noteType: "experience", interviewProfile: "experience_basic", defaults: profile.defaults, frontmatterWhitelist: config.frontmatterWhitelist, }); // Wizard starten const modal = new InterviewWizardModal(app, profile, onFinish); modal.open(); ``` **Features:** - **Nested Loops:** Unterstützung für verschachtelte Loops - **Section-basierte Ausgabe:** Output wird in Sections geschrieben - **Frontmatter-Whitelist:** Erlaubte Frontmatter-Keys - **Post-Run Actions:** Automatische Actions nach Wizard (z.B. Edger) ### 5. Mapping (`src/mapping/`) **Zweck:** Semantic Mapping Builder **Hauptdateien:** - `semanticMappingBuilder.ts` - Haupt-Mapping-Builder - `mappingExtractor.ts` - Extrahiert existierende Mappings - `mappingBuilder.ts` - Baut neue Mappings - `edgeTypeSelector.ts` - Edge-Type-Selection UI **Verwendung:** ```typescript import { buildSemanticMappings } from "./mapping/semanticMappingBuilder"; const result = await buildSemanticMappings( app, activeFile, settings, allowOverwrite, plugin ); ``` ### 6. Parser (`src/parser/`) **Zweck:** Markdown Parsing **Hauptdateien:** - `parseEdgesFromCallouts.ts` - Extrahiert Edges aus Callouts - `parseFrontmatter.ts` - Frontmatter-Parsing - `parseRelLinks.ts` - Relative Link-Parsing **Verwendung:** ```typescript import { parseEdgesFromCallouts } from "./parser/parseEdgesFromCallouts"; const edges = parseEdgesFromCallouts(content, file, vocabulary); ``` ### 7. UI (`src/ui/`) **Zweck:** UI Components (Modals, Views) **Hauptdateien:** - `InterviewWizardModal.ts` - Interview Wizard UI - `ProfileSelectionModal.ts` - Profil-Auswahl UI - `MindnetSettingTab.ts` - Settings Tab - `EdgeTypeChooserModal.ts` - Edge-Type-Auswahl UI - `EntityPickerModal.ts` - Entity-Auswahl UI - `AdoptNoteModal.ts` - Note-Adoption-Confirmation - `FolderTreeModal.ts` - Folder-Auswahl UI - `LinkPromptModal.ts` - Link-Eingabe UI - `InlineEdgeTypeModal.ts` - Inline Edge-Type-Selection - `ConfirmOverwriteModal.ts` - Overwrite-Confirmation **Pattern:** ```typescript export class MyModal extends Modal { constructor(app: App, onResult: (result: MyResult) => void) { super(app); this.onResult = onResult; } onOpen() { // Build UI } onClose() { // Cleanup } } ``` ### 8. Vocabulary (`src/vocab/`) **Zweck:** Edge Vocabulary Management **Hauptdateien:** - `Vocabulary.ts` - Wrapper-Klasse für Lookup-Methoden - `VocabularyLoader.ts` - Lädt Vocabulary-Datei - `parseEdgeVocabulary.ts` - Parst Markdown zu EdgeVocabulary - `types.ts` - Vocabulary-Types **Verwendung:** ```typescript import { VocabularyLoader } from "./vocab/VocabularyLoader"; import { parseEdgeVocabulary } from "./vocab/parseEdgeVocabulary"; import { Vocabulary } from "./vocab/Vocabulary"; const text = await VocabularyLoader.loadText(app, path); const parsed = parseEdgeVocabulary(text); const vocabulary = new Vocabulary(parsed); const canonical = vocabulary.getCanonical("ausgelöst_durch"); const normalized = vocabulary.normalize("causes"); ``` ### 9. Lint (`src/lint/`) **Zweck:** Linting Engine für Note-Validierung **Hauptdateien:** - `LintEngine.ts` - Haupt-Linting-Engine - `rules/index.ts` - Regel-Registry - `rules/rule_hub_has_causality.ts` - Kausalitäts-Prüfung - `rules/rule_missing_target.ts` - Fehlende Target-Prüfung - `rules/rule_unkown_edge.ts` - Unbekannte Edge-Type-Prüfung - `types.ts` - Lint-Types **Verwendung:** ```typescript import { LintEngine } from "./lint/LintEngine"; const findings = await LintEngine.lintCurrentNote( app, vocabulary, { showCanonicalHints: true } ); ``` ### 10. Export (`src/export/`) **Zweck:** Graph Export zu JSON **Hauptdateien:** - `exportGraph.ts` - Haupt-Export-Funktion - `types.ts` - Export-Types **Verwendung:** ```typescript import { exportGraph } from "./export/exportGraph"; await exportGraph(app, vocabulary, "_system/exports/graph.json"); ``` ### 11. Schema (`src/schema/`) **Zweck:** Graph Schema Loading **Hauptdateien:** - `GraphSchemaLoader.ts` - Lädt Graph-Schema-Datei **Verwendung:** ```typescript import { GraphSchemaLoader } from "./schema/GraphSchemaLoader"; const schema = await GraphSchemaLoader.load(app, schemaPath); const typical = schema.getTypicalEdgeTypes("experience"); ``` ### 12. Unresolved Link (`src/unresolvedLink/`) **Zweck:** Unresolved Link Handling **Hauptdateien:** - `unresolvedLinkHandler.ts` - Haupt-Handler für Link-Clicks - `linkHelpers.ts` - Link-Parsing und -Normalisierung - `adoptHelpers.ts` - Note-Adoption-Logik **Verwendung:** ```typescript import { isUnresolvedLink, normalizeLinkTarget } from "./unresolvedLink/linkHelpers"; import { isAdoptCandidate, evaluateAdoptionConfidence } from "./unresolvedLink/adoptHelpers"; const unresolved = isUnresolvedLink(app, linkTarget, sourcePath); const confidence = evaluateAdoptionConfidence(file, content, maxChars, hint, windowMs); ``` ### 13. Entity Picker (`src/entityPicker/`) **Zweck:** Entity Selection (Notes, Folders) **Hauptdateien:** - `noteIndex.ts` - Baut Index aller Notes - `folderTree.ts` - Folder-Tree-Struktur - `filters.ts` - Filter-Logik - `wikilink.ts` - Wikilink-Parsing - `types.ts` - Entity-Picker-Types **Verwendung:** ```typescript import { buildNoteIndex } from "./entityPicker/noteIndex"; import { buildFolderTree } from "./entityPicker/folderTree"; const index = await buildNoteIndex(app); const tree = buildFolderTree(app); ``` ### 14. Commands (`src/commands/`) **Zweck:** Command Implementations **Hauptdateien:** - `inspectChainsCommand.ts` - Chain Inspector Command - `fixFindingsCommand.ts` - Fix Findings Command **Verwendung:** ```typescript import { executeInspectChains } from "./commands/inspectChainsCommand"; import { executeFixFindings } from "./commands/fixFindingsCommand"; await executeInspectChains(app, editor, filePath, chainRoles, settings, {}, chainTemplates, result); await executeFixFindings(app, editor, filePath, chainRoles, interviewConfig, settings, plugin); ``` --- ## Erweiterungen entwickeln ### 1. Neuen Command hinzufügen **Schritt 1:** Command in `main.ts` registrieren ```typescript this.addCommand({ id: "mindnet-my-command", name: "Mindnet: My Command", callback: async () => { // Implementation }, }); ``` **Schritt 2:** Implementation in separater Datei (optional) ```typescript // src/commands/myCommand.ts export async function executeMyCommand( app: App, settings: MindnetSettings ): Promise { // Implementation } ``` **Schritt 3:** In `main.ts` importieren und verwenden ```typescript import { executeMyCommand } from "./commands/myCommand"; this.addCommand({ id: "mindnet-my-command", name: "Mindnet: My Command", callback: async () => { await executeMyCommand(this.app, this.settings); }, }); ``` ### 2. Neue Lint-Regel hinzufügen **Schritt 1:** Regel-Datei erstellen ```typescript // src/lint/rules/rule_my_rule.ts import type { LintRule, LintFinding } from "../types"; export const ruleMyRule: LintRule = { id: "my_rule", name: "My Rule", severity: "WARN", check: async (app, file, vocabulary): Promise => { const findings: LintFinding[] = []; // Check logic return findings; }, }; ``` **Schritt 2:** Regel registrieren ```typescript // src/lint/rules/index.ts import { ruleMyRule } from "./rule_my_rule"; export const RULES = [ ruleMyRule, // ... weitere Regeln ]; ``` ### 3. Neues Finding hinzufügen **Schritt 1:** Finding-Code definieren ```typescript // In chainInspector.ts oder templateMatching.ts const finding: Finding = { code: "my_finding", severity: "WARN", message: "My finding message", evidence: { /* ... */ }, }; ``` **Schritt 2:** Finding generieren ```typescript if (condition) { findings.push({ code: "my_finding", severity: "WARN", message: "My finding message", evidence: { /* ... */ }, }); } ``` ### 4. Neues UI-Element hinzufügen **Schritt 1:** Modal/Component erstellen ```typescript // src/ui/MyModal.ts import { Modal } from "obsidian"; export class MyModal extends Modal { constructor(app: App, onResult: (result: MyResult) => void) { super(app); this.onResult = onResult; } onOpen() { const { contentEl } = this; // Build UI } onClose() { const { contentEl } = this; contentEl.empty(); } } ``` **Schritt 2:** Modal verwenden ```typescript import { MyModal } from "./ui/MyModal"; new MyModal(this.app, (result) => { // Handle result }).open(); ``` --- ## Testing ### Test-Setup **Framework:** Vitest **Konfiguration:** `vitest.config.ts` **Test-Dateien:** `src/tests/**/*.test.ts` ### Tests ausführen ```bash # Alle Tests npm run test # Watch Mode npm run test -- --watch # Spezifische Datei npm run test -- src/tests/analysis/chainInspector.test.ts ``` ### Test-Pattern ```typescript import { describe, it, expect } from "vitest"; describe("MyModule", () => { it("should do something", () => { const result = myFunction(input); expect(result).toBe(expected); }); }); ``` ### Mocking **Obsidian API Mocking:** ```typescript // src/__mocks__/obsidian.ts export const mockApp = { vault: { /* ... */ }, metadataCache: { /* ... */ }, // ... }; ``` --- ## Build & Deployment ### Development Build ```bash npm run dev ``` **Ergebnis:** - `main.js` wird erstellt (mit Source Maps) - Watch Mode aktiviert - Automatisches Rebuild bei Änderungen ### Production Build ```bash npm run build ``` **Ergebnis:** - `main.js` wird erstellt (minified, ohne Source Maps) - TypeScript-Check wird ausgeführt - Tree-Shaking aktiviert ### Lokales Deployment **Windows (PowerShell):** ```bash npm run deploy:local # oder powershell -ExecutionPolicy Bypass -File scripts/deploy-local.ps1 ``` **Script:** `scripts/deploy-local.ps1` - Kopiert `main.js`, `manifest.json` nach Vault Plugin-Ordner - Optional: `styles.css` falls vorhanden **Zielpfad:** ``` /.obsidian/plugins/mindnet-causal-assistant/ ``` ### Release-Prozess 1. **Version bumpen:** ```bash npm version patch|minor|major ``` - Aktualisiert `manifest.json` und `package.json` - Fügt Eintrag zu `versions.json` hinzu 2. **Build:** ```bash npm run build ``` 3. **Git Commit:** ```bash git add manifest.json versions.json git commit -m "Release v1.0.1" git tag v1.0.1 ``` 4. **GitHub Release:** - Erstelle Release mit Tag `v1.0.1` (ohne `v` Prefix) - Upload `main.js`, `manifest.json`, optional `styles.css` --- ## Code-Standards ### TypeScript - **Strict Mode:** Aktiviert - **No Implicit Any:** Aktiviert - **Strict Null Checks:** Aktiviert - **Module Resolution:** Node ### Code-Organisation - **Ein Datei = Ein Verantwortungsbereich** - **Klare Module-Grenzen** - **Keine zirkulären Dependencies** - **Tests neben Source-Code** ### Naming Conventions - **Dateien:** `camelCase.ts` (z.B. `chainInspector.ts`) - **Klassen:** `PascalCase` (z.B. `ChainInspector`) - **Funktionen:** `camelCase` (z.B. `executeInspectChains`) - **Interfaces:** `PascalCase` (z.B. `MindnetSettings`) - **Types:** `PascalCase` (z.B. `ChainRolesConfig`) ### Kommentare - **JSDoc** für öffentliche APIs - **Inline-Kommentare** für komplexe Logik - **TODO-Kommentare** für geplante Features --- ## Debugging ### Console-Logging ```typescript console.log("[Module] Message", data); console.warn("[Module] Warning", data); console.error("[Module] Error", data); ``` ### Debug-Settings **Settings:** `debugLogging: boolean` **Verwendung:** ```typescript if (this.settings.debugLogging) { console.log("[Module] Debug info", data); } ``` ### DevTools **Öffnen:** `Ctrl+Shift+I` (Windows/Linux) oder `Cmd+Option+I` (Mac) **Console:** Für Logs und Errors **Network:** Für API-Calls (falls vorhanden) **Sources:** Für Source Maps (Development Build) --- ## Bekannte Einschränkungen ### Obsidian API - **Mobile:** Nicht alle APIs verfügbar - **Desktop-only:** `isDesktopOnly: true` im Manifest - **CodeMirror:** Abhängig von Obsidian-Version ### Performance - **Graph Building:** Kann bei großen Vaults langsam sein - **Live-Reload:** Debounced (200ms) für Performance - **Template Matching:** BFS-Limit (max 30 Nodes) --- ## Weitere Ressourcen - **Benutzerhandbuch:** Endnutzer-Workflows - **Administratorhandbuch:** Konfiguration und Wartung - **Architektur-Dokumentation:** System-Übersicht - **Installation & Deployment:** Setup-Anleitung - **Obsidian API Docs:** https://docs.obsidian.md --- **Ende des Entwicklerhandbuchs**