import { Notice, Plugin, TFile } from "obsidian"; import { DEFAULT_SETTINGS, type MindnetSettings, normalizeVaultPath } from "./settings"; import { VocabularyLoader } from "./vocab/VocabularyLoader"; import { parseEdgeVocabulary } from "./vocab/parseEdgeVocabulary"; import { Vocabulary } from "./vocab/Vocabulary"; import { LintEngine } from "./lint/LintEngine"; import { MindnetSettingTab } from "./ui/MindnetSettingTab"; export default class MindnetCausalAssistantPlugin extends Plugin { settings: MindnetSettings; private vocabulary: Vocabulary | null = null; private reloadDebounceTimer: number | null = null; async onload(): Promise { await this.loadSettings(); // Add settings tab this.addSettingTab(new MindnetSettingTab(this.app, this)); // Register live reload for edge vocabulary file this.registerEvent( this.app.vault.on("modify", async (file: TFile) => { const normalizedFilePath = normalizeVaultPath(file.path); const normalizedVocabPath = normalizeVaultPath(this.settings.edgeVocabularyPath); // Check if modified file matches vocabulary path (exact match or ends with) if (normalizedFilePath === normalizedVocabPath || normalizedFilePath === `/${normalizedVocabPath}` || normalizedFilePath.endsWith(`/${normalizedVocabPath}`)) { // Debounce reload to avoid multiple rapid reloads if (this.reloadDebounceTimer !== null) { window.clearTimeout(this.reloadDebounceTimer); } this.reloadDebounceTimer = window.setTimeout(async () => { await this.reloadVocabulary(); this.reloadDebounceTimer = null; }, 200); } }) ); this.addCommand({ id: "mindnet-reload-edge-vocabulary", name: "Mindnet: Reload edge vocabulary", callback: async () => { await this.reloadVocabulary(); }, }); this.addCommand({ id: "mindnet-validate-current-note", name: "Mindnet: Validate current note", callback: async () => { try { const vocabulary = await this.ensureVocabularyLoaded(); if (!vocabulary) { return; } const findings = await LintEngine.lintCurrentNote(this.app, vocabulary); // Count findings by severity const errorCount = findings.filter(f => f.severity === "ERROR").length; const warnCount = findings.filter(f => f.severity === "WARN").length; const infoCount = findings.filter(f => f.severity === "INFO").length; // Show summary notice new Notice(`Lint: ${errorCount} errors, ${warnCount} warnings, ${infoCount} info`); // Log findings to console console.log("=== Lint Findings ==="); for (const finding of findings) { const quickfixInfo = finding.quickFixes && finding.quickFixes.length > 0 ? ` [QuickFix: ${finding.quickFixes.map(qf => qf.title).join(", ")}]` : ""; console.log(`[${finding.severity}] ${finding.ruleId}: ${finding.message} (${finding.filePath}:${finding.lineStart}${quickfixInfo})`); } } catch (e) { const msg = e instanceof Error ? e.message : String(e); new Notice(`Failed to validate note: ${msg}`); console.error(e); } }, }); } onunload(): void { // nothing yet } private async loadSettings(): Promise { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } async saveSettings(): Promise { await this.saveData(this.settings); } /** * Ensure vocabulary is loaded. Auto-loads if not present. * Returns Vocabulary instance or null on failure. */ private async ensureVocabularyLoaded(): Promise { if (this.vocabulary) { return this.vocabulary; } try { const text = await VocabularyLoader.loadText( this.app, this.settings.edgeVocabularyPath ); const parsed = parseEdgeVocabulary(text); this.vocabulary = new Vocabulary(parsed); const stats = this.vocabulary.getStats(); console.log("Vocabulary auto-loaded", stats); return this.vocabulary; } catch (e) { const msg = e instanceof Error ? e.message : String(e); if (msg.includes("not found") || msg.includes("Vocabulary file not found")) { new Notice("edge_vocabulary.md not found. Check the path in plugin settings."); } else { new Notice(`Failed to load vocabulary: ${msg}. Check plugin settings.`); } console.error("Failed to load vocabulary:", e); return null; } } /** * Reload vocabulary from file. Used by manual command and live reload. */ private async reloadVocabulary(): Promise { try { const text = await VocabularyLoader.loadText( this.app, this.settings.edgeVocabularyPath ); const parsed = parseEdgeVocabulary(text); this.vocabulary = new Vocabulary(parsed); const stats = this.vocabulary.getStats(); console.log("Vocabulary loaded", stats); new Notice(`Edge vocabulary reloaded: ${stats.canonicalCount} canonical, ${stats.aliasCount} aliases`); // Log normalization examples if (stats.canonicalCount > 0) { const firstCanonical = Array.from(parsed.byCanonical.keys())[0]; if (firstCanonical) { const canonicalNorm = this.vocabulary.normalize(firstCanonical); console.log(`Normalization example (canonical): "${firstCanonical}" -> canonical: ${canonicalNorm.canonical}, inverse: ${canonicalNorm.inverse}`); } if (stats.aliasCount > 0) { const firstAlias = Array.from(parsed.aliasToCanonical.keys())[0]; if (firstAlias) { const aliasNorm = this.vocabulary.normalize(firstAlias); console.log(`Normalization example (alias): "${firstAlias}" -> canonical: ${aliasNorm.canonical}, inverse: ${aliasNorm.inverse}`); } } } } catch (e) { const msg = e instanceof Error ? e.message : String(e); if (msg.includes("not found") || msg.includes("Vocabulary file not found")) { new Notice("edge_vocabulary.md not found. Configure path in plugin settings."); } else { new Notice(`Failed to reload vocabulary: ${msg}`); } console.error(e); } } }