mindnet_obsidian/src/commands/inspectChainsCommand.ts
Lars 3bb59afdda
Some checks are pending
Node.js build / build (20.x) (push) Waiting to run
Node.js build / build (22.x) (push) Waiting to run
Enhance template matching and chain inspection features
- 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.
2026-01-18 22:10:44 +01:00

251 lines
9.1 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) {
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));
}