mindnet_obsidian/src/commands/inspectChainsCommand.ts

226 lines
7.7 KiB
TypeScript
Raw Normal View History

/**
* 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));
}