Files
openclaw/extensions/memory-wiki/src/lint.test.ts
2026-05-08 21:35:49 +01:00

162 lines
5.7 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { lintMemoryWikiVault } from "./lint.js";
import { renderWikiMarkdown } from "./markdown.js";
import { createMemoryWikiTestHarness } from "./test-helpers.js";
const { createVault } = createMemoryWikiTestHarness();
describe("lintMemoryWikiVault", () => {
it("accepts native markdown links that include the relative .md target", async () => {
const { rootDir, config } = await createVault({
prefix: "memory-wiki-lint-native-links-",
config: {
vault: { renderMode: "native" },
},
});
await Promise.all(
["entities", "sources"].map((dir) => fs.mkdir(path.join(rootDir, dir), { recursive: true })),
);
await fs.writeFile(
path.join(rootDir, "sources", "alpha.md"),
renderWikiMarkdown({
frontmatter: {
pageType: "source",
id: "source.alpha",
title: "Alpha Source",
},
body: "# Alpha Source\n",
}),
"utf8",
);
await fs.writeFile(
path.join(rootDir, "entities", "alpha.md"),
renderWikiMarkdown({
frontmatter: {
pageType: "entity",
id: "entity.alpha",
title: "Alpha",
sourceIds: ["source.alpha"],
},
body: "# Alpha\n\n[Alpha Source](sources/alpha.md)\n",
}),
"utf8",
);
const result = await lintMemoryWikiVault(config);
expect(result.issues.some((issue) => issue.code === "broken-wikilink")).toBe(false);
});
it("detects duplicate ids, provenance gaps, contradictions, and open questions", async () => {
const { rootDir, config } = await createVault({
prefix: "memory-wiki-lint-",
config: {
vault: { renderMode: "obsidian" },
},
});
await Promise.all(
["entities", "concepts", "sources", "syntheses"].map((dir) =>
fs.mkdir(path.join(rootDir, dir), { recursive: true }),
),
);
const duplicate = renderWikiMarkdown({
frontmatter: {
pageType: "entity",
id: "entity.alpha",
title: "Alpha",
contradictions: ["Conflicts with source.beta"],
questions: ["Is Alpha still active?"],
confidence: 0.2,
claims: [
{
id: "claim.alpha.db",
text: "Alpha uses PostgreSQL for production writes.",
confidence: 0.2,
evidence: [],
},
],
},
body: "# Alpha\n\n[[missing-page]]\n",
});
await fs.writeFile(path.join(rootDir, "entities", "alpha.md"), duplicate, "utf8");
await fs.writeFile(path.join(rootDir, "concepts", "alpha.md"), duplicate, "utf8");
await fs.writeFile(
path.join(rootDir, "sources", "bridge-alpha.md"),
renderWikiMarkdown({
frontmatter: {
pageType: "source",
id: "source.bridge.alpha",
title: "Bridge Alpha",
sourceType: "memory-bridge",
},
body: "# Bridge Alpha\n",
}),
"utf8",
);
await fs.writeFile(
path.join(rootDir, "syntheses", "alpha-db.md"),
renderWikiMarkdown({
frontmatter: {
pageType: "synthesis",
id: "synthesis.alpha.db",
title: "Alpha Database",
sourceIds: ["source.bridge.alpha"],
updatedAt: "2025-10-01T00:00:00.000Z",
claims: [
{
id: "claim.alpha.db",
text: "Alpha uses MySQL for production writes.",
status: "contested",
confidence: 0.7,
evidence: [
{
sourceId: "source.bridge.alpha",
lines: "1-3",
updatedAt: "2025-10-01T00:00:00.000Z",
},
],
},
],
},
body: "# Alpha Database\n",
}),
"utf8",
);
const result = await lintMemoryWikiVault(config);
expect(result.issueCount).toBeGreaterThan(0);
expect(result.issues.map((issue) => issue.code)).toContain("duplicate-id");
expect(result.issues.map((issue) => issue.code)).toContain("missing-source-ids");
expect(result.issues.map((issue) => issue.code)).toContain("missing-import-provenance");
expect(result.issues.map((issue) => issue.code)).toContain("broken-wikilink");
expect(result.issues.map((issue) => issue.code)).toContain("contradiction-present");
expect(result.issues.map((issue) => issue.code)).toContain("claim-conflict");
expect(result.issues.map((issue) => issue.code)).toContain("open-question");
expect(result.issues.map((issue) => issue.code)).toContain("low-confidence");
expect(result.issues.map((issue) => issue.code)).toContain("claim-missing-evidence");
expect(result.issues.map((issue) => issue.code)).toContain("claim-low-confidence");
expect(result.issues.map((issue) => issue.code)).toContain("stale-page");
expect(result.issues.map((issue) => issue.code)).toContain("stale-claim");
expect(
result.issuesByCategory.contradictions.some((issue) => issue.code === "claim-conflict"),
).toBe(true);
expect(result.issuesByCategory["open-questions"].length).toBeGreaterThanOrEqual(2);
expect(
result.issuesByCategory.provenance.some(
(issue) => issue.code === "missing-import-provenance",
),
).toBe(true);
expect(
result.issuesByCategory.provenance.some((issue) => issue.code === "claim-missing-evidence"),
).toBe(true);
await expect(fs.readFile(result.reportPath, "utf8")).resolves.toContain("### Errors");
await expect(fs.readFile(result.reportPath, "utf8")).resolves.toContain("### Contradictions");
await expect(fs.readFile(result.reportPath, "utf8")).resolves.toContain("### Open Questions");
});
});