138 lines
4.5 KiB
TypeScript
138 lines
4.5 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 } from "../dictionary/types";
|
|||
|
|
|
|||
|
|
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}`
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return lines.join("\n");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Execute inspect chains command.
|
|||
|
|
*/
|
|||
|
|
export async function executeInspectChains(
|
|||
|
|
app: App,
|
|||
|
|
editor: Editor,
|
|||
|
|
filePath: string,
|
|||
|
|
chainRoles: ChainRolesConfig | null,
|
|||
|
|
options: InspectChainsOptions = {}
|
|||
|
|
): 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",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Inspect chains
|
|||
|
|
const report = await inspectChains(app, context, inspectorOptions, chainRoles);
|
|||
|
|
|
|||
|
|
// 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));
|
|||
|
|
}
|