@sourcepress/knowledge 0.1.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.
Files changed (100) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +21 -0
  3. package/dist/__tests__/graph-builder.test.d.ts +2 -0
  4. package/dist/__tests__/graph-builder.test.d.ts.map +1 -0
  5. package/dist/__tests__/graph-builder.test.js +122 -0
  6. package/dist/__tests__/graph-builder.test.js.map +1 -0
  7. package/dist/__tests__/graph-ops.test.d.ts +2 -0
  8. package/dist/__tests__/graph-ops.test.d.ts.map +1 -0
  9. package/dist/__tests__/graph-ops.test.js +181 -0
  10. package/dist/__tests__/graph-ops.test.js.map +1 -0
  11. package/dist/__tests__/ingestion.test.d.ts +2 -0
  12. package/dist/__tests__/ingestion.test.d.ts.map +1 -0
  13. package/dist/__tests__/ingestion.test.js +108 -0
  14. package/dist/__tests__/ingestion.test.js.map +1 -0
  15. package/dist/__tests__/json-file-store.test.d.ts +2 -0
  16. package/dist/__tests__/json-file-store.test.d.ts.map +1 -0
  17. package/dist/__tests__/json-file-store.test.js +180 -0
  18. package/dist/__tests__/json-file-store.test.js.map +1 -0
  19. package/dist/__tests__/knowledge-engine.test.d.ts +2 -0
  20. package/dist/__tests__/knowledge-engine.test.d.ts.map +1 -0
  21. package/dist/__tests__/knowledge-engine.test.js +152 -0
  22. package/dist/__tests__/knowledge-engine.test.js.map +1 -0
  23. package/dist/__tests__/knowledge-store.test.d.ts +2 -0
  24. package/dist/__tests__/knowledge-store.test.d.ts.map +1 -0
  25. package/dist/__tests__/knowledge-store.test.js +97 -0
  26. package/dist/__tests__/knowledge-store.test.js.map +1 -0
  27. package/dist/__tests__/scraper.test.d.ts +2 -0
  28. package/dist/__tests__/scraper.test.d.ts.map +1 -0
  29. package/dist/__tests__/scraper.test.js +66 -0
  30. package/dist/__tests__/scraper.test.js.map +1 -0
  31. package/dist/__tests__/sitemap-parser.test.d.ts +2 -0
  32. package/dist/__tests__/sitemap-parser.test.d.ts.map +1 -0
  33. package/dist/__tests__/sitemap-parser.test.js +75 -0
  34. package/dist/__tests__/sitemap-parser.test.js.map +1 -0
  35. package/dist/graph-builder.d.ts +17 -0
  36. package/dist/graph-builder.d.ts.map +1 -0
  37. package/dist/graph-builder.js +98 -0
  38. package/dist/graph-builder.js.map +1 -0
  39. package/dist/graph-ops.d.ts +21 -0
  40. package/dist/graph-ops.d.ts.map +1 -0
  41. package/dist/graph-ops.js +108 -0
  42. package/dist/graph-ops.js.map +1 -0
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +8 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/ingestion/index.d.ts +4 -0
  48. package/dist/ingestion/index.d.ts.map +1 -0
  49. package/dist/ingestion/index.js +3 -0
  50. package/dist/ingestion/index.js.map +1 -0
  51. package/dist/ingestion/scraper.d.ts +22 -0
  52. package/dist/ingestion/scraper.d.ts.map +1 -0
  53. package/dist/ingestion/scraper.js +118 -0
  54. package/dist/ingestion/scraper.js.map +1 -0
  55. package/dist/ingestion/sitemap-parser.d.ts +32 -0
  56. package/dist/ingestion/sitemap-parser.d.ts.map +1 -0
  57. package/dist/ingestion/sitemap-parser.js +104 -0
  58. package/dist/ingestion/sitemap-parser.js.map +1 -0
  59. package/dist/ingestion/types.d.ts +58 -0
  60. package/dist/ingestion/types.d.ts.map +1 -0
  61. package/dist/ingestion/types.js +2 -0
  62. package/dist/ingestion/types.js.map +1 -0
  63. package/dist/json-file-store.d.ts +19 -0
  64. package/dist/json-file-store.d.ts.map +1 -0
  65. package/dist/json-file-store.js +100 -0
  66. package/dist/json-file-store.js.map +1 -0
  67. package/dist/knowledge-engine.d.ts +45 -0
  68. package/dist/knowledge-engine.d.ts.map +1 -0
  69. package/dist/knowledge-engine.js +160 -0
  70. package/dist/knowledge-engine.js.map +1 -0
  71. package/dist/knowledge-store.d.ts +14 -0
  72. package/dist/knowledge-store.d.ts.map +1 -0
  73. package/dist/knowledge-store.js +40 -0
  74. package/dist/knowledge-store.js.map +1 -0
  75. package/dist/types.d.ts +67 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +2 -0
  78. package/dist/types.js.map +1 -0
  79. package/package.json +26 -0
  80. package/src/__tests__/graph-builder.test.ts +129 -0
  81. package/src/__tests__/graph-ops.test.ts +189 -0
  82. package/src/__tests__/ingestion.test.ts +127 -0
  83. package/src/__tests__/json-file-store.test.ts +206 -0
  84. package/src/__tests__/knowledge-engine.test.ts +177 -0
  85. package/src/__tests__/knowledge-store.test.ts +111 -0
  86. package/src/__tests__/scraper.test.ts +74 -0
  87. package/src/__tests__/sitemap-parser.test.ts +85 -0
  88. package/src/graph-builder.ts +109 -0
  89. package/src/graph-ops.ts +129 -0
  90. package/src/index.ts +27 -0
  91. package/src/ingestion/index.ts +10 -0
  92. package/src/ingestion/scraper.ts +137 -0
  93. package/src/ingestion/sitemap-parser.ts +119 -0
  94. package/src/ingestion/types.ts +57 -0
  95. package/src/json-file-store.ts +127 -0
  96. package/src/knowledge-engine.ts +217 -0
  97. package/src/knowledge-store.ts +49 -0
  98. package/src/types.ts +76 -0
  99. package/tsconfig.json +5 -0
  100. package/vitest.config.ts +2 -0
