mindnet_obsidian/src/commands/inspectChainsCommand.ts
Lars 90ccec5f7d
Some checks are pending
Node.js build / build (20.x) (push) Waiting to run
Node.js build / build (22.x) (push) Waiting to run
Implement findings fixing and template matching features in Mindnet plugin
- Added a new command to fix findings in the current section of a markdown file, enhancing user experience by automating issue resolution.
- Introduced settings for configuring actions related to missing notes and headings, allowing for customizable behavior during the fixing process.
- Enhanced the chain inspector to support template matching, providing users with insights into template utilization and potential gaps in their content.
- Updated the analysis report to include detailed metadata about edges and role matches, improving the clarity and usefulness of inspection results.
- Improved error handling and user notifications for fixing findings and template matching processes, ensuring better feedback during execution.
2026-01-18 21:10:33 +01:00

226 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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<ReturnType<typeof inspectChains>>): 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) {
lines.push(` - ${match.templateName} (score: ${match.score})`);
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.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}`);
}
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<ChainTemplatesConfig>
): Promise<void> {
// 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
);
// 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));
}