@zosmaai/pi-llm-wiki 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,654 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { dirname, join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
- import { captureFile, captureText, captureUrl } from "../extensions/llm-wiki/lib/source-packet.js";
7
- import { ensureVaultStructure, getVaultPaths } from "../extensions/llm-wiki/lib/utils.js";
8
-
9
- // ─── Helpers ────────────────────────────────────────────
10
-
11
- let tempDir: string;
12
- const __fname = typeof __filename !== "undefined" ? __filename : "";
13
- const __dname =
14
- typeof __dirname !== "undefined"
15
- ? __dirname
16
- : typeof import.meta !== "undefined" && import.meta.dirname
17
- ? import.meta.dirname
18
- : dirname(fileURLToPath(__fname || `file://${process.cwd()}/test/dummy.ts`));
19
-
20
- const rootDir = resolve(__dname, "..");
21
-
22
- function readFile(path: string): string {
23
- return readFileSync(path, { encoding: "utf-8" });
24
- }
25
-
26
- function createWikiRoot(): string {
27
- const dir = join(tempDir, `wiki-${Math.random().toString(36).slice(2)}`);
28
- mkdirSync(dir, { recursive: true });
29
-
30
- const dirs = [
31
- "raw/articles",
32
- "raw/papers",
33
- "raw/notes",
34
- "raw/assets",
35
- "wiki/entities",
36
- "wiki/concepts",
37
- "wiki/sources",
38
- "wiki/syntheses",
39
- "wiki/changes",
40
- "outputs",
41
- ".discoveries",
42
- ];
43
- for (const d of dirs) mkdirSync(join(dir, d), { recursive: true });
44
-
45
- return dir;
46
- }
47
-
48
- function createConfig(dir: string, overrides: Record<string, unknown> = {}) {
49
- const defaults: Record<string, unknown> = {
50
- wiki: { mode: "personal", topic: "Test Topic" },
51
- change_detection: false,
52
- };
53
- const config = { ...defaults, ...overrides } as Record<string, Record<string, unknown>>;
54
- const mode = config.wiki?.mode || "personal";
55
- const topic = config.wiki?.topic || "Test Topic";
56
- writeFileSync(
57
- join(dir, "config.yaml"),
58
- `# LLM Wiki Configuration\nwiki:\n mode: ${mode}\n topic: "${topic}"\n`,
59
- );
60
- }
61
-
62
- function createSourceFile(dir: string, name: string, content: string) {
63
- writeFileSync(join(dir, "raw", "articles", name), content);
64
- }
65
-
66
- function createWikiPage(dir: string, subdir: string | "", name: string, content: string) {
67
- const target = subdir ? join(dir, "wiki", subdir, name) : join(dir, "wiki", name);
68
- writeFileSync(target, content);
69
- }
70
-
71
- // ─── Package Structure Tests ────────────────────────────
72
-
73
- describe("package structure", () => {
74
- it("should have a valid package.json with pi manifest", () => {
75
- const pkg = JSON.parse(readFile(join(rootDir, "package.json")));
76
- expect(pkg.name).toBe("@zosmaai/pi-llm-wiki");
77
- expect(pkg.keywords).toContain("pi-package");
78
- expect(pkg.pi.extensions).toContain("./extensions");
79
- expect(pkg.pi.skills).toContain("./skills");
80
- expect(pkg.pi.prompts).toContain("./prompts");
81
- expect(pkg.peerDependencies).toBeDefined();
82
- expect(pkg.peerDependencies["@mariozechner/pi-coding-agent"]).toBe("*");
83
- expect(pkg.peerDependencies.typebox).toBe("*");
84
- });
85
-
86
- it("should have a SKILL.md with valid frontmatter and schema content", () => {
87
- const skillPath = join(rootDir, "skills", "llm-wiki", "SKILL.md");
88
- expect(existsSync(skillPath)).toBe(true);
89
- const content = readFile(skillPath);
90
- expect(content).toContain("name: llm-wiki");
91
- expect(content).toContain("## Golden Rules");
92
- expect(content).toContain("RAW IS IMMUTABLE");
93
- expect(content).toContain("## Workflows");
94
- expect(content).toContain("wiki_ingest");
95
- expect(content).toContain("Obsidian Integration");
96
- expect(content).toContain("Personal Wiki");
97
- expect(content).toContain("Company Wiki");
98
- });
99
-
100
- it("should have all 8 prompt templates with frontmatter", () => {
101
- const prompts = [
102
- "wiki-init.md",
103
- "wiki-ingest.md",
104
- "wiki-query.md",
105
- "wiki-lint.md",
106
- "wiki-discover.md",
107
- "wiki-run.md",
108
- "wiki-status.md",
109
- "wiki-digest.md",
110
- ];
111
- for (const prompt of prompts) {
112
- const path = join(rootDir, "prompts", prompt);
113
- expect(existsSync(path)).toBe(true);
114
- const content = readFile(path);
115
- expect(content).toContain("description:");
116
- expect(content).toContain("argument-hint:");
117
- expect(content).toContain("section: LLM Wiki");
118
- expect(content).toContain("topLevelCli: true");
119
- expect(content).not.toContain("\nargs:");
120
- }
121
- });
122
-
123
- it("should include prompt arguments in templates that accept them", () => {
124
- const promptsWithArgs = [
125
- "wiki-init.md",
126
- "wiki-ingest.md",
127
- "wiki-query.md",
128
- "wiki-lint.md",
129
- "wiki-discover.md",
130
- "wiki-run.md",
131
- "wiki-digest.md",
132
- ];
133
- for (const prompt of promptsWithArgs) {
134
- const content = readFile(join(rootDir, "prompts", prompt));
135
- expect(content).toContain("$ARGUMENTS");
136
- }
137
-
138
- const query = readFile(join(rootDir, "prompts", "wiki-query.md"));
139
- expect(query).toContain("## User Question");
140
- expect(query).toContain("$ARGUMENTS");
141
- });
142
-
143
- it("should have all wiki template files", () => {
144
- const t = join(rootDir, "skills", "llm-wiki", "templates");
145
- expect(existsSync(join(t, "INDEX.md"))).toBe(true);
146
- expect(existsSync(join(t, "LOG.md"))).toBe(true);
147
- expect(existsSync(join(t, "DASHBOARD.md"))).toBe(true);
148
- expect(existsSync(join(t, "config.yaml"))).toBe(true);
149
- expect(existsSync(join(t, "pages", "entity.md"))).toBe(true);
150
- expect(existsSync(join(t, "pages", "concept.md"))).toBe(true);
151
- expect(existsSync(join(t, "pages", "source.md"))).toBe(true);
152
- expect(existsSync(join(t, "pages", "synthesis.md"))).toBe(true);
153
- });
154
-
155
- it("should have the extension entry point", () => {
156
- const extPath = join(rootDir, "extensions", "llm-wiki", "index.ts");
157
- expect(existsSync(extPath)).toBe(true);
158
- const content = readFile(extPath);
159
- expect(content).toContain("ExtensionAPI");
160
- expect(content).toContain("registerWikiBootstrap");
161
- });
162
-
163
- it("should have all custom tools in the extension", () => {
164
- const toolsPath = join(rootDir, "extensions", "llm-wiki", "lib", "tools.ts");
165
- expect(existsSync(toolsPath)).toBe(true);
166
- const content = readFile(toolsPath);
167
- const tools = [
168
- "wiki_bootstrap",
169
- "wiki_capture_source",
170
- "wiki_ingest",
171
- "wiki_ensure_page",
172
- "wiki_search",
173
- "wiki_lint",
174
- "wiki_status",
175
- "wiki_rebuild_meta",
176
- "wiki_log_event",
177
- "wiki_watch",
178
- ];
179
- for (const tool of tools) {
180
- expect(content).toContain(tool);
181
- }
182
- });
183
-
184
- it("should keep MarkItDown timeout configurable and avoid PDF byte fallbacks", () => {
185
- const sourcePacketPath = join(rootDir, "extensions", "llm-wiki", "lib", "source-packet.ts");
186
- expect(existsSync(sourcePacketPath)).toBe(true);
187
- const content = readFile(sourcePacketPath);
188
- expect(content).toContain("WIKI_MARKITDOWN_TIMEOUT_MS");
189
- expect(content).toContain("DEFAULT_MARKITDOWN_TIMEOUT_MS = 180_000");
190
- expect(content).toContain("isPdfUrl(url)");
191
- expect(content).toContain("looksLikePdf(curlResult.stdout)");
192
- expect(content).toContain("pdfExtractionFailureMessage");
193
- });
194
-
195
- it("should have a comprehensive README with install instructions", () => {
196
- const readme = readFile(join(rootDir, "README.md"));
197
- expect(readme).toContain("@zosmaai/pi-llm-wiki");
198
- expect(readme).toContain("pi install npm:@zosmaai/pi-llm-wiki");
199
- expect(readme).toContain("Karpathy");
200
- expect(readme).toContain("Obsidian");
201
- });
202
- });
203
-
204
- // ─── SKILL.md Frontmatter Validation ────────────────────
205
-
206
- describe("skill frontmatter validation", () => {
207
- const skillPath = join(rootDir, "skills", "llm-wiki", "SKILL.md");
208
-
209
- it("should have name matching directory, lowercase with hyphens only", () => {
210
- const content = readFile(skillPath);
211
- const match = content.match(/^---\n([\s\S]*?)\n---/) as RegExpMatchArray | null;
212
- expect(match).not.toBeNull();
213
- const frontmatter = match![1];
214
- expect(frontmatter).toContain("name: llm-wiki");
215
-
216
- const nameMatch = frontmatter.match(/name:\s*(\S+)/);
217
- expect(nameMatch).not.toBeNull();
218
- const name = nameMatch![1];
219
- expect(name).toMatch(/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/);
220
- expect(name.length).toBeLessThanOrEqual(64);
221
- expect(name).not.toContain("--");
222
- expect(name).not.toMatch(/^-|-$/);
223
- });
224
-
225
- it("should have a description under 1024 characters", () => {
226
- const content = readFile(skillPath);
227
- const match = content.match(/^---\n([\s\S]*?)\n---/) as RegExpMatchArray | null;
228
- expect(match).not.toBeNull();
229
- const descMatch = match![1].match(/description:\s*(.+)/);
230
- expect(descMatch).not.toBeNull();
231
- expect(descMatch![1].length).toBeLessThanOrEqual(1024);
232
- });
233
- });
234
-
235
- // ─── Wiki Directory Structure Tests ─────────────────────
236
-
237
- describe("source packet capture", () => {
238
- const html = "<html><head><title>Example Page</title></head><body>Hello</body></html>";
239
- const pdfBytes = "%PDF-1.7\n1 0 obj\n<</Type/Catalog>>\nendobj\n";
240
-
241
- beforeEach(() => {
242
- tempDir = join(tmpdir(), `pi-llm-wiki-capture-${Date.now()}`);
243
- mkdirSync(tempDir, { recursive: true });
244
- });
245
-
246
- afterEach(() => {
247
- rmSync(tempDir, { recursive: true, force: true });
248
- });
249
-
250
- /** Build a mock pi.exec that optionally handles curl -o file writes. */
251
- function mockPi(stdout?: string, writeOriginal = true) {
252
- return {
253
- exec: async (command: string, args: string[]) => {
254
- if (command === "sh") return { stdout: "no\n", stderr: "", code: 0 };
255
- if (command === "curl" && args.includes("-o")) {
256
- if (writeOriginal) {
257
- const outputPath = args[args.indexOf("-o") + 1];
258
- writeFileSync(outputPath, stdout ?? html, "utf-8");
259
- }
260
- return { stdout: "", stderr: "", code: 0 };
261
- }
262
- if (command === "curl") return { stdout: stdout ?? html, stderr: "", code: 0 };
263
- throw new Error(`Unexpected command: ${command}`);
264
- },
265
- };
266
- }
267
-
268
- function makePaths() {
269
- const p = getVaultPaths(join(tempDir, `wiki-${Math.random().toString(36).slice(2)}`));
270
- ensureVaultStructure(p);
271
- return p;
272
- }
273
-
274
- it("should preserve the original artifact for URL captures and render clickable links", async () => {
275
- const paths = makePaths();
276
- const pi = mockPi();
277
-
278
- const url = "https://example.com/article";
279
- const result = await captureUrl(pi as never, paths, url);
280
-
281
- // Original artifact preserved
282
- expect(existsSync(join(result.packetPath, "original", "source.html"))).toBe(true);
283
- expect(readFile(join(result.packetPath, "original", "source.html"))).toBe(html);
284
- expect(readFile(join(result.packetPath, "extracted.md"))).toBe(html);
285
-
286
- // Clickable URL in source page
287
- const sourcePage = readFile(result.sourcePagePath);
288
- expect(sourcePage).toContain(`> _Original: [${url}](${url})_`);
289
- });
290
-
291
- it("should write PDF extraction failure message for .pdf URLs when MarkItDown is unavailable", async () => {
292
- const paths = makePaths();
293
- const pi = mockPi();
294
-
295
- const url = "https://example.com/report.pdf";
296
- const result = await captureUrl(pi as never, paths, url);
297
-
298
- // Should NOT have written raw original for .pdf (no uvx)
299
- expect(existsSync(join(result.packetPath, "original", "source.pdf"))).toBe(true);
300
-
301
- // extracted.md should contain a failure message, not raw bytes
302
- const extracted = readFile(join(result.packetPath, "extracted.md"));
303
- expect(extracted).not.toContain("%PDF-");
304
- expect(extracted).toContain("PDF content could not be converted");
305
- expect(extracted).toContain(url);
306
- });
307
-
308
- it("should sniff %PDF- bytes from non-.pdf URLs and write a failure message instead", async () => {
309
- const paths = makePaths();
310
- // curl returns PDF bytes from a non-.pdf URL
311
- const pi = mockPi(pdfBytes);
312
-
313
- const url = "https://example.com/download?format=pdf";
314
- const result = await captureUrl(pi as never, paths, url);
315
-
316
- const extracted = readFile(join(result.packetPath, "extracted.md"));
317
- expect(extracted).not.toContain("%PDF-");
318
- expect(extracted).toContain("PDF content could not be converted");
319
- expect(extracted).toContain(url);
320
- });
321
-
322
- it("should name original artifacts based on URL extension", async () => {
323
- const cases: Array<{ url: string; expected: string }> = [
324
- { url: "https://example.com/article.html", expected: "source.html" },
325
- { url: "https://example.com/doc.pdf", expected: "source.pdf" },
326
- { url: "https://example.com/notes.md", expected: "source.md" },
327
- { url: "https://example.com/data.xml", expected: "source.xml" },
328
- { url: "https://example.com/readme.txt", expected: "source.txt" },
329
- { url: "https://example.com/page", expected: "source.html" },
330
- { url: "https://example.com/page?format=pdf", expected: "source.html" },
331
- ];
332
-
333
- for (const { url, expected } of cases) {
334
- const paths = makePaths();
335
- const pi = mockPi();
336
- const result = await captureUrl(pi as never, paths, url);
337
- const originalFile = join(result.packetPath, "original", expected);
338
- expect(existsSync(originalFile)).toBe(true);
339
- }
340
- });
341
-
342
- it("should render source page without an Original: line for text captures", async () => {
343
- const paths = makePaths();
344
- const result = captureText(paths, "Some text content", "My Note");
345
-
346
- const sourcePage = readFile(result.sourcePagePath);
347
- expect(sourcePage).toContain("# My Note");
348
- expect(sourcePage).not.toContain("Original:");
349
- expect(sourcePage).toContain("_Auto-preview: Some text content_");
350
- });
351
-
352
- it("should truncate auto-preview to 500 characters", async () => {
353
- const paths = makePaths();
354
- const longText = "A".repeat(1000);
355
- const result = captureText(paths, longText, "Long Note");
356
-
357
- const sourcePage = readFile(result.sourcePagePath);
358
- // Preview should be 500 chars + "..."
359
- expect(sourcePage).toContain(`_Auto-preview: ${"A".repeat(500)}..._`);
360
- });
361
-
362
- it("should handle local PDF file capture failure message when MarkItDown is unavailable", async () => {
363
- const paths = makePaths();
364
- // Create a temporary PDF file
365
- const pdfPath = join(tempDir, "test.pdf");
366
- writeFileSync(pdfPath, pdfBytes, "utf-8");
367
-
368
- const pi = mockPi();
369
- const result = await captureFile(pi as never, paths, pdfPath);
370
-
371
- const extracted = readFile(join(result.packetPath, "extracted.md"));
372
- expect(extracted).not.toContain("%PDF-");
373
- expect(extracted).toContain("PDF content could not be converted");
374
- });
375
-
376
- it("should copy local non-PDF file content to extracted.md", async () => {
377
- const paths = makePaths();
378
- const mdPath = join(tempDir, "notes.md");
379
- writeFileSync(mdPath, "# My Notes\n\nHello world.", "utf-8");
380
-
381
- const pi = mockPi();
382
- const result = await captureFile(pi as never, paths, mdPath);
383
-
384
- const extracted = readFile(join(result.packetPath, "extracted.md"));
385
- expect(extracted).toContain("# My Notes");
386
- expect(extracted).toContain("Hello world.");
387
-
388
- // Original file should be preserved
389
- expect(existsSync(join(result.packetPath, "original", "notes.md"))).toBe(true);
390
- });
391
-
392
- it("should convert XML files to readable markdown in extracted.md", async () => {
393
- const paths = makePaths();
394
- const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
395
- <document>
396
- <title>Project Report</title>
397
- <section>
398
- <heading>Findings</heading>
399
- <p>The analysis revealed several key insights.</p>
400
- <list>
401
- <item>First finding</item>
402
- <item>Second finding</item>
403
- </list>
404
- </section>
405
- </document>`;
406
- const xmlPath = join(tempDir, "report.xml");
407
- writeFileSync(xmlPath, xmlContent, "utf-8");
408
-
409
- const pi = mockPi();
410
- const result = await captureFile(pi as never, paths, xmlPath);
411
-
412
- const extracted = readFile(join(result.packetPath, "extracted.md"));
413
- // Should have extracted title
414
- expect(extracted).toContain("Project Report");
415
- // Should have extracted text content
416
- expect(extracted).toContain("The analysis revealed several key insights.");
417
- expect(extracted).toContain("First finding");
418
- expect(extracted).toContain("Second finding");
419
- // Should NOT contain raw XML tags
420
- expect(extracted).not.toContain("<?xml");
421
- expect(extracted).not.toContain("<document>");
422
- expect(extracted).not.toContain("</document>");
423
-
424
- // Original file should be preserved
425
- expect(existsSync(join(result.packetPath, "original", "report.xml"))).toBe(true);
426
- });
427
-
428
- it("should fall back to raw XML content when tag stripping produces nothing", async () => {
429
- const paths = makePaths();
430
- const xmlContent = `<?xml version="1.0"?><data><![CDATA[Hello]]></data>`;
431
- const xmlPath = join(tempDir, "minimal.xml");
432
- writeFileSync(xmlPath, xmlContent, "utf-8");
433
-
434
- const pi = mockPi();
435
- const result = await captureFile(pi as never, paths, xmlPath);
436
-
437
- const extracted = readFile(join(result.packetPath, "extracted.md"));
438
- // Should have the text content at minimum
439
- expect(extracted).toContain("Hello");
440
- });
441
- });
442
-
443
- describe("wiki directory structure", () => {
444
- let wikiDir: string;
445
-
446
- beforeEach(() => {
447
- tempDir = join(tmpdir(), `pi-llm-wiki-test-${Date.now()}`);
448
- mkdirSync(tempDir, { recursive: true });
449
- wikiDir = createWikiRoot();
450
- });
451
-
452
- afterEach(() => {
453
- rmSync(tempDir, { recursive: true, force: true });
454
- });
455
-
456
- it("should have all required directories", () => {
457
- expect(existsSync(join(wikiDir, "raw", "articles"))).toBe(true);
458
- expect(existsSync(join(wikiDir, "raw", "papers"))).toBe(true);
459
- expect(existsSync(join(wikiDir, "raw", "notes"))).toBe(true);
460
- expect(existsSync(join(wikiDir, "wiki", "entities"))).toBe(true);
461
- expect(existsSync(join(wikiDir, "wiki", "concepts"))).toBe(true);
462
- expect(existsSync(join(wikiDir, "wiki", "sources"))).toBe(true);
463
- expect(existsSync(join(wikiDir, "wiki", "syntheses"))).toBe(true);
464
- expect(existsSync(join(wikiDir, "wiki", "changes"))).toBe(true);
465
- expect(existsSync(join(wikiDir, "outputs"))).toBe(true);
466
- expect(existsSync(join(wikiDir, ".discoveries"))).toBe(true);
467
- });
468
-
469
- it("should create source pages from ingested files", () => {
470
- createConfig(wikiDir);
471
- createSourceFile(wikiDir, "test-article.md", "# Test\nContent about AI.");
472
- expect(existsSync(join(wikiDir, "raw", "articles", "test-article.md"))).toBe(true);
473
-
474
- createWikiPage(
475
- wikiDir,
476
- "sources",
477
- "test-article.md",
478
- "---\ntype: source\nformat: article\nraw_path: raw/articles/test-article.md\ningested: 2026-04-27\ntopics: [ai]\n---\n\n# Test Article\n\n## Summary\nAI content.\n",
479
- );
480
- const content = readFile(join(wikiDir, "wiki", "sources", "test-article.md"));
481
- expect(content).toContain("type: source");
482
- expect(content).toContain("raw_path: raw/articles/test-article.md");
483
- });
484
-
485
- it("should create entity pages with correct format", () => {
486
- createWikiPage(
487
- wikiDir,
488
- "entities",
489
- "test-entity.md",
490
- "---\ntype: entity\ncategory: person\ncreated: 2026-04-27\nupdated: 2026-04-27\nsources: [raw/articles/test.md]\n---\n\n# Person\n\n## Links\n- [[related-concept]]\n\n## Sources\n- [test](../raw/articles/test.md)\n",
491
- );
492
- const content = readFile(join(wikiDir, "wiki", "entities", "test-entity.md"));
493
- expect(content).toContain("type: entity");
494
- expect(content).toContain("category: person");
495
- expect(content).toContain("[[related-concept]]");
496
- });
497
-
498
- it("should create concept pages with correct format", () => {
499
- createWikiPage(
500
- wikiDir,
501
- "concepts",
502
- "test-concept.md",
503
- "---\ntype: concept\ndomain: engineering\ncreated: 2026-04-27\nupdated: 2026-04-27\nsources: []\n---\n\n# Test Concept\n\n## Links\n- [[other-concept]]\n",
504
- );
505
- const content = readFile(join(wikiDir, "wiki", "concepts", "test-concept.md"));
506
- expect(content).toContain("type: concept");
507
- expect(content).toContain("domain: engineering");
508
- expect(content).toContain("[[other-concept]]");
509
- });
510
-
511
- it("should create synthesis pages with correct format", () => {
512
- createWikiPage(
513
- wikiDir,
514
- "syntheses",
515
- "comparison.md",
516
- "---\ntype: synthesis\ntopic: comparison\ncreated: 2026-04-27\nupdated: 2026-04-27\nsources_count: 2\n---\n\n# Comparison\n\n## Sources Used\n- [[source-1]]\n- [[source-2]]\n",
517
- );
518
- const content = readFile(join(wikiDir, "wiki", "syntheses", "comparison.md"));
519
- expect(content).toContain("type: synthesis");
520
- expect(content).toContain("sources_count: 2");
521
- expect(content).toContain("[[source-1]]");
522
- });
523
-
524
- it("should maintain INDEX.md catalog", () => {
525
- createWikiPage(
526
- wikiDir,
527
- "",
528
- "INDEX.md",
529
- "# Wiki Index\n\n## Entities\n- [test](entities/test.md)\n",
530
- );
531
- const content = readFile(join(wikiDir, "wiki", "INDEX.md"));
532
- expect(content).toContain("test");
533
- });
534
-
535
- it("should append to LOG.md", () => {
536
- writeFileSync(join(wikiDir, "wiki", "LOG.md"), "## [2026-04-27] ingest | 3 pages\n");
537
- const content = readFile(join(wikiDir, "wiki", "LOG.md"));
538
- expect(content).toContain("ingest");
539
- expect(content).toContain("3 pages");
540
- });
541
-
542
- it("should handle contradiction markers", () => {
543
- createWikiPage(
544
- wikiDir,
545
- "concepts",
546
- "conflict.md",
547
- "---\ntype: concept\ndomain: ai\ncreated: 2026-04-27\nupdated: 2026-04-27\nsources: [a.md, b.md]\n---\n\n> ⚠️ **Contradiction:** A claims X but B claims Y.\n",
548
- );
549
- const content = readFile(join(wikiDir, "wiki", "concepts", "conflict.md"));
550
- expect(content).toContain("Contradiction:");
551
- });
552
- });
553
-
554
- // ─── Cross-Reference Integrity ─────────────────────────
555
-
556
- describe("cross-reference integrity", () => {
557
- let wikiDir: string;
558
-
559
- beforeEach(() => {
560
- tempDir = join(tmpdir(), `pi-llm-wiki-xref-${Date.now()}`);
561
- mkdirSync(tempDir, { recursive: true });
562
- wikiDir = createWikiRoot();
563
- });
564
-
565
- afterEach(() => {
566
- rmSync(tempDir, { recursive: true, force: true });
567
- });
568
-
569
- it("should allow orphan detection by absence of inbound wikilinks", () => {
570
- createWikiPage(
571
- wikiDir,
572
- "entities",
573
- "orphan.md",
574
- "---\ntype: entity\ncategory: person\ncreated: 2026-04-27\nupdated: 2026-04-27\nsources: []\n---\n# Orphan\n",
575
- );
576
- const content = readFile(join(wikiDir, "wiki", "entities", "orphan.md"));
577
- expect(content).not.toContain("[[orphan");
578
- });
579
-
580
- it("should detect broken wikilinks referencing nonexistent pages", () => {
581
- createWikiPage(
582
- wikiDir,
583
- "concepts",
584
- "main.md",
585
- "---\ntype: concept\ndomain: ai\ncreated: 2026-04-27\nupdated: 2026-04-27\nsources: []\n---\n\n# Main\n[[missing-page]] and [[another-missing]]\n",
586
- );
587
- const content = readFile(join(wikiDir, "wiki", "concepts", "main.md"));
588
- expect(content).toContain("[[missing-page]]");
589
- expect(content).toContain("[[another-missing]]");
590
- expect(existsSync(join(wikiDir, "wiki", "entities", "missing-page.md"))).toBe(false);
591
- expect(existsSync(join(wikiDir, "wiki", "concepts", "missing-page.md"))).toBe(false);
592
- });
593
- });
594
-
595
- // ─── Configuration Tests ──────────────────────────────
596
-
597
- describe("configuration", () => {
598
- let wikiDir: string;
599
-
600
- beforeEach(() => {
601
- tempDir = join(tmpdir(), `pi-llm-wiki-config-${Date.now()}`);
602
- mkdirSync(tempDir, { recursive: true });
603
- wikiDir = createWikiRoot();
604
- });
605
-
606
- afterEach(() => {
607
- rmSync(tempDir, { recursive: true, force: true });
608
- });
609
-
610
- it("should accept personal mode config", () => {
611
- createConfig(wikiDir, { wiki: { mode: "personal", topic: "Learning" } });
612
- const config = readFile(join(wikiDir, "config.yaml"));
613
- expect(config).toContain("mode: personal");
614
- });
615
-
616
- it("should accept company mode config", () => {
617
- createConfig(wikiDir, { wiki: { mode: "company", topic: "Competitors" } });
618
- const config = readFile(join(wikiDir, "config.yaml"));
619
- expect(config).toContain("mode: company");
620
- });
621
-
622
- it("should support company mode with change detection pages", () => {
623
- createConfig(wikiDir, { wiki: { mode: "company", topic: "Market" }, change_detection: true });
624
- const config = readFile(join(wikiDir, "config.yaml"));
625
- expect(config).toContain("mode: company");
626
-
627
- createWikiPage(
628
- wikiDir,
629
- "changes",
630
- "competitor-2026-04-27.md",
631
- "---\ntype: change\nentity: competitor\ndetected: 2026-04-27\n---\n\n# Change\nPricing changed from $99 to $149.\n",
632
- );
633
- expect(existsSync(join(wikiDir, "wiki", "changes", "competitor-2026-04-27.md"))).toBe(true);
634
- const content = readFile(join(wikiDir, "wiki", "changes", "competitor-2026-04-27.md"));
635
- expect(content).toContain("type: change");
636
- expect(content).toContain("Pricing changed");
637
- });
638
-
639
- it("should track discovery history", () => {
640
- const history = { processed: [{ path: "raw/articles/a.md", ingested: "2026-04-27" }] };
641
- writeFileSync(join(wikiDir, ".discoveries", "history.json"), JSON.stringify(history));
642
- const content = JSON.parse(readFile(join(wikiDir, ".discoveries", "history.json")));
643
- expect(content.processed).toHaveLength(1);
644
- expect(content.processed[0].path).toBe("raw/articles/a.md");
645
- });
646
-
647
- it("should track knowledge gaps", () => {
648
- const gaps = { gaps: [{ topic: "reinforcement learning", priority: "high" }] };
649
- writeFileSync(join(wikiDir, ".discoveries", "gaps.json"), JSON.stringify(gaps));
650
- const content = JSON.parse(readFile(join(wikiDir, ".discoveries", "gaps.json")));
651
- expect(content.gaps).toHaveLength(1);
652
- expect(content.gaps[0].priority).toBe("high");
653
- });
654
- });
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "types": ["node"],
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "noEmit": true,
13
- "isolatedModules": true,
14
- "outDir": "dist",
15
- "rootDir": "."
16
- },
17
- "include": ["extensions/**/*.ts", "test/**/*.ts"],
18
- "exclude": ["node_modules", "dist"]
19
- }
package/vitest.config.ts DELETED
@@ -1,16 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- include: ["test/**/*.test.ts"],
7
- environment: "node",
8
- testTimeout: 10_000,
9
- hookTimeout: 10_000,
10
- coverage: {
11
- provider: "v8",
12
- reporter: ["text", "lcov", "html"],
13
- include: ["extensions/**/*.ts", "skills/**/*.md"],
14
- },
15
- },
16
- });