@@ -0,0 +1,180 @@
1
+ import { existsSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { JsonFileStore } from "../json-file-store.js";
6
+ function makeKnowledgeFile(overrides = {}) {
7
+ return {
8
+ path: "knowledge/clients/acme.md",
9
+ type: "project-notes",
10
+ quality: "structured",
11
+ quality_score: 8,
12
+ entities: [
13
+ { type: "client", name: "Acme Corp" },
14
+ { type: "technology", name: "Next.js" },
15
+ ],
16
+ ingested_at: "2026-04-04T10:00:00Z",
17
+ source: "manual",
18
+ body: "Meeting with Acme Corp about Next.js migration.",
19
+ ...overrides,
20
+ };
21
+ }
22
+ function makeGraph() {
23
+ return {
24
+ entities: new Map([
25
+ [
26
+ "acme-corp",
27
+ {
28
+ type: "client",
29
+ name: "Acme Corp",
30
+ aliases: ["Acme"],
31
+ confidence: 0.95,
32
+ source_file: "knowledge/clients/acme.md",
33
+ },
34
+ ],
35
+ ]),
36
+ relations: [
37
+ {
38
+ from_entity: "acme-corp",
39
+ to_entity: "nextjs",
40
+ relation_type: "uses",
41
+ confidence: 0.9,
42
+ evidence: "Migration project",
43
+ source_file: "knowledge/clients/acme.md",
44
+ },
45
+ ],
46
+ clusters: [],
47
+ built_at: "2026-04-04T12:00:00Z",
48
+ file_count: 1,
49
+ };
50
+ }
51
+ let testDir;
52
+ describe("JsonFileStore", () => {
53
+ beforeEach(() => {
54
+ testDir = join(tmpdir(), `sourcepress-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
55
+ });
56
+ afterEach(() => {
57
+ if (existsSync(testDir)) {
58
+ rmSync(testDir, { recursive: true });
59
+ }
60
+ });
61
+ it("creates the directory if it does not exist", () => {
62
+ new JsonFileStore(testDir);
63
+ expect(existsSync(testDir)).toBe(true);
64
+ });
65
+ describe("store and retrieve", () => {
66
+ it("stores and retrieves a knowledge file", async () => {
67
+ const store = new JsonFileStore(testDir);
68
+ const file = makeKnowledgeFile();
69
+ await store.store(file);
70
+ const retrieved = await store.retrieve(file.path);
71
+ expect(retrieved).toEqual(file);
72
+ });
73
+ it("returns null for non-existent file", async () => {
74
+ const store = new JsonFileStore(testDir);
75
+ const retrieved = await store.retrieve("nonexistent.md");
76
+ expect(retrieved).toBeNull();
77
+ });
78
+ it("overwrites existing file at same path", async () => {
79
+ const store = new JsonFileStore(testDir);
80
+ await store.store(makeKnowledgeFile());
81
+ await store.store(makeKnowledgeFile({ quality_score: 9 }));
82
+ const retrieved = await store.retrieve("knowledge/clients/acme.md");
83
+ expect(retrieved?.quality_score).toBe(9);
84
+ });
85
+ });
86
+ describe("persistence across instances", () => {
87
+ it("persists knowledge files to disk", async () => {
88
+ const store1 = new JsonFileStore(testDir);
89
+ await store1.store(makeKnowledgeFile({ path: "a.md" }));
90
+ await store1.store(makeKnowledgeFile({ path: "b.md" }));
91
+ const store2 = new JsonFileStore(testDir);
92
+ expect(await store2.count()).toBe(2);
93
+ expect(await store2.retrieve("a.md")).not.toBeNull();
94
+ });
95
+ it("persists graph to disk", async () => {
96
+ const store1 = new JsonFileStore(testDir);
97
+ const graph = makeGraph();
98
+ await store1.saveGraph(graph);
99
+ const store2 = new JsonFileStore(testDir);
100
+ const loaded = await store2.loadGraph();
101
+ expect(loaded).not.toBeNull();
102
+ expect(loaded?.entities.get("acme-corp")?.name).toBe("Acme Corp");
103
+ expect(loaded?.relations).toHaveLength(1);
104
+ expect(loaded?.built_at).toBe("2026-04-04T12:00:00Z");
105
+ });
106
+ });
107
+ describe("list", () => {
108
+ it("lists all stored files", async () => {
109
+ const store = new JsonFileStore(testDir);
110
+ await store.store(makeKnowledgeFile({ path: "a.md" }));
111
+ await store.store(makeKnowledgeFile({ path: "b.md" }));
112
+ const files = await store.list();
113
+ expect(files).toHaveLength(2);
114
+ });
115
+ it("filters by type", async () => {
116
+ const store = new JsonFileStore(testDir);
117
+ await store.store(makeKnowledgeFile({ path: "a.md", type: "project-notes" }));
118
+ await store.store(makeKnowledgeFile({ path: "b.md", type: "transcript" }));
119
+ const files = await store.list({ type: "transcript" });
120
+ expect(files).toHaveLength(1);
121
+ expect(files[0].type).toBe("transcript");
122
+ });
123
+ it("filters by quality", async () => {
124
+ const store = new JsonFileStore(testDir);
125
+ await store.store(makeKnowledgeFile({ path: "a.md", quality: "structured" }));
126
+ await store.store(makeKnowledgeFile({ path: "b.md", quality: "draft" }));
127
+ const files = await store.list({ quality: "draft" });
128
+ expect(files).toHaveLength(1);
129
+ });
130
+ it("filters by source", async () => {
131
+ const store = new JsonFileStore(testDir);
132
+ await store.store(makeKnowledgeFile({ path: "a.md", source: "manual" }));
133
+ await store.store(makeKnowledgeFile({ path: "b.md", source: "url" }));
134
+ const files = await store.list({ source: "url" });
135
+ expect(files).toHaveLength(1);
136
+ });
137
+ });
138
+ describe("delete", () => {
139
+ it("deletes an existing file and persists", async () => {
140
+ const store = new JsonFileStore(testDir);
141
+ await store.store(makeKnowledgeFile());
142
+ const deleted = await store.delete("knowledge/clients/acme.md");
143
+ expect(deleted).toBe(true);
144
+ const store2 = new JsonFileStore(testDir);
145
+ expect(await store2.retrieve("knowledge/clients/acme.md")).toBeNull();
146
+ });
147
+ it("returns false for non-existent file", async () => {
148
+ const store = new JsonFileStore(testDir);
149
+ expect(await store.delete("nonexistent.md")).toBe(false);
150
+ });
151
+ });
152
+ describe("count", () => {
153
+ it("returns 0 for empty store", async () => {
154
+ const store = new JsonFileStore(testDir);
155
+ expect(await store.count()).toBe(0);
156
+ });
157
+ it("returns correct count", async () => {
158
+ const store = new JsonFileStore(testDir);
159
+ await store.store(makeKnowledgeFile({ path: "a.md" }));
160
+ await store.store(makeKnowledgeFile({ path: "b.md" }));
161
+ expect(await store.count()).toBe(2);
162
+ });
163
+ });
164
+ describe("graph", () => {
165
+ it("returns null when no graph saved", async () => {
166
+ const store = new JsonFileStore(testDir);
167
+ expect(await store.loadGraph()).toBeNull();
168
+ });
169
+ it("preserves Map structure through serialization", async () => {
170
+ const store = new JsonFileStore(testDir);
171
+ const graph = makeGraph();
172
+ await store.saveGraph(graph);
173
+ const store2 = new JsonFileStore(testDir);
174
+ const loaded = await store2.loadGraph();
175
+ expect(loaded?.entities).toBeInstanceOf(Map);
176
+ expect(loaded?.entities.size).toBe(1);
177
+ });
178
+ });
179
+ });
180
+ //# sourceMappingURL=json-file-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-file-store.test.js","sourceRoot":"","sources":["../../src/__tests__/json-file-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=knowledge-engine.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge-engine.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/knowledge-engine.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,152 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { KnowledgeEngine } from "../knowledge-engine.js";
3
+ import { InMemoryKnowledgeStore } from "../knowledge-store.js";
4
+ vi.mock("@sourcepress/ai", async () => {
5
+ const actual = await vi.importActual("@sourcepress/ai");
6
+ return { ...actual, classify: vi.fn(), extract: vi.fn() };
7
+ });
8
+ import { classify, extract } from "@sourcepress/ai";
9
+ const mockProvider = {
10
+ provider: "anthropic",
11
+ model: "claude-sonnet-4-5-20250514",
12
+ };
13
+ function makeMockBudget() {
14
+ return {
15
+ record: vi.fn(),
16
+ getStatus: vi.fn().mockReturnValue({
17
+ spent_today_usd: 0,
18
+ limit_usd: 5,
19
+ warn_at_usd: 3,
20
+ remaining_usd: 5,
21
+ is_over_limit: false,
22
+ is_warned: false,
23
+ reset_at: new Date().toISOString(),
24
+ }),
25
+ canSpend: vi.fn().mockReturnValue(true),
26
+ getHistory: vi.fn().mockReturnValue([]),
27
+ resetDaily: vi.fn(),
28
+ };
29
+ }
30
+ const mockUsage = {
31
+ input_tokens: 500,
32
+ output_tokens: 100,
33
+ estimated_cost_usd: 0.01,
34
+ function_name: "test",
35
+ timestamp: new Date().toISOString(),
36
+ };
37
+ describe("KnowledgeEngine", () => {
38
+ let engine;
39
+ let store;
40
+ let budget;
41
+ beforeEach(() => {
42
+ store = new InMemoryKnowledgeStore();
43
+ budget = makeMockBudget();
44
+ engine = new KnowledgeEngine(store, mockProvider, budget);
45
+ vi.clearAllMocks();
46
+ });
47
+ describe("ingest", () => {
48
+ it("classifies, extracts, and stores a knowledge file", async () => {
49
+ vi.mocked(classify).mockResolvedValueOnce({
50
+ quality: "structured",
51
+ quality_score: 8,
52
+ type: "project-notes",
53
+ reasoning: "Well structured",
54
+ usage: mockUsage,
55
+ });
56
+ vi.mocked(extract).mockResolvedValueOnce({
57
+ entities: [{ type: "client", name: "Acme Corp", aliases: ["Acme"], confidence: 0.95 }],
58
+ relations: [
59
+ {
60
+ from_entity: "Acme Corp",
61
+ to_entity: "Next.js",
62
+ relation_type: "uses",
63
+ confidence: 0.9,
64
+ evidence: "Acme uses Next.js",
65
+ },
66
+ ],
67
+ usage: mockUsage,
68
+ });
69
+ const result = await engine.ingest("knowledge/clients/acme.md", "Meeting with Acme Corp about their Next.js migration.", "manual");
70
+ expect(result.path).toBe("knowledge/clients/acme.md");
71
+ expect(result.quality).toBe("structured");
72
+ expect(result.quality_score).toBe(8);
73
+ expect(result.entities).toHaveLength(1);
74
+ const stored = await store.retrieve("knowledge/clients/acme.md");
75
+ expect(stored).not.toBeNull();
76
+ // biome-ignore lint/style/noNonNullAssertion: asserted not null on line above
77
+ expect(stored.quality).toBe("structured");
78
+ });
79
+ });
80
+ describe("buildGraph", () => {
81
+ it("builds graph from all stored knowledge files", async () => {
82
+ await store.store({
83
+ path: "knowledge/a.md",
84
+ type: "notes",
85
+ quality: "structured",
86
+ quality_score: 8,
87
+ entities: [{ type: "client", name: "Acme Corp" }],
88
+ ingested_at: new Date().toISOString(),
89
+ source: "manual",
90
+ body: "Acme meeting notes",
91
+ });
92
+ vi.mocked(extract).mockResolvedValueOnce({
93
+ entities: [{ type: "client", name: "Acme Corp", aliases: ["Acme"], confidence: 0.95 }],
94
+ relations: [],
95
+ usage: mockUsage,
96
+ });
97
+ const graph = await engine.buildGraph();
98
+ expect(graph.entities.size).toBeGreaterThanOrEqual(1);
99
+ expect(graph.built_at).toBeTruthy();
100
+ });
101
+ });
102
+ describe("query", () => {
103
+ it("queries the graph for an entity", async () => {
104
+ await store.store({
105
+ path: "knowledge/a.md",
106
+ type: "notes",
107
+ quality: "structured",
108
+ quality_score: 8,
109
+ entities: [{ type: "client", name: "Acme Corp" }],
110
+ ingested_at: new Date().toISOString(),
111
+ source: "manual",
112
+ body: "Acme notes",
113
+ });
114
+ vi.mocked(extract).mockResolvedValueOnce({
115
+ entities: [{ type: "client", name: "Acme Corp", aliases: ["Acme"], confidence: 0.95 }],
116
+ relations: [],
117
+ usage: mockUsage,
118
+ });
119
+ await engine.buildGraph();
120
+ const result = engine.query("Acme Corp");
121
+ expect(result).not.toBeNull();
122
+ // biome-ignore lint/style/noNonNullAssertion: asserted not null on line above
123
+ expect(result.entity.name).toBe("Acme Corp");
124
+ });
125
+ it("returns null before graph is built", () => {
126
+ expect(engine.query("Anything")).toBeNull();
127
+ });
128
+ });
129
+ describe("findGaps", () => {
130
+ it("delegates gap detection to graph ops", async () => {
131
+ await store.store({
132
+ path: "knowledge/a.md",
133
+ type: "notes",
134
+ quality: "structured",
135
+ quality_score: 8,
136
+ entities: [{ type: "client", name: "Acme Corp" }],
137
+ ingested_at: new Date().toISOString(),
138
+ source: "manual",
139
+ body: "Acme notes",
140
+ });
141
+ vi.mocked(extract).mockResolvedValueOnce({
142
+ entities: [{ type: "client", name: "Acme Corp", aliases: [], confidence: 0.95 }],
143
+ relations: [],
144
+ usage: mockUsage,
145
+ });
146
+ await engine.buildGraph();
147
+ const gaps = engine.findGaps([]);
148
+ expect(gaps.length).toBeGreaterThanOrEqual(1);
149
+ });
150
+ });
151
+ });
152
+ //# sourceMappingURL=knowledge-engine.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge-engine.test.js","sourceRoot":"","sources":["../../src/__tests__/knowledge-engine.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=knowledge-store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge-store.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/knowledge-store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,97 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { InMemoryKnowledgeStore } from "../knowledge-store.js";
3
+ function makeKnowledgeFile(overrides = {}) {
4
+ return {
5
+ path: "knowledge/clients/acme.md",
6
+ type: "project-notes",
7
+ quality: "structured",
8
+ quality_score: 8,
9
+ entities: [
10
+ { type: "client", name: "Acme Corp" },
11
+ { type: "technology", name: "Next.js" },
12
+ ],
13
+ ingested_at: "2026-04-04T10:00:00Z",
14
+ source: "manual",
15
+ body: "Meeting with Acme Corp about Next.js migration.",
16
+ ...overrides,
17
+ };
18
+ }
19
+ describe("InMemoryKnowledgeStore", () => {
20
+ let store;
21
+ beforeEach(() => {
22
+ store = new InMemoryKnowledgeStore();
23
+ });
24
+ describe("store and retrieve", () => {
25
+ it("stores and retrieves a knowledge file", async () => {
26
+ const file = makeKnowledgeFile();
27
+ await store.store(file);
28
+ const retrieved = await store.retrieve(file.path);
29
+ expect(retrieved).toEqual(file);
30
+ });
31
+ it("returns null for non-existent file", async () => {
32
+ const retrieved = await store.retrieve("nonexistent.md");
33
+ expect(retrieved).toBeNull();
34
+ });
35
+ it("overwrites existing file at same path", async () => {
36
+ const file = makeKnowledgeFile();
37
+ await store.store(file);
38
+ const updated = makeKnowledgeFile({ quality_score: 9 });
39
+ await store.store(updated);
40
+ const retrieved = await store.retrieve(file.path);
41
+ expect(retrieved?.quality_score).toBe(9);
42
+ });
43
+ });
44
+ describe("list", () => {
45
+ it("lists all stored files", async () => {
46
+ await store.store(makeKnowledgeFile({ path: "knowledge/a.md" }));
47
+ await store.store(makeKnowledgeFile({ path: "knowledge/b.md" }));
48
+ const files = await store.list();
49
+ expect(files).toHaveLength(2);
50
+ });
51
+ it("filters by type", async () => {
52
+ await store.store(makeKnowledgeFile({ path: "a.md", type: "project-notes" }));
53
+ await store.store(makeKnowledgeFile({ path: "b.md", type: "transcript" }));
54
+ const files = await store.list({ type: "transcript" });
55
+ expect(files).toHaveLength(1);
56
+ expect(files[0].type).toBe("transcript");
57
+ });
58
+ it("filters by quality", async () => {
59
+ await store.store(makeKnowledgeFile({ path: "a.md", quality: "structured" }));
60
+ await store.store(makeKnowledgeFile({ path: "b.md", quality: "draft" }));
61
+ const files = await store.list({ quality: "draft" });
62
+ expect(files).toHaveLength(1);
63
+ expect(files[0].quality).toBe("draft");
64
+ });
65
+ it("filters by source", async () => {
66
+ await store.store(makeKnowledgeFile({ path: "a.md", source: "manual" }));
67
+ await store.store(makeKnowledgeFile({ path: "b.md", source: "url" }));
68
+ const files = await store.list({ source: "url" });
69
+ expect(files).toHaveLength(1);
70
+ expect(files[0].source).toBe("url");
71
+ });
72
+ });
73
+ describe("delete", () => {
74
+ it("deletes an existing file", async () => {
75
+ await store.store(makeKnowledgeFile());
76
+ const deleted = await store.delete("knowledge/clients/acme.md");
77
+ expect(deleted).toBe(true);
78
+ const retrieved = await store.retrieve("knowledge/clients/acme.md");
79
+ expect(retrieved).toBeNull();
80
+ });
81
+ it("returns false for non-existent file", async () => {
82
+ const deleted = await store.delete("nonexistent.md");
83
+ expect(deleted).toBe(false);
84
+ });
85
+ });
86
+ describe("count", () => {
87
+ it("returns 0 for empty store", async () => {
88
+ expect(await store.count()).toBe(0);
89
+ });
90
+ it("returns correct count", async () => {
91
+ await store.store(makeKnowledgeFile({ path: "a.md" }));
92
+ await store.store(makeKnowledgeFile({ path: "b.md" }));
93
+ expect(await store.count()).toBe(2);
94
+ });
95
+ });
96
+ });
97
+ //# sourceMappingURL=knowledge-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge-store.test.js","sourceRoot":"","sources":["../../src/__tests__/knowledge-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scraper.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scraper.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/scraper.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { Scraper } from "../ingestion/scraper.js";
3
+ const SAMPLE_HTML = `
4
+ <!DOCTYPE html>
5
+ <html>
6
+ <head><title>Test Article</title></head>
7
+ <body>
8
+ <header><nav>Skip me</nav></header>
9
+ <article>
10
+ <h1>Test Article Title</h1>
11
+ <p>This is the first paragraph with <strong>bold text</strong> and a
12
+ <a href="https://example.com">link</a>.</p>
13
+ <h2>Section Two</h2>
14
+ <p>Another paragraph with <em>italic</em> content. It has enough text to
15
+ be considered readable content by the Readability algorithm, which typically
16
+ requires a minimum amount of textual content to determine that an element
17
+ contains the main article body rather than boilerplate navigation.</p>
18
+ <ul>
19
+ <li>Item one</li>
20
+ <li>Item two</li>
21
+ </ul>
22
+ <p>Final paragraph to ensure sufficient content length for the readability
23
+ parser to properly identify the main content area of this test page.</p>
24
+ </article>
25
+ <footer>Footer stuff</footer>
26
+ </body>
27
+ </html>`;
28
+ function mockFetcher(html, status = 200) {
29
+ return vi.fn().mockResolvedValue({
30
+ ok: status >= 200 && status < 300,
31
+ status,
32
+ statusText: status === 200 ? "OK" : "Not Found",
33
+ text: () => Promise.resolve(html),
34
+ });
35
+ }
36
+ describe("Scraper", () => {
37
+ it("scrapes a URL and returns readable content", async () => {
38
+ const fetcher = mockFetcher(SAMPLE_HTML);
39
+ const scraper = new Scraper(fetcher);
40
+ const result = await scraper.scrape("https://example.com/article");
41
+ expect(fetcher).toHaveBeenCalledWith("https://example.com/article", undefined);
42
+ expect(result.url).toBe("https://example.com/article");
43
+ expect(result.title).toContain("Test Article");
44
+ expect(result.content).toContain("first paragraph");
45
+ expect(result.markdown).toContain("**bold text**");
46
+ expect(result.length).toBeGreaterThan(0);
47
+ expect(result.scraped_at).toBeTruthy();
48
+ });
49
+ it("throws on HTTP error", async () => {
50
+ const fetcher = mockFetcher("", 404);
51
+ const scraper = new Scraper(fetcher);
52
+ await expect(scraper.scrape("https://example.com/missing")).rejects.toThrow("404");
53
+ });
54
+ it("throws when no readable content found", async () => {
55
+ const fetcher = mockFetcher("<html><body><nav>Just navigation</nav></body></html>");
56
+ const scraper = new Scraper(fetcher);
57
+ await expect(scraper.scrape("https://example.com/empty")).rejects.toThrow("No readable content");
58
+ });
59
+ it("extracts markdown from HTML directly", () => {
60
+ const scraper = new Scraper(mockFetcher(""));
61
+ const result = scraper.extractFromHtml("https://example.com", SAMPLE_HTML);
62
+ expect(result.markdown).toContain("[link](https://example.com)");
63
+ expect(result.markdown).toContain("- Item one");
64
+ });
65
+ });
66
+ //# sourceMappingURL=scraper.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scraper.test.js","sourceRoot":"","sources":["../../src/__tests__/scraper.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sitemap-parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap-parser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sitemap-parser.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { SitemapParser } from "../ingestion/sitemap-parser.js";
3
+ const SAMPLE_SITEMAP = `<?xml version="1.0" encoding="UTF-8"?>
4
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
5
+ <url><loc>https://example.com/</loc></url>
6
+ <url><loc>https://example.com/blog/post-1</loc></url>
7
+ <url><loc>https://example.com/blog/post-2</loc></url>
8
+ <url><loc>https://example.com/services/web</loc></url>
9
+ <url><loc>https://example.com/services/mobile</loc></url>
10
+ <url><loc>https://example.com/services/cloud</loc></url>
11
+ <url><loc>https://example.com/cases/acme</loc></url>
12
+ <url><loc>https://example.com/about</loc></url>
13
+ </urlset>`;
14
+ function mockFetcher(xml, status = 200) {
15
+ return vi.fn().mockResolvedValue({
16
+ ok: status >= 200 && status < 300,
17
+ status,
18
+ statusText: "OK",
19
+ text: () => Promise.resolve(xml),
20
+ });
21
+ }
22
+ describe("SitemapParser", () => {
23
+ it("parses a sitemap and groups URLs by section", async () => {
24
+ const parser = new SitemapParser(mockFetcher(SAMPLE_SITEMAP));
25
+ const result = await parser.parse("https://example.com/sitemap.xml");
26
+ expect(result.total_urls).toBe(8);
27
+ expect(result.sections.length).toBeGreaterThanOrEqual(3);
28
+ const services = result.sections.find((s) => s.pattern === "/services/*");
29
+ expect(services).toBeDefined();
30
+ expect(services?.count).toBe(3);
31
+ const blog = result.sections.find((s) => s.pattern === "/blog/*");
32
+ expect(blog).toBeDefined();
33
+ expect(blog?.count).toBe(2);
34
+ });
35
+ it("throws on HTTP error", async () => {
36
+ const parser = new SitemapParser(mockFetcher("", 404));
37
+ await expect(parser.parse("https://example.com/sitemap.xml")).rejects.toThrow("404");
38
+ });
39
+ it("throws when no URLs found", async () => {
40
+ const parser = new SitemapParser(mockFetcher("<urlset></urlset>"));
41
+ await expect(parser.parse("https://example.com/sitemap.xml")).rejects.toThrow("No URLs found");
42
+ });
43
+ it("filters URLs by include patterns", async () => {
44
+ const parser = new SitemapParser(mockFetcher(SAMPLE_SITEMAP));
45
+ const result = await parser.parse("https://example.com/sitemap.xml");
46
+ const filtered = parser.filterUrls(result, {
47
+ sitemap_url: "https://example.com/sitemap.xml",
48
+ include: ["/services/*"],
49
+ });
50
+ expect(filtered).toHaveLength(3);
51
+ expect(filtered.every((u) => u.includes("/services/"))).toBe(true);
52
+ });
53
+ it("filters URLs by exclude patterns", async () => {
54
+ const parser = new SitemapParser(mockFetcher(SAMPLE_SITEMAP));
55
+ const result = await parser.parse("https://example.com/sitemap.xml");
56
+ const filtered = parser.filterUrls(result, {
57
+ sitemap_url: "https://example.com/sitemap.xml",
58
+ exclude: ["/blog/*"],
59
+ });
60
+ expect(filtered.some((u) => u.includes("/blog/"))).toBe(false);
61
+ expect(filtered.length).toBe(6); // 8 total - 2 blog
62
+ });
63
+ it("combines include and exclude", async () => {
64
+ const parser = new SitemapParser(mockFetcher(SAMPLE_SITEMAP));
65
+ const result = await parser.parse("https://example.com/sitemap.xml");
66
+ const filtered = parser.filterUrls(result, {
67
+ sitemap_url: "https://example.com/sitemap.xml",
68
+ include: ["/services/*", "/blog/*"],
69
+ exclude: ["/blog/*"],
70
+ });
71
+ expect(filtered).toHaveLength(3);
72
+ expect(filtered.every((u) => u.includes("/services/"))).toBe(true);
73
+ });
74
+ });
75
+ //# sourceMappingURL=sitemap-parser.test.js.map