/** * Command: Inspect Chains (Current Section) */ import type { App, Editor } from "obsidian"; import { resolveSectionContext } from "../analysis/sectionContext"; import { inspectChains } from "../analysis/chainInspector"; import type { ChainRolesConfig, ChainTemplatesConfig, DictionaryLoadResult } from "../dictionary/types"; import type { MindnetSettings } from "../settings"; export interface InspectChainsOptions { includeNoteLinks?: boolean; includeCandidates?: boolean; maxDepth?: number; direction?: "forward" | "backward" | "both"; } /** * Format report as pretty-printed string. */ function formatReport(report: Awaited>): string { const lines: string[] = []; lines.push("=== Chain Inspector Report ==="); lines.push(""); lines.push(`Context: ${report.context.file}`); if (report.context.heading) { lines.push(`Section: ${report.context.heading}`); } lines.push(`Zone: ${report.context.zoneKind}`); lines.push(""); lines.push("Settings:"); lines.push(` - Include Note Links: ${report.settings.includeNoteLinks}`); lines.push(` - Include Candidates: ${report.settings.includeCandidates}`); lines.push(` - Max Depth: ${report.settings.maxDepth}`); lines.push(` - Direction: ${report.settings.direction}`); lines.push(""); lines.push(`Neighbors:`); lines.push(` Incoming: ${report.neighbors.incoming.length}`); for (const edge of report.neighbors.incoming.slice(0, 5)) { const targetStr = edge.target.heading ? `${edge.target.file}#${edge.target.heading}` : edge.target.file; lines.push(` - ${edge.rawEdgeType} -> ${targetStr} [${edge.scope}]`); } if (report.neighbors.incoming.length > 5) { lines.push(` ... and ${report.neighbors.incoming.length - 5} more`); } lines.push(` Outgoing: ${report.neighbors.outgoing.length}`); for (const edge of report.neighbors.outgoing.slice(0, 5)) { const targetStr = edge.target.heading ? `${edge.target.file}#${edge.target.heading}` : edge.target.file; lines.push(` - ${edge.rawEdgeType} -> ${targetStr} [${edge.scope}]`); } if (report.neighbors.outgoing.length > 5) { lines.push(` ... and ${report.neighbors.outgoing.length - 5} more`); } lines.push(""); lines.push(`Paths:`); lines.push(` Forward: ${report.paths.forward.length}`); for (const path of report.paths.forward.slice(0, 3)) { const pathStr = path.nodes .map((n) => (n.heading ? `${n.file}#${n.heading}` : n.file)) .join(" -> "); lines.push(` - ${pathStr} (${path.edges.length} edges)`); } if (report.paths.forward.length > 3) { lines.push(` ... and ${report.paths.forward.length - 3} more`); } lines.push(` Backward: ${report.paths.backward.length}`); for (const path of report.paths.backward.slice(0, 3)) { const pathStr = path.nodes .map((n) => (n.heading ? `${n.file}#${n.heading}` : n.file)) .join(" -> "); lines.push(` - ${pathStr} (${path.edges.length} edges)`); } if (report.paths.backward.length > 3) { lines.push(` ... and ${report.paths.backward.length - 3} more`); } lines.push(""); lines.push(`Gap Heuristics (Findings): ${report.findings.length}`); if (report.findings.length === 0) { lines.push(` ✓ No issues detected`); } else { for (const finding of report.findings) { const severityIcon = finding.severity === "error" ? "❌" : finding.severity === "warn" ? "⚠️" : "ℹ️"; lines.push( ` ${severityIcon} [${finding.severity.toUpperCase()}] ${finding.code}: ${finding.message}` ); } } lines.push(""); // Add analysisMeta section if (report.analysisMeta) { lines.push("Analysis Meta:"); lines.push(` - Edges Total: ${report.analysisMeta.edgesTotal}`); lines.push(` - Edges With Canonical: ${report.analysisMeta.edgesWithCanonical}`); lines.push(` - Edges Unmapped: ${report.analysisMeta.edgesUnmapped}`); if (Object.keys(report.analysisMeta.roleMatches).length > 0) { lines.push(` - Role Matches:`); for (const [roleName, count] of Object.entries(report.analysisMeta.roleMatches)) { lines.push(` - ${roleName}: ${count}`); } } else { lines.push(` - Role Matches: (none)`); } } lines.push(""); // Add template matches section if (report.templateMatches && report.templateMatches.length > 0) { lines.push("Template Matches:"); // Sort by score desc, then name asc (already sorted in matching) const topMatches = report.templateMatches.slice(0, 3); for (const match of topMatches) { const confidenceEmoji = match.confidence === "confirmed" ? "✓" : match.confidence === "plausible" ? "~" : "?"; lines.push(` - ${match.templateName} (score: ${match.score}, confidence: ${confidenceEmoji} ${match.confidence})`); if (Object.keys(match.slotAssignments).length > 0) { lines.push(` Slots:`); const sortedSlots = Object.keys(match.slotAssignments).sort(); for (const slotId of sortedSlots) { const assignment = match.slotAssignments[slotId]; if (assignment) { const nodeStr = assignment.heading ? `${assignment.file}#${assignment.heading}` : assignment.file; lines.push(` ${slotId}: ${nodeStr} [${assignment.noteType}]`); } } } if (match.missingSlots.length > 0) { lines.push(` Missing slots: ${match.missingSlots.join(", ")}`); } if (match.requiredLinks > 0) { const linksStatus = match.linksComplete ? "complete" : "incomplete"; lines.push(` Links: ${match.satisfiedLinks}/${match.requiredLinks} (${linksStatus})`); } else if (match.roleEvidence && match.roleEvidence.length > 0) { lines.push(` Links: ${match.satisfiedLinks}/${match.requiredLinks} satisfied`); } } if (report.templateMatches.length > 3) { lines.push(` ... and ${report.templateMatches.length - 3} more`); } } else if (report.templatesSource) { lines.push("Template Matches: (none)"); } // Add templates source info if (report.templatesSource) { lines.push(""); lines.push("Templates Source:"); lines.push(` - Path: ${report.templatesSource.path}`); lines.push(` - Status: ${report.templatesSource.status}`); if (report.templatesSource.loadedAt) { const date = new Date(report.templatesSource.loadedAt); lines.push(` - Loaded: ${date.toISOString()}`); } lines.push(` - Templates: ${report.templatesSource.templateCount}`); } // Add template matching profile info if (report.templateMatchingProfileUsed) { lines.push(""); lines.push("Template Matching Profile:"); lines.push(` - Profile: ${report.templateMatchingProfileUsed.name}`); lines.push(` - Resolved from: ${report.templateMatchingProfileUsed.resolvedFrom}`); if (report.templateMatchingProfileUsed.profileConfig) { const config = report.templateMatchingProfileUsed.profileConfig; if (config.required_links !== undefined) { lines.push(` - Required links: ${config.required_links}`); } if (config.min_slots_filled_for_gap_findings !== undefined) { lines.push(` - Min slots filled for findings: ${config.min_slots_filled_for_gap_findings}`); } if (config.min_score_for_gap_findings !== undefined) { lines.push(` - Min score for findings: ${config.min_score_for_gap_findings}`); } } } return lines.join("\n"); } /** * Execute inspect chains command. */ export async function executeInspectChains( app: App, editor: Editor, filePath: string, chainRoles: ChainRolesConfig | null, settings: MindnetSettings, options: InspectChainsOptions = {}, chainTemplates?: ChainTemplatesConfig | null, templatesLoadResult?: DictionaryLoadResult ): Promise { // Resolve section context const context = resolveSectionContext(editor, filePath); // Build options with defaults const inspectorOptions = { includeNoteLinks: options.includeNoteLinks ?? true, includeCandidates: options.includeCandidates ?? false, maxDepth: options.maxDepth ?? 3, direction: options.direction ?? "both", }; // Prepare templates source info let templatesSourceInfo: { path: string; status: string; loadedAt: number | null; templateCount: number } | undefined; if (templatesLoadResult) { templatesSourceInfo = { path: templatesLoadResult.resolvedPath, status: templatesLoadResult.status, loadedAt: templatesLoadResult.loadedAt, templateCount: chainTemplates?.templates?.length || 0, }; } // Inspect chains const report = await inspectChains( app, context, inspectorOptions, chainRoles, settings.edgeVocabularyPath, chainTemplates, templatesSourceInfo, settings.templateMatchingProfile ); // Log report as JSON console.log("=== Chain Inspector Report (JSON) ==="); console.log(JSON.stringify(report, null, 2)); // Log pretty-printed summary console.log("\n=== Chain Inspector Report (Summary) ==="); console.log(formatReport(report)); }