- Added new properties to TemplateMatch interface for tracking slots and links completeness, and confidence levels. - Updated resolveCanonicalEdgeType function to handle optional edge vocabulary. - Enhanced computeFindings function to include missing link constraints findings. - Improved inspectChains function to report on links completeness and confidence levels. - Refactored tests to utilize real configuration files and improve integration with the vault structure. - Updated helper functions to support loading from real vault paths, enhancing test reliability.
251 lines
9.1 KiB
TypeScript
251 lines
9.1 KiB
TypeScript
/**
|
||
* 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) {
|
||
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<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,
|
||
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));
|
||
}
|