import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { compileMemoryWikiVault } from "./compile.js"; import { renderWikiMarkdown } from "./markdown.js"; import { createMemoryWikiTestHarness } from "./test-helpers.js"; const { createVault } = createMemoryWikiTestHarness(); describe("compileMemoryWikiVault", () => { let suiteRoot = ""; let caseId = 0; beforeAll(async () => { suiteRoot = await fs.mkdtemp(path.join(os.tmpdir(), "memory-wiki-compile-suite-")); }); afterAll(async () => { if (suiteRoot) { await fs.rm(suiteRoot, { recursive: true, force: true }); } }); function nextCaseRoot() { return path.join(suiteRoot, `case-${caseId++}`); } it("writes root and directory indexes for native markdown", async () => { const { rootDir, config } = await createVault({ rootDir: nextCaseRoot(), initialize: true, }); await fs.writeFile( path.join(rootDir, "sources", "alpha.md"), renderWikiMarkdown({ frontmatter: { pageType: "source", id: "source.alpha", title: "Alpha", claims: [ { id: "claim.alpha.doc", text: "Alpha is the canonical source page.", status: "supported", evidence: [{ sourceId: "source.alpha", lines: "1-3" }], }, ], }, body: "# Alpha\n", }), "utf8", ); const result = await compileMemoryWikiVault(config); expect(result.pageCounts.source).toBe(1); expect(result.claimCount).toBe(1); await expect(fs.readFile(path.join(rootDir, "index.md"), "utf8")).resolves.toContain( "[Alpha](sources/alpha.md)", ); await expect(fs.readFile(path.join(rootDir, "index.md"), "utf8")).resolves.toContain( "- Claims: 1", ); await expect(fs.readFile(path.join(rootDir, "sources", "index.md"), "utf8")).resolves.toContain( "[Alpha](sources/alpha.md)", ); const agentDigest = JSON.parse( await fs.readFile(path.join(rootDir, ".openclaw-wiki", "cache", "agent-digest.json"), "utf8"), ) as { claimCount: number; pages: Array<{ path: string; claimCount: number; topClaims: Array<{ text: string }> }>; }; expect(agentDigest.claimCount).toBe(1); expect(agentDigest.pages).toContainEqual( expect.objectContaining({ path: "sources/alpha.md", claimCount: 1, topClaims: [expect.objectContaining({ text: "Alpha is the canonical source page." })], }), ); await expect( fs.readFile(path.join(rootDir, ".openclaw-wiki", "cache", "claims.jsonl"), "utf8"), ).resolves.toContain('"text":"Alpha is the canonical source page."'); }); it("renders obsidian-friendly links when configured", async () => { const { rootDir, config } = await createVault({ rootDir: nextCaseRoot(), initialize: true, config: { vault: { renderMode: "obsidian" }, }, }); await fs.writeFile( path.join(rootDir, "sources", "alpha.md"), renderWikiMarkdown({ frontmatter: { pageType: "source", id: "source.alpha", title: "Alpha" }, body: "# Alpha\n", }), "utf8", ); await compileMemoryWikiVault(config); await expect(fs.readFile(path.join(rootDir, "index.md"), "utf8")).resolves.toContain( "[[sources/alpha|Alpha]]", ); }); it("writes related blocks from source ids and shared sources", async () => { const { rootDir, config } = await createVault({ rootDir: nextCaseRoot(), initialize: true, }); await fs.writeFile( path.join(rootDir, "sources", "alpha.md"), renderWikiMarkdown({ frontmatter: { pageType: "source", id: "source.alpha", title: "Alpha" }, body: "# Alpha\n", }), "utf8", ); await fs.writeFile( path.join(rootDir, "entities", "beta.md"), renderWikiMarkdown({ frontmatter: { pageType: "entity", id: "entity.beta", title: "Beta", sourceIds: ["source.alpha"], }, body: "# Beta\n", }), "utf8", ); await fs.writeFile( path.join(rootDir, "concepts", "gamma.md"), renderWikiMarkdown({ frontmatter: { pageType: "concept", id: "concept.gamma", title: "Gamma", sourceIds: ["source.alpha"], }, body: "# Gamma\n", }), "utf8", ); await compileMemoryWikiVault(config); await expect(fs.readFile(path.join(rootDir, "entities", "beta.md"), "utf8")).resolves.toContain( "## Related", ); await expect(fs.readFile(path.join(rootDir, "entities", "beta.md"), "utf8")).resolves.toContain( "[Alpha](sources/alpha.md)", ); await expect(fs.readFile(path.join(rootDir, "entities", "beta.md"), "utf8")).resolves.toContain( "[Gamma](concepts/gamma.md)", ); await expect(fs.readFile(path.join(rootDir, "sources", "alpha.md"), "utf8")).resolves.toContain( "[Beta](entities/beta.md)", ); await expect(fs.readFile(path.join(rootDir, "sources", "alpha.md"), "utf8")).resolves.toContain( "[Gamma](concepts/gamma.md)", ); }); it("does not relate every page through a broad shared source", async () => { const { rootDir, config } = await createVault({ rootDir: nextCaseRoot(), initialize: true, }); await fs.writeFile( path.join(rootDir, "sources", "alpha.md"), renderWikiMarkdown({ frontmatter: { pageType: "source", id: "source.alpha", title: "Alpha" }, body: "# Alpha\n", }), "utf8", ); for (let index = 0; index < 30; index += 1) { await fs.writeFile( path.join(rootDir, "entities", `entity-${index}.md`), renderWikiMarkdown({ frontmatter: { pageType: "entity", id: `entity.${index}`, title: `Entity ${index}`, sourceIds: ["source.alpha"], }, body: `# Entity ${index}\n`, }), "utf8", ); } await compileMemoryWikiVault(config); const firstEntity = await fs.readFile(path.join(rootDir, "entities", "entity-0.md"), "utf8"); const sourcePage = await fs.readFile(path.join(rootDir, "sources", "alpha.md"), "utf8"); expect(firstEntity).toContain("[Alpha](sources/alpha.md)"); expect(firstEntity).not.toContain("### Related Pages"); expect(sourcePage).not.toContain("### Referenced By"); }); it("writes dashboard report pages when createDashboards is enabled", async () => { const { rootDir, config } = await createVault({ rootDir: nextCaseRoot(), initialize: true, }); await fs.writeFile( path.join(rootDir, "entities", "alpha.md"), renderWikiMarkdown({ frontmatter: { pageType: "entity", id: "entity.alpha", title: "Alpha", sourceIds: ["source.alpha"], questions: ["What changed after launch?"], contradictions: ["Conflicts with source.beta"], confidence: 0.3, claims: [ { id: "claim.alpha.db", text: "Alpha uses PostgreSQL for production writes.", status: "supported", confidence: 0.4, evidence: [], }, ], }, body: "# Alpha\n", }), "utf8", ); await fs.writeFile( path.join(rootDir, "concepts", "alpha-db.md"), renderWikiMarkdown({ frontmatter: { pageType: "concept", id: "concept.alpha.db", title: "Alpha DB", sourceIds: ["source.alpha"], updatedAt: "2025-10-01T00:00:00.000Z", claims: [ { id: "claim.alpha.db", text: "Alpha uses MySQL for production writes.", status: "contested", confidence: 0.62, evidence: [ { sourceId: "source.alpha", lines: "9-11", updatedAt: "2025-10-01T00:00:00.000Z", }, ], }, ], }, body: "# Alpha DB\n", }), "utf8", ); await fs.writeFile( path.join(rootDir, "sources", "alpha.md"), renderWikiMarkdown({ frontmatter: { pageType: "source", id: "source.alpha", title: "Alpha Source", updatedAt: "2026-01-01T00:00:00.000Z", }, body: "# Alpha Source\n", }), "utf8", ); const result = await compileMemoryWikiVault(config); expect(result.pageCounts.report).toBeGreaterThanOrEqual(5); await expect( fs.readFile(path.join(rootDir, "reports", "open-questions.md"), "utf8"), ).resolves.toContain("[Alpha](entities/alpha.md): What changed after launch?"); await expect( fs.readFile(path.join(rootDir, "reports", "contradictions.md"), "utf8"), ).resolves.toContain("Conflicts with source.beta: [Alpha](entities/alpha.md)"); await expect( fs.readFile(path.join(rootDir, "reports", "contradictions.md"), "utf8"), ).resolves.toContain("`claim.alpha.db`"); await expect( fs.readFile(path.join(rootDir, "reports", "low-confidence.md"), "utf8"), ).resolves.toContain("[Alpha](entities/alpha.md): confidence 0.30"); await expect( fs.readFile(path.join(rootDir, "reports", "low-confidence.md"), "utf8"), ).resolves.toContain("Alpha uses PostgreSQL for production writes."); await expect( fs.readFile(path.join(rootDir, "reports", "claim-health.md"), "utf8"), ).resolves.toContain("Missing Evidence"); await expect( fs.readFile(path.join(rootDir, "reports", "claim-health.md"), "utf8"), ).resolves.toContain("Alpha uses PostgreSQL for production writes."); await expect( fs.readFile(path.join(rootDir, "reports", "stale-pages.md"), "utf8"), ).resolves.toContain("[Alpha](entities/alpha.md): missing updatedAt"); const agentDigest = JSON.parse( await fs.readFile(path.join(rootDir, ".openclaw-wiki", "cache", "agent-digest.json"), "utf8"), ) as { claimHealth: { missingEvidence: number; freshness: { unknown: number } }; contradictionClusters: Array<{ key: string }>; }; expect(agentDigest.claimHealth.missingEvidence).toBeGreaterThanOrEqual(1); expect(agentDigest.claimHealth.freshness.unknown).toBeGreaterThanOrEqual(1); expect(agentDigest.contradictionClusters).toContainEqual( expect.objectContaining({ key: "claim.alpha.db" }), ); }); it("skips dashboard report pages when createDashboards is disabled", async () => { const { rootDir, config } = await createVault({ rootDir: nextCaseRoot(), initialize: true, config: { render: { createDashboards: false }, }, }); await fs.writeFile( path.join(rootDir, "entities", "alpha.md"), renderWikiMarkdown({ frontmatter: { pageType: "entity", id: "entity.alpha", title: "Alpha", sourceIds: ["source.alpha"], questions: ["What changed after launch?"], }, body: "# Alpha\n", }), "utf8", ); await compileMemoryWikiVault(config); await expect(fs.access(path.join(rootDir, "reports", "open-questions.md"))).rejects.toThrow(); }); it("writes agent directory, relationship, provenance, and privacy reports", async () => { const { rootDir, config } = await createVault({ rootDir: nextCaseRoot(), initialize: true, }); await fs.writeFile( path.join(rootDir, "entities", "brad.md"), renderWikiMarkdown({ frontmatter: { pageType: "entity", entityType: "person", id: "entity.brad", title: "Brad Groux", canonicalId: "maintainer.brad-groux", aliases: ["brad"], privacyTier: "local-private", bestUsedFor: ["Microsoft routing"], lastRefreshedAt: "2026-04-29T00:00:00.000Z", personCard: { handles: ["@bgroux"], lane: "Microsoft Teams", askFor: ["Teams and Azure questions"], privacyTier: "confirm-before-use", }, relationships: [ { targetId: "entity.alice", targetTitle: "Alice", kind: "collaborates-with", evidenceKind: "discrawl-stat", privacyTier: "local-private", }, ], claims: [ { id: "claim.brad.teams", text: "Brad is useful for Microsoft Teams routing.", status: "supported", confidence: 0.9, evidence: [ { kind: "maintainer-whois", sourceId: "source.maintainers", privacyTier: "local-private", }, ], }, ], }, body: "# Brad Groux\n", }), "utf8", ); await compileMemoryWikiVault(config); await expect( fs.readFile(path.join(rootDir, "reports", "person-agent-directory.md"), "utf8"), ).resolves.toContain("Microsoft Teams"); await expect( fs.readFile(path.join(rootDir, "reports", "relationship-graph.md"), "utf8"), ).resolves.toContain("collaborates-with"); await expect( fs.readFile(path.join(rootDir, "reports", "provenance-coverage.md"), "utf8"), ).resolves.toContain("maintainer-whois: 1"); await expect( fs.readFile(path.join(rootDir, "reports", "privacy-review.md"), "utf8"), ).resolves.toContain("confirm-before-use"); const agentDigest = JSON.parse( await fs.readFile(path.join(rootDir, ".openclaw-wiki", "cache", "agent-digest.json"), "utf8"), ) as { pages: Array<{ path: string; canonicalId?: string; aliases?: string[]; personCard?: { lane?: string }; relationshipCount?: number; }>; }; expect(agentDigest.pages).toContainEqual( expect.objectContaining({ path: "entities/brad.md", canonicalId: "maintainer.brad-groux", aliases: ["brad"], personCard: expect.objectContaining({ lane: "Microsoft Teams" }), relationshipCount: 1, }), ); await expect( fs.readFile(path.join(rootDir, ".openclaw-wiki", "cache", "claims.jsonl"), "utf8"), ).resolves.toContain('"evidenceKinds":["maintainer-whois"]'); }); it("ignores generated related links when computing backlinks on repeated compile", async () => { const { rootDir, config } = await createVault({ rootDir: nextCaseRoot(), initialize: true, }); await fs.writeFile( path.join(rootDir, "entities", "beta.md"), renderWikiMarkdown({ frontmatter: { pageType: "entity", id: "entity.beta", title: "Beta" }, body: "# Beta\n", }), "utf8", ); await fs.writeFile( path.join(rootDir, "concepts", "gamma.md"), renderWikiMarkdown({ frontmatter: { pageType: "concept", id: "concept.gamma", title: "Gamma" }, body: "# Gamma\n\nSee [Beta](entities/beta.md).\n", }), "utf8", ); await compileMemoryWikiVault(config); const second = await compileMemoryWikiVault(config); expect(second.updatedFiles).toEqual([]); await expect(fs.readFile(path.join(rootDir, "entities", "beta.md"), "utf8")).resolves.toContain( "[Gamma](concepts/gamma.md)", ); await expect( fs.readFile(path.join(rootDir, "concepts", "gamma.md"), "utf8"), ).resolves.not.toContain("### Referenced By"); }); });