- Introduced a wide two-column layout for the Chain Workbench modal, improving user experience and accessibility. - Added new styles for workbench components, including headers, filters, and main containers, to enhance visual organization. - Updated chain templates to allow for multiple distinct matches per template, improving flexibility in template matching. - Enhanced documentation to clarify the new settings and commands related to the Chain Workbench and edge detection features. - Implemented logging for better tracking of missing configurations, ensuring users are informed about any loading issues.
200 lines
6.4 KiB
TypeScript
200 lines
6.4 KiB
TypeScript
/**
|
|
* Tests for Chain Inspector top-N template matches (v0.4.4).
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import { inspectChains } from "../../analysis/chainInspector";
|
|
import { createVaultAppFromFixtures } from "../helpers/vaultHelper";
|
|
import { loadChainRolesFromFixtures, loadChainTemplatesFromFixtures } from "../helpers/configHelper";
|
|
import type { SectionContext } from "../../analysis/sectionContext";
|
|
import type { ChainTemplatesConfig } from "../../dictionary/types";
|
|
|
|
describe("Chain Inspector top-N template matches", () => {
|
|
it("should return top 3 matches sorted by confidence, score, templateName", async () => {
|
|
const app = createVaultAppFromFixtures();
|
|
|
|
const chainRoles = loadChainRolesFromFixtures();
|
|
const baseTemplates = loadChainTemplatesFromFixtures();
|
|
|
|
if (!chainRoles || !baseTemplates) {
|
|
console.warn("Skipping test: real config files not found in fixtures");
|
|
return;
|
|
}
|
|
|
|
// Create templates config with multiple templates that should match
|
|
const chainTemplates: ChainTemplatesConfig = {
|
|
...baseTemplates,
|
|
defaults: {
|
|
...baseTemplates.defaults,
|
|
profiles: {
|
|
discovery: {
|
|
required_links: false,
|
|
min_slots_filled_for_gap_findings: 1,
|
|
min_score_for_gap_findings: 0,
|
|
},
|
|
},
|
|
},
|
|
templates: [
|
|
...baseTemplates.templates,
|
|
{
|
|
name: "simple_chain",
|
|
slots: [
|
|
{ id: "start", allowed_node_types: ["experience"] },
|
|
{ id: "end", allowed_node_types: ["decision"] },
|
|
],
|
|
links: [
|
|
{ from: "start", to: "end", allowed_edge_roles: ["causal"] },
|
|
],
|
|
},
|
|
{
|
|
name: "trigger_transformation",
|
|
slots: [
|
|
{ id: "trigger", allowed_node_types: ["experience"] },
|
|
{ id: "transformation", allowed_node_types: ["insight"] },
|
|
],
|
|
links: [
|
|
{ from: "trigger", to: "transformation", allowed_edge_roles: ["causal", "influences"] },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const context: SectionContext = {
|
|
file: "Tests/01_experience_trigger.md",
|
|
heading: "Kontext",
|
|
zoneKind: "content",
|
|
sectionIndex: 0,
|
|
};
|
|
|
|
const report = await inspectChains(
|
|
app,
|
|
context,
|
|
{
|
|
includeNoteLinks: true,
|
|
includeCandidates: false,
|
|
maxDepth: 3,
|
|
direction: "both",
|
|
},
|
|
chainRoles,
|
|
"_system/dictionary/edge_vocabulary.md",
|
|
chainTemplates,
|
|
undefined,
|
|
"discovery"
|
|
);
|
|
|
|
// Should have template matches (may be empty if no templates match)
|
|
if (!report.templateMatches || report.templateMatches.length === 0) {
|
|
// If no matches, topNUsed should still be set if templates were processed
|
|
if (report.templatesSource) {
|
|
expect(report.analysisMeta?.topNUsed).toBe(3);
|
|
}
|
|
return; // Skip ordering tests if no matches
|
|
}
|
|
|
|
// Should have at most 3 matches per template (topN per chain type)
|
|
const byTemplate = new Map<string, number>();
|
|
for (const m of report.templateMatches) {
|
|
byTemplate.set(m.templateName, (byTemplate.get(m.templateName) ?? 0) + 1);
|
|
}
|
|
for (const count of byTemplate.values()) {
|
|
expect(count).toBeLessThanOrEqual(3);
|
|
}
|
|
|
|
// Should have topNUsed in analysisMeta
|
|
expect(report.analysisMeta?.topNUsed).toBe(3);
|
|
|
|
// Verify deterministic ordering: confidence rank (confirmed > plausible > weak), then score desc, then templateName asc
|
|
const confidenceRank = (c: "confirmed" | "plausible" | "weak"): number => {
|
|
if (c === "confirmed") return 3;
|
|
if (c === "plausible") return 2;
|
|
return 1; // weak
|
|
};
|
|
|
|
for (let i = 0; i < report.templateMatches.length - 1; i++) {
|
|
const current = report.templateMatches[i];
|
|
const next = report.templateMatches[i + 1];
|
|
|
|
if (!current || !next) continue;
|
|
|
|
const currentRank = confidenceRank(current.confidence);
|
|
const nextRank = confidenceRank(next.confidence);
|
|
|
|
// If confidence ranks differ, current should be higher
|
|
if (currentRank !== nextRank) {
|
|
expect(currentRank).toBeGreaterThan(nextRank);
|
|
continue;
|
|
}
|
|
|
|
// If scores differ, current should be higher
|
|
if (current.score !== next.score) {
|
|
expect(current.score).toBeGreaterThanOrEqual(next.score);
|
|
continue;
|
|
}
|
|
|
|
// If templateNames differ, current should be lexicographically smaller
|
|
expect(current.templateName.localeCompare(next.templateName)).toBeLessThanOrEqual(0);
|
|
}
|
|
|
|
// If we have multiple templates, both should appear
|
|
const templateNames = report.templateMatches.map(m => m.templateName);
|
|
if (templateNames.length >= 2) {
|
|
// At least one template should be present
|
|
expect(templateNames.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("should show Why evidence in pretty print format", async () => {
|
|
const app = createVaultAppFromFixtures();
|
|
|
|
const chainRoles = loadChainRolesFromFixtures();
|
|
const chainTemplates = loadChainTemplatesFromFixtures();
|
|
|
|
if (!chainRoles || !chainTemplates) {
|
|
console.warn("Skipping test: real config files not found in fixtures");
|
|
return;
|
|
}
|
|
|
|
const context: SectionContext = {
|
|
file: "Tests/01_experience_trigger.md",
|
|
heading: "Kontext",
|
|
zoneKind: "content",
|
|
sectionIndex: 0,
|
|
};
|
|
|
|
const report = await inspectChains(
|
|
app,
|
|
context,
|
|
{
|
|
includeNoteLinks: true,
|
|
includeCandidates: false,
|
|
maxDepth: 3,
|
|
direction: "both",
|
|
},
|
|
chainRoles,
|
|
"_system/dictionary/edge_vocabulary.md",
|
|
chainTemplates,
|
|
undefined,
|
|
"discovery"
|
|
);
|
|
|
|
// Check that matches have roleEvidence if they have links
|
|
if (report.templateMatches && report.templateMatches.length > 0) {
|
|
for (const match of report.templateMatches) {
|
|
if (match.requiredLinks > 0 && match.satisfiedLinks > 0) {
|
|
// Should have roleEvidence
|
|
expect(match.roleEvidence).toBeDefined();
|
|
if (match.roleEvidence && match.roleEvidence.length > 0) {
|
|
// Each evidence should have from, to, edgeRole, rawEdgeType
|
|
for (const ev of match.roleEvidence) {
|
|
expect(ev.from).toBeDefined();
|
|
expect(ev.to).toBeDefined();
|
|
expect(ev.edgeRole).toBeDefined();
|
|
expect(ev.rawEdgeType).toBeDefined();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|