/** * Tests for Chain Inspector confidence and missing_link_constraints finding using real files. */ 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 confidence and missing_link_constraints", () => { it("should emit missing_link_constraints (INFO) for discovery profile with complete slots but incomplete links", 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 discovery profile and a template that requires causal links 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: [ { name: "loop_learning", slots: [ { id: "trigger", allowed_node_types: ["experience"] }, { id: "transformation", allowed_node_types: ["insight"] }, ], links: [ { from: "trigger", to: "transformation", allowed_edge_roles: ["causal"] }, ], }, ], }; 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 match if (!report.templateMatches || report.templateMatches.length === 0) { return; // Skip if no matches } const match = report.templateMatches[0]; expect(match).toBeDefined(); if (!match) return; // If slots are complete but links incomplete, should be plausible if (match.slotsComplete && !match.linksComplete) { expect(match.confidence).toBe("plausible"); // Should emit missing_link_constraints finding with INFO severity const missingLinkFinding = report.findings.find((f) => f.code === "missing_link_constraints"); expect(missingLinkFinding).toBeDefined(); expect(missingLinkFinding?.severity).toBe("info"); } }); it("should emit missing_link_constraints (WARN) for decisioning profile with complete slots but incomplete links", 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; } const chainTemplates: ChainTemplatesConfig = { ...baseTemplates, defaults: { ...baseTemplates.defaults, profiles: { decisioning: { required_links: true, min_slots_filled_for_gap_findings: 3, min_score_for_gap_findings: 20, }, }, }, templates: [ { name: "loop_learning", slots: [ { id: "trigger", allowed_node_types: ["experience"] }, { id: "transformation", allowed_node_types: ["insight"] }, ], links: [ { from: "trigger", to: "transformation", allowed_edge_roles: ["causal"] }, ], }, ], }; 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, "decisioning" ); // Should emit missing_link_constraints finding with WARN severity if (!report.templateMatches || report.templateMatches.length === 0) { return; // Skip if no matches } const match = report.templateMatches[0]; if (!match) return; if (match.slotsComplete && !match.linksComplete) { const missingLinkFinding = report.findings.find((f) => f.code === "missing_link_constraints"); expect(missingLinkFinding).toBeDefined(); expect(missingLinkFinding?.severity).toBe("warn"); } }); it("should compute confirmed confidence for complete chain with causal roles", 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, "decisioning" ); // Should have complete match with confirmed confidence if all slots and links are found if (!report.templateMatches || report.templateMatches.length === 0) { return; // Skip if no matches } const match = report.templateMatches[0]; if (!match) return; if (match.slotsComplete && match.linksComplete) { expect(match.confidence).toBe("confirmed"); // Should NOT emit missing_link_constraints finding const missingLinkFinding = report.findings.find((f) => f.code === "missing_link_constraints"); expect(missingLinkFinding).toBeUndefined(); } }); });