ralph-hero-knowledge-index 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 (47) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/.mcp.json +12 -0
  3. package/dist/db.d.ts +30 -0
  4. package/dist/db.js +73 -0
  5. package/dist/db.js.map +1 -0
  6. package/dist/embedder.d.ts +4 -0
  7. package/dist/embedder.js +24 -0
  8. package/dist/embedder.js.map +1 -0
  9. package/dist/hybrid-search.d.ts +13 -0
  10. package/dist/hybrid-search.js +85 -0
  11. package/dist/hybrid-search.js.map +1 -0
  12. package/dist/index.d.ts +14 -0
  13. package/dist/index.js +64 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/parser.d.ts +18 -0
  16. package/dist/parser.js +39 -0
  17. package/dist/parser.js.map +1 -0
  18. package/dist/reindex.d.ts +1 -0
  19. package/dist/reindex.js +77 -0
  20. package/dist/reindex.js.map +1 -0
  21. package/dist/search.d.ts +23 -0
  22. package/dist/search.js +63 -0
  23. package/dist/search.js.map +1 -0
  24. package/dist/traverse.d.ts +22 -0
  25. package/dist/traverse.js +91 -0
  26. package/dist/traverse.js.map +1 -0
  27. package/dist/vector-search.d.ts +15 -0
  28. package/dist/vector-search.js +52 -0
  29. package/dist/vector-search.js.map +1 -0
  30. package/package.json +27 -0
  31. package/src/__tests__/db.test.ts +51 -0
  32. package/src/__tests__/hybrid-search.test.ts +112 -0
  33. package/src/__tests__/index.test.ts +8 -0
  34. package/src/__tests__/parser.test.ts +100 -0
  35. package/src/__tests__/search.test.ts +92 -0
  36. package/src/__tests__/traverse.test.ts +115 -0
  37. package/src/__tests__/vector-search.test.ts +66 -0
  38. package/src/db.ts +103 -0
  39. package/src/embedder.ts +37 -0
  40. package/src/hybrid-search.ts +102 -0
  41. package/src/index.ts +76 -0
  42. package/src/parser.ts +63 -0
  43. package/src/reindex.ts +89 -0
  44. package/src/search.ts +92 -0
  45. package/src/traverse.ts +130 -0
  46. package/src/vector-search.ts +64 -0
  47. package/tsconfig.json +17 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "ralph-knowledge",
