mindnet_obsidian/docs/03_Entwicklerhandbuch.md
Lars 74cacdd41d
Some checks are pending
Node.js build / build (20.x) (push) Waiting to run
Node.js build / build (22.x) (push) Waiting to run
Update documentation and enhance chain inspection logic
- Revamped the README to provide comprehensive documentation for the Mindnet Causal Assistant plugin, including user, administrator, developer, and architect guides.
- Added specialized documentation sections for installation, deployment, and troubleshooting.
- Enhanced the chain inspection logic to determine effective required links based on template definitions, profiles, and defaults, improving the accuracy of findings related to link completeness.
2026-01-20 11:34:58 +01:00

915 lines
22 KiB
Markdown

# 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 <repository-url> 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 run dev`)
3. **Automatisches Rebuild** bei Dateiänderungen
4. **Deploy lokal** (`npm run deploy:local` oder `npm 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<void> {
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<void> {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings(): Promise<void> {
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<void> {
// 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<LintFinding[]> => {
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:**
```
<vault>/.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**