- 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.
915 lines
22 KiB
Markdown
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**
|