3
+ "version": "0.1.0",
4
+ "description": "Knowledge graph for ralph-hero: semantic search, relationship traversal, and document indexing across thoughts/ documents. Optional companion to ralph-hero.",
5
+ "author": {
6
+ "name": "Chad Dubiel",
7
+ "url": "https://github.com/cdubiel08"
8
+ },
9
+ "homepage": "https://github.com/cdubiel08/ralph-hero",
10
+ "repository": "https://github.com/cdubiel08/ralph-hero",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "knowledge-graph",
14
+ "semantic-search",
15
+ "obsidian",
16
+ "ralph-hero",
17
+ "document-indexing",
18
+ "fts5",
19
+ "embeddings"
20
+ ]
21
+ }
package/.mcp.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "ralph-knowledge": {
4
+ "command": "npx",
5
+ "args": ["-y", "ralph-hero-knowledge-index@0.1.0"],
6
+ "cwd": "${CLAUDE_PLUGIN_ROOT}",
7
+ "env": {
8
+ "RALPH_KNOWLEDGE_DB": "${RALPH_KNOWLEDGE_DB:-knowledge.db}"
9
+ }
10
+ }
11
+ }
12
+ }
package/dist/db.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { Database as DatabaseType } from "better-sqlite3";
2
+ export interface DocumentRow {
3
+ id: string;
4
+ path: string;
5
+ title: string;
6
+ date: string | null;
7
+ type: string | null;
8
+ status: string | null;
9
+ githubIssue: number | null;
10
+ content: string;
11
+ }
12
+ export interface RelationshipRow {
13
+ sourceId: string;
14
+ targetId: string;
15
+ type: string;
16
+ }
17
+ export declare class KnowledgeDB {
18
+ readonly db: DatabaseType;
19
+ constructor(dbPath: string);
20
+ private createSchema;
21
+ upsertDocument(doc: DocumentRow): void;
22
+ getDocument(id: string): DocumentRow | undefined;
23
+ setTags(docId: string, tags: string[]): void;
24
+ getTags(docId: string): string[];
25
+ addRelationship(sourceId: string, targetId: string, type: string): void;
26
+ getRelationshipsFrom(sourceId: string): RelationshipRow[];
27
+ getRelationshipsTo(targetId: string): RelationshipRow[];
28
+ clearAll(): void;
29
+ close(): void;
30
+ }
package/dist/db.js ADDED
@@ -0,0 +1,73 @@
1
+ import Database from "better-sqlite3";
2
+ export class KnowledgeDB {
3
+ db;
4
+ constructor(dbPath) {
5
+ this.db = new Database(dbPath);
6
+ this.db.pragma("journal_mode = WAL");
7
+ this.createSchema();
8
+ }
9
+ createSchema() {
10
+ this.db.exec(`
11
+ CREATE TABLE IF NOT EXISTS documents (
12
+ id TEXT PRIMARY KEY,
13
+ path TEXT NOT NULL,
14
+ title TEXT,
15
+ date TEXT,
16
+ type TEXT,
17
+ status TEXT,
18
+ github_issue INTEGER,
19
+ content TEXT
20
+ );
21
+ CREATE TABLE IF NOT EXISTS tags (
22
+ doc_id TEXT REFERENCES documents(id) ON DELETE CASCADE,
23
+ tag TEXT,
24
+ PRIMARY KEY (doc_id, tag)
25
+ );
26
+ CREATE TABLE IF NOT EXISTS relationships (
27
+ source_id TEXT REFERENCES documents(id) ON DELETE CASCADE,
28
+ target_id TEXT,
29
+ type TEXT CHECK(type IN ('builds_on', 'tensions', 'superseded_by')),
30
+ PRIMARY KEY (source_id, target_id, type)
31
+ );
32
+ CREATE INDEX IF NOT EXISTS idx_rel_target ON relationships(target_id, type);
33
+ CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag);
34
+ `);
35
+ }
36
+ upsertDocument(doc) {
37
+ this.db.prepare(`
38
+ INSERT INTO documents (id, path, title, date, type, status, github_issue, content)
39
+ VALUES (@id, @path, @title, @date, @type, @status, @githubIssue, @content)
40
+ ON CONFLICT(id) DO UPDATE SET
41
+ path = @path, title = @title, date = @date, type = @type,
42
+ status = @status, github_issue = @githubIssue, content = @content
43
+ `).run(doc);
44
+ }
45
+ getDocument(id) {
46
+ return this.db.prepare(`SELECT id, path, title, date, type, status, github_issue AS githubIssue, content FROM documents WHERE id = ?`).get(id);
47
+ }
48
+ setTags(docId, tags) {
49
+ this.db.prepare("DELETE FROM tags WHERE doc_id = ?").run(docId);
50
+ const insert = this.db.prepare("INSERT INTO tags (doc_id, tag) VALUES (?, ?)");
51
+ for (const tag of tags)
52
+ insert.run(docId, tag);
53
+ }
54
+ getTags(docId) {
55
+ return this.db.prepare("SELECT tag FROM tags WHERE doc_id = ? ORDER BY tag").all(docId).map(r => r.tag);
56
+ }
57
+ addRelationship(sourceId, targetId, type) {
58
+ this.db.prepare("INSERT OR IGNORE INTO relationships (source_id, target_id, type) VALUES (?, ?, ?)").run(sourceId, targetId, type);
59
+ }
60
+ getRelationshipsFrom(sourceId) {
61
+ return this.db.prepare("SELECT source_id AS sourceId, target_id AS targetId, type FROM relationships WHERE source_id = ?").all(sourceId);
62
+ }
63
+ getRelationshipsTo(targetId) {
64
+ return this.db.prepare("SELECT source_id AS sourceId, target_id AS targetId, type FROM relationships WHERE target_id = ?").all(targetId);
65
+ }
66
+ clearAll() {
67
+ this.db.exec("DELETE FROM relationships; DELETE FROM tags; DELETE FROM documents;");
68
+ }
69
+ close() {
70
+ this.db.close();
71
+ }
72
+ }
73
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAoBtC,MAAM,OAAO,WAAW;IACb,EAAE,CAAe;IAE1B,YAAY,MAAc;QACxB,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACrC,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;KAwBZ,CAAC,CAAC;IACL,CAAC;IAED,cAAc,CAAC,GAAgB;QAC7B,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;KAMf,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACd,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,8GAA8G,CAC/G,CAAC,GAAG,CAAC,EAAE,CAA4B,CAAC;IACvC,CAAC;IAED,OAAO,CAAC,KAAa,EAAE,IAAc;QACnC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC;QAC/E,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,CAAC,KAAa;QACnB,OAAQ,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,oDAAoD,CAAC,CAAC,GAAG,CAAC,KAAK,CAA4B,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtI,CAAC;IAED,eAAe,CAAC,QAAgB,EAAE,QAAgB,EAAE,IAAY;QAC9D,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,mFAAmF,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IACrI,CAAC;IAED,oBAAoB,CAAC,QAAgB;QACnC,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kGAAkG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAsB,CAAC;IAChK,CAAC;IAED,kBAAkB,CAAC,QAAgB;QACjC,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kGAAkG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAsB,CAAC;IAChK,CAAC;IAED,QAAQ;QACN,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;IACtF,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ import { type FeatureExtractionPipeline } from "@huggingface/transformers";
2
+ export declare function getEmbedder(): Promise<FeatureExtractionPipeline>;
3
+ export declare function embed(text: string): Promise<Float32Array>;
4
+ export declare function prepareTextForEmbedding(title: string, content: string): string;
@@ -0,0 +1,24 @@
1
+ import { pipeline, } from "@huggingface/transformers";
2
+ const MODEL_ID = "Xenova/all-MiniLM-L6-v2";
3
+ const MAX_CHARS = 500;
4
+ let embedderInstance = null;
5
+ export async function getEmbedder() {
6
+ if (!embedderInstance) {
7
+ // @ts-expect-error pipeline() overload union is too complex for TS
8
+ embedderInstance = (await pipeline("feature-extraction", MODEL_ID));
9
+ }
10
+ return embedderInstance;
11
+ }
12
+ export async function embed(text) {
13
+ const embedder = await getEmbedder();
14
+ const truncated = text.slice(0, MAX_CHARS);
15
+ const output = await embedder(truncated, {
16
+ pooling: "mean",
17
+ normalize: true,
18
+ });
19
+ return new Float32Array(output.data);
20
+ }
21
+ export function prepareTextForEmbedding(title, content) {
22
+ return `${title}\n${content}`.slice(0, MAX_CHARS);
23
+ }
24
+ //# sourceMappingURL=embedder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedder.js","sourceRoot":"","sources":["../src/embedder.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,GAET,MAAM,2BAA2B,CAAC;AAEnC,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAC3C,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,IAAI,gBAAgB,GAAqC,IAAI,CAAC;AAE9D,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,mEAAmE;QACnE,gBAAgB,GAAG,CAAC,MAAM,QAAQ,CAChC,oBAAoB,EACpB,QAAQ,CACT,CAA8B,CAAC;IAClC,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAY;IACtC,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE;QACvC,OAAO,EAAE,MAAM;QACf,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;IACH,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC,IAAyB,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,KAAa,EACb,OAAe;IAEf,OAAO,GAAG,KAAK,KAAK,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { KnowledgeDB } from "./db.js";
2
+ import type { FtsSearch, SearchOptions, SearchResult } from "./search.js";
3
+ import type { VectorSearch } from "./vector-search.js";
4
+ export type EmbedFn = (text: string) => Promise<Float32Array>;
5
+ export declare class HybridSearch {
6
+ private readonly db;
7
+ private readonly fts;
8
+ private readonly vec;
9
+ private readonly embedFn;
10
+ private static readonly RRF_K;
11
+ constructor(db: KnowledgeDB, fts: FtsSearch, vec: VectorSearch, embedFn: EmbedFn);
12
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
13
+ }
@@ -0,0 +1,85 @@
1
+ export class HybridSearch {
2
+ db;
3
+ fts;
4
+ vec;
5
+ embedFn;
6
+ static RRF_K = 60;
7
+ constructor(db, fts, vec, embedFn) {
8
+ this.db = db;
9
+ this.fts = fts;
10
+ this.vec = vec;
11
+ this.embedFn = embedFn;
12
+ }
13
+ async search(query, options = {}) {
14
+ const { type, tags, includeSuperseded = false, limit = 20 } = options;
15
+ // Run FTS and vector search
16
+ const ftsResults = this.fts.search(query, {
17
+ includeSuperseded: true,
18
+ limit: limit * 2,
19
+ });
20
+ const queryEmbedding = await this.embedFn(query);
21
+ const vecResults = this.vec.search(queryEmbedding, limit * 2);
22
+ // Build RRF score map
23
+ const scores = new Map();
24
+ for (let i = 0; i < ftsResults.length; i++) {
25
+ const id = ftsResults[i].id;
26
+ const rrfScore = 1 / (HybridSearch.RRF_K + i + 1);
27
+ scores.set(id, (scores.get(id) ?? 0) + rrfScore);
28
+ }
29
+ for (let i = 0; i < vecResults.length; i++) {
30
+ const id = vecResults[i].id;
31
+ const rrfScore = 1 / (HybridSearch.RRF_K + i + 1);
32
+ scores.set(id, (scores.get(id) ?? 0) + rrfScore);
33
+ }
34
+ // Build a lookup of FTS results by id for quick access
35
+ const ftsById = new Map();
36
+ for (const r of ftsResults) {
37
+ ftsById.set(r.id, r);
38
+ }
39
+ // Assemble combined results
40
+ const combined = [];
41
+ for (const [id, rrfScore] of scores) {
42
+ const ftsHit = ftsById.get(id);
43
+ if (ftsHit) {
44
+ combined.push({ ...ftsHit, score: rrfScore });
45
+ }
46
+ else {
47
+ // Vector-only result: fetch document metadata from db
48
+ const doc = this.db.getDocument(id);
49
+ if (!doc)
50
+ continue;
51
+ combined.push({
52
+ id: doc.id,
53
+ path: doc.path,
54
+ title: doc.title,
55
+ type: doc.type,
56
+ status: doc.status,
57
+ date: doc.date,
58
+ score: rrfScore,
59
+ snippet: "",
60
+ });
61
+ }
62
+ }
63
+ // Sort by RRF score descending
64
+ combined.sort((a, b) => b.score - a.score);
65
+ // Post-filter: superseded
66
+ let filtered = combined;
67
+ if (!includeSuperseded) {
68
+ filtered = filtered.filter((r) => r.status !== "superseded");
69
+ }
70
+ // Post-filter: type
71
+ if (type) {
72
+ filtered = filtered.filter((r) => r.type === type);
73
+ }
74
+ // Post-filter: tags
75
+ if (tags && tags.length > 0) {
76
+ const tagSet = new Set(tags);
77
+ filtered = filtered.filter((r) => {
78
+ const docTags = this.db.getTags(r.id);
79
+ return docTags.some((t) => tagSet.has(t));
80
+ });
81
+ }
82
+ return filtered.slice(0, limit);
83
+ }
84
+ }
85
+ //# sourceMappingURL=hybrid-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybrid-search.js","sourceRoot":"","sources":["../src/hybrid-search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { KnowledgeDB } from "./db.js";
3
+ import { FtsSearch } from "./search.js";
4
+ import { VectorSearch } from "./vector-search.js";
5
+ import { HybridSearch } from "./hybrid-search.js";
6
+ import { Traverser } from "./traverse.js";
7
+ export declare function createServer(dbPath: string): {
8
+ server: McpServer;
9
+ db: KnowledgeDB;
10
+ fts: FtsSearch;
11
+ vec: VectorSearch;
12
+ hybrid: HybridSearch;
13
+ traverser: Traverser;
14
+ };
package/dist/index.js ADDED
@@ -0,0 +1,64 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { KnowledgeDB } from "./db.js";
5
+ import { FtsSearch } from "./search.js";
6
+ import { VectorSearch } from "./vector-search.js";
7
+ import { HybridSearch } from "./hybrid-search.js";
8
+ import { Traverser } from "./traverse.js";
9
+ import { embed } from "./embedder.js";
10
+ export function createServer(dbPath) {
11
+ const server = new McpServer({ name: "ralph-hero-knowledge", version: "0.1.0" });
12
+ const db = new KnowledgeDB(dbPath);
13
+ const fts = new FtsSearch(db);
14
+ const vec = new VectorSearch(db);
15
+ const hybrid = new HybridSearch(db, fts, vec, embed);
16
+ const traverser = new Traverser(db);
17
+ server.tool("knowledge_search", "Search the knowledge base by keyword, semantic similarity, and tags. Returns ranked documents.", {
18
+ query: z.string().describe("Search query (keywords or natural language)"),
19
+ tags: z.array(z.string()).optional().describe("Filter by tags"),
20
+ type: z.string().optional().describe("Filter by document type (research, plan, review, idea, report)"),
21
+ limit: z.number().optional().describe("Max results (default: 10)"),
22
+ includeSuperseded: z.boolean().optional().describe("Include superseded documents (default: false)"),
23
+ }, async (args) => {
24
+ try {
25
+ const results = await hybrid.search(args.query, {
26
+ tags: args.tags,
27
+ type: args.type,
28
+ limit: args.limit ?? 10,
29
+ includeSuperseded: args.includeSuperseded,
30
+ });
31
+ const enriched = results.map(r => ({ ...r, tags: db.getTags(r.id) }));
32
+ return { content: [{ type: "text", text: JSON.stringify(enriched, null, 2) }] };
33
+ }
34
+ catch (e) {
35
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
36
+ }
37
+ });
38
+ server.tool("knowledge_traverse", "Walk typed relationship edges (builds_on, tensions, superseded_by) from a document.", {
39
+ from: z.string().describe("Document ID (filename without extension)"),
40
+ type: z.enum(["builds_on", "tensions", "superseded_by"]).optional().describe("Filter by relationship type"),
41
+ depth: z.number().optional().describe("Max traversal depth (default: 3)"),
42
+ direction: z.enum(["outgoing", "incoming"]).optional().describe("Edge direction (default: outgoing)"),
43
+ }, async (args) => {
44
+ try {
45
+ const opts = { type: args.type, depth: args.depth ?? 3 };
46
+ const results = args.direction === "incoming"
47
+ ? traverser.traverseIncoming(args.from, opts)
48
+ : traverser.traverse(args.from, opts);
49
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
50
+ }
51
+ catch (e) {
52
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
53
+ }
54
+ });
55
+ return { server, db, fts, vec, hybrid, traverser };
56
+ }
57
+ const isMain = process.argv[1]?.endsWith("index.js");
58
+ if (isMain) {
59
+ const dbPath = process.env.RALPH_KNOWLEDGE_DB ?? "knowledge.db";
60
+ const { server } = createServer(dbPath);
61
+ const transport = new StdioServerTransport();
62
+ server.connect(transport).catch(console.error);
63
+ }
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACjF,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC;IAEpC,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,gGAAgG,EAChG;QACE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;QACzE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAC/D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gEAAgE,CAAC;QACtG,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;QAClE,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;KACpG,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE;gBAC9C,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;gBACvB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;aAC1C,CAAC,CAAC;YACH,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACtE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAC3F,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAW,CAAW,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACzG,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,qFAAqF,EACrF;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;QACrE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;QAC3G,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QACzE,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;KACtG,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,KAAK,UAAU;gBAC3C,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;gBAC7C,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACxC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAC1F,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAW,CAAW,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACzG,CAAC;IACH,CAAC,CACF,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACrD,CAAC;AAED,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;AACrD,IAAI,MAAM,EAAE,CAAC;IACX,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,cAAc,CAAC;IAChE,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,18 @@
1
+ export interface Relationship {
2
+ sourceId: string;
3
+ targetId: string;
4
+ type: "builds_on" | "tensions" | "superseded_by";
5
+ }
6
+ export interface ParsedDocument {
7
+ id: string;
8
+ path: string;
9
+ title: string;
10
+ date: string | null;
11
+ type: string | null;
12
+ status: string | null;
13
+ githubIssue: number | null;
14
+ tags: string[];
15
+ relationships: Relationship[];
16
+ content: string;
17
+ }
18
+ export declare function parseDocument(id: string, path: string, raw: string): ParsedDocument;
package/dist/parser.js ADDED
@@ -0,0 +1,39 @@
1
+ import { parse as parseYaml } from "yaml";
2
+ const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/;
3
+ const TITLE_RE = /^# (.+)$/m;
4
+ const WIKILINK_REL_RE = /^- (builds_on|tensions):: \[\[(.+?)\]\]/gm;
5
+ const SUPERSEDED_BY_RE = /\[\[(.+?)\]\]/;
6
+ export function parseDocument(id, path, raw) {
7
+ const fmMatch = raw.match(FRONTMATTER_RE);
8
+ const frontmatter = fmMatch ? parseYaml(fmMatch[1]) ?? {} : {};
9
+ const body = fmMatch ? raw.slice(fmMatch[0].length).trim() : raw.trim();
10
+ const titleMatch = body.match(TITLE_RE);
11
+ const title = titleMatch ? titleMatch[1].trim() : id;
12
+ const relationships = [];
13
+ let match;
14
+ const relRe = new RegExp(WIKILINK_REL_RE.source, "gm");
15
+ while ((match = relRe.exec(body)) !== null) {
16
+ relationships.push({
17
+ sourceId: id,
18
+ targetId: match[2],
19
+ type: match[1],
20
+ });
21
+ }
22
+ const supersededBy = frontmatter.superseded_by;
23
+ if (typeof supersededBy === "string") {
24
+ const wlMatch = supersededBy.match(SUPERSEDED_BY_RE);
25
+ if (wlMatch) {
26
+ relationships.push({ sourceId: id, targetId: wlMatch[1], type: "superseded_by" });
27
+ }
28
+ }
29
+ const tags = Array.isArray(frontmatter.tags) ? frontmatter.tags.map(String) : [];
30
+ return {
31
+ id, path, title,
32
+ date: frontmatter.date ? String(frontmatter.date) : null,
33
+ type: frontmatter.type ?? null,
34
+ status: frontmatter.status ?? null,
35
+ githubIssue: typeof frontmatter.github_issue === "number" ? frontmatter.github_issue : null,
36
+ tags, relationships, content: body,
37
+ };
38
+ }
39
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAqB1C,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAC/C,MAAM,QAAQ,GAAG,WAAW,CAAC;AAC7B,MAAM,eAAe,GAAG,2CAA2C,CAAC;AACpE,MAAM,gBAAgB,GAAG,eAAe,CAAC;AAEzC,MAAM,UAAU,aAAa,CAAC,EAAU,EAAE,IAAY,EAAE,GAAW;IACjE,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IACxE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAErD,MAAM,aAAa,GAAmB,EAAE,CAAC;IACzC,IAAI,KAA6B,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACvD,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,aAAa,CAAC,IAAI,CAAC;YACjB,QAAQ,EAAE,EAAE;YACZ,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;YAClB,IAAI,EAAE,KAAK,CAAC,CAAC,CAA6B;SAC3C,CAAC,CAAC;IACL,CAAC;IAED,MAAM,YAAY,GAAG,WAAW,CAAC,aAAa,CAAC;IAC/C,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACrD,IAAI,OAAO,EAAE,CAAC;YACZ,aAAa,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAa,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE3F,OAAO;QACL,EAAE,EAAE,IAAI,EAAE,KAAK;QACf,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;QACxD,IAAI,EAAE,WAAW,CAAC,IAAI,IAAI,IAAI;QAC9B,MAAM,EAAE,WAAW,CAAC,MAAM,IAAI,IAAI;QAClC,WAAW,EAAE,OAAO,WAAW,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI;QAC3F,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI;KACnC,CAAC;AACJ,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { join, relative, basename } from "node:path";
3
+ import { KnowledgeDB } from "./db.js";
4
+ import { FtsSearch } from "./search.js";
5
+ import { VectorSearch } from "./vector-search.js";
6
+ import { embed, prepareTextForEmbedding } from "./embedder.js";
7
+ import { parseDocument } from "./parser.js";
8
+ function findMarkdownFiles(dir) {
9
+ const results = [];
10
+ function walk(d) {
11
+ for (const entry of readdirSync(d, { withFileTypes: true })) {
12
+ const fullPath = join(d, entry.name);
13
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
14
+ walk(fullPath);
15
+ }
16
+ else if (entry.isFile() && entry.name.endsWith(".md")) {
17
+ results.push(fullPath);
18
+ }
19
+ }
20
+ }
21
+ walk(dir);
22
+ return results;
23
+ }
24
+ async function reindex(thoughtsDir, dbPath) {
25
+ console.log(`Indexing ${thoughtsDir} -> ${dbPath}`);
26
+ const db = new KnowledgeDB(dbPath);
27
+ const fts = new FtsSearch(db);
28
+ const vec = new VectorSearch(db);
29
+ vec.createIndex();
30
+ db.clearAll();
31
+ vec.dropIndex();
32
+ vec.createIndex();
33
+ const files = findMarkdownFiles(thoughtsDir);
34
+ console.log(`Found ${files.length} markdown files`);
35
+ let indexed = 0;
36
+ for (const filePath of files) {
37
+ const raw = readFileSync(filePath, "utf-8");
38
+ const relPath = relative(join(thoughtsDir, ".."), filePath);
39
+ const id = basename(filePath, ".md");
40
+ const parsed = parseDocument(id, relPath, raw);
41
+ db.upsertDocument({
42
+ id: parsed.id,
43
+ path: parsed.path,
44
+ title: parsed.title,
45
+ date: parsed.date,
46
+ type: parsed.type,
47
+ status: parsed.status,
48
+ githubIssue: parsed.githubIssue,
49
+ content: parsed.content,
50
+ });
51
+ if (parsed.tags.length > 0) {
52
+ db.setTags(parsed.id, parsed.tags);
53
+ }
54
+ for (const rel of parsed.relationships) {
55
+ db.addRelationship(rel.sourceId, rel.targetId, rel.type);
56
+ }
57
+ const text = prepareTextForEmbedding(parsed.title, parsed.content);
58
+ try {
59
+ const embedding = await embed(text);
60
+ vec.upsertEmbedding(parsed.id, embedding);
61
+ }
62
+ catch (e) {
63
+ console.warn(`Failed to embed ${id}: ${e.message}`);
64
+ }
65
+ indexed++;
66
+ if (indexed % 50 === 0) {
67
+ console.log(` ${indexed}/${files.length} indexed`);
68
+ }
69
+ }
70
+ fts.rebuildIndex();
71
+ console.log(`Done. ${indexed} documents indexed.`);
72
+ db.close();
73
+ }
74
+ const thoughtsDir = process.argv[2] ?? "../../thoughts";
75
+ const dbPath = process.argv[3] ?? "knowledge.db";
76
+ reindex(thoughtsDir, dbPath).catch(console.error);
77
+ //# sourceMappingURL=reindex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reindex.js","sourceRoot":"","sources":["../src/reindex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { KnowledgeDB } from "./db.js";
2
+ export interface SearchOptions {
3
+ type?: string;
4
+ tags?: string[];
5
+ includeSuperseded?: boolean;
6
+ limit?: number;
7
+ }
8
+ export interface SearchResult {
9
+ id: string;
10
+ path: string;
11
+ title: string;
12
+ type: string | null;
13
+ status: string | null;
14
+ date: string | null;
15
+ score: number;
16
+ snippet: string;
17
+ }
18
+ export declare class FtsSearch {
19
+ private readonly db;
20
+ constructor(db: KnowledgeDB);
21
+ rebuildIndex(): void;
22
+ search(query: string, options?: SearchOptions): SearchResult[];
23
+ }
package/dist/search.js ADDED
@@ -0,0 +1,63 @@
1
+ export class FtsSearch {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ rebuildIndex() {
7
+ this.db.db.exec(`DROP TABLE IF EXISTS documents_fts`);
8
+ this.db.db.exec(`
9
+ CREATE VIRTUAL TABLE documents_fts USING fts5(
10
+ title,
11
+ path,
12
+ content,
13
+ content='documents',
14
+ content_rowid='rowid'
15
+ )
16
+ `);
17
+ this.db.db.exec(`
18
+ INSERT INTO documents_fts(rowid, title, path, content)
19
+ SELECT rowid, title, path, content FROM documents
20
+ `);
21
+ }
22
+ search(query, options = {}) {
23
+ const { type, tags, includeSuperseded = false, limit = 20 } = options;
24
+ const conditions = ["documents_fts MATCH @query"];
25
+ const params = { query, limit };
26
+ if (!includeSuperseded) {
27
+ conditions.push("d.status IS NOT 'superseded'");
28
+ }
29
+ if (type) {
30
+ conditions.push("d.type = @type");
31
+ params.type = type;
32
+ }
33
+ let joinClause = "";
34
+ if (tags && tags.length > 0) {
35
+ joinClause = "JOIN tags t ON t.doc_id = d.id";
36
+ const tagPlaceholders = tags.map((_, i) => `@tag${i}`);
37
+ conditions.push(`t.tag IN (${tagPlaceholders.join(", ")})`);
38
+ tags.forEach((tag, i) => {
39
+ params[`tag${i}`] = tag;
40
+ });
41
+ }
42
+ const whereClause = conditions.join(" AND ");
43
+ const sql = `
44
+ SELECT
45
+ d.id,
46
+ d.path,
47
+ d.title,
48
+ d.type,
49
+ d.status,
50
+ d.date,
51
+ rank AS score,
52
+ snippet(documents_fts, 2, '<b>', '</b>', '...', 32) AS snippet
53
+ FROM documents_fts
54
+ JOIN documents d ON d.rowid = documents_fts.rowid
55
+ ${joinClause}
56
+ WHERE ${whereClause}
57
+ ORDER BY rank ASC
58
+ LIMIT @limit
59
+ `;
60
+ return this.db.db.prepare(sql).all(params);
61
+ }
62
+ }
63
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAoBA,MAAM,OAAO,SAAS;IACH,EAAE,CAAc;IAEjC,YAAY,EAAe;QACzB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED,YAAY;QACV,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QACtD,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC;;;;;;;;KAQf,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC;;;KAGf,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,KAAa,EAAE,UAAyB,EAAE;QAC/C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,GAAG,KAAK,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;QAEtE,MAAM,UAAU,GAAa,CAAC,4BAA4B,CAAC,CAAC;QAC5D,MAAM,MAAM,GAA4B,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAEzD,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACT,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAClC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;QACrB,CAAC;QAED,IAAI,UAAU,GAAG,EAAE,CAAC;QACpB,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,UAAU,GAAG,gCAAgC,CAAC;YAC9C,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACvD,UAAU,CAAC,IAAI,CAAC,aAAa,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5D,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;gBACtB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE7C,MAAM,GAAG,GAAG;;;;;;;;;;;;QAYR,UAAU;cACJ,WAAW;;;KAGpB,CAAC;QAEF,OAAO,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAmB,CAAC;IAC/D,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ import type { KnowledgeDB } from "./db.js";
2
+ export interface TraverseOptions {
3
+ type?: string;
4
+ depth?: number;
5
+ }
6
+ export interface TraverseResult {
7
+ sourceId: string;
8
+ targetId: string;
9
+ type: string;
10
+ depth: number;
11
+ doc: {
12
+ title: string;
13
+ status: string | null;
14
+ date: string | null;
15
+ } | null;
16
+ }
17
+ export declare class Traverser {
18
+ private readonly db;
19
+ constructor(db: KnowledgeDB);
20
+ traverse(fromId: string, options?: TraverseOptions): TraverseResult[];
21
+ traverseIncoming(toId: string, options?: TraverseOptions): TraverseResult[];
22
+ }