@weavelogic/knowledge-graph-agent 0.8.2 → 0.8.3

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.
@@ -54,6 +54,10 @@ export declare class KnowledgeGraphDatabase {
54
54
  * Delete node
55
55
  */
56
56
  deleteNode(id: string): boolean;
57
+ /**
58
+ * Clear all nodes and edges (for full regeneration)
59
+ */
60
+ clearAll(): void;
57
61
  /**
58
62
  * Update tags for a node
59
63
  */
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/core/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACV,QAAQ,EACR,UAAU,EACX,MAAM,YAAY,CAAC;AAuGpB;;GAEG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAuB1B;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAgCrC;;OAEG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAOzC;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAOjD;;OAEG;IACH,WAAW,IAAI,aAAa,EAAE;IAM9B;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,aAAa,EAAE;IAM/C;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,EAAE;IAMrD;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,EAAE;IAY3C;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,aAAa,EAAE;IAsBvD;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAU/B;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAWrC;;OAEG;IACH,UAAU,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAepD;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAQ9B;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAM7C;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAM7C;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQrC;;OAEG;IACH,QAAQ,IAAI,UAAU;IA2DtB;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMvC;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAY7C;;OAEG;IACH,OAAO,CAAC,SAAS;IA8BjB;;OAEG;IACH,OAAO,CAAC,SAAS;IAUjB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,WAAW,IAAI,QAAQ,CAAC,QAAQ;CAGjC;AA2BD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB,CAErE"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/core/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACV,QAAQ,EACR,UAAU,EACX,MAAM,YAAY,CAAC;AAuGpB;;GAEG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAuB1B;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAgCrC;;OAEG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAOzC;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAOjD;;OAEG;IACH,WAAW,IAAI,aAAa,EAAE;IAM9B;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,aAAa,EAAE;IAM/C;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,EAAE;IAMrD;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,EAAE;IAY3C;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,aAAa,EAAE;IAsBvD;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAM/B;;OAEG;IACH,QAAQ,IAAI,IAAI;IAUhB;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAWrC;;OAEG;IACH,UAAU,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAepD;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAQ9B;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAM7C;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAM7C;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQrC;;OAEG;IACH,QAAQ,IAAI,UAAU;IA2DtB;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMvC;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAY7C;;OAEG;IACH,OAAO,CAAC,SAAS;IA8BjB;;OAEG;IACH,OAAO,CAAC,SAAS;IAUjB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,WAAW,IAAI,QAAQ,CAAC,QAAQ;CAGjC;AA2BD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB,CAErE"}
@@ -237,6 +237,14 @@ class KnowledgeGraphDatabase {
237
237
  const result = stmt.run(id);
238
238
  return result.changes > 0;
239
239
  }
240
+ /**
241
+ * Clear all nodes and edges (for full regeneration)
242
+ */
243
+ clearAll() {
244
+ this.db.exec("DELETE FROM edges");
245
+ this.db.exec("DELETE FROM node_tags");
246
+ this.db.exec("DELETE FROM nodes");
247
+ }
240
248
  // ========================================================================
241
249
  // Tag Operations
242
250
  // ========================================================================
@@ -1 +1 @@
1
- {"version":3,"file":"database.js","sources":["../../src/core/database.ts"],"sourcesContent":["/**\n * Knowledge Graph Database\n *\n * SQLite database for persistent storage of knowledge graph data.\n * Compatible with claude-flow database patterns.\n */\n\nimport Database from 'better-sqlite3';\nimport { existsSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type {\n KnowledgeNode,\n GraphEdge,\n GraphStats,\n NodeType,\n NodeStatus,\n} from './types.js';\n\n/**\n * Safely parse JSON with fallback\n * Prevents error leakage from malformed JSON\n */\nfunction safeJsonParse<T>(str: string | null | undefined, fallback: T): T {\n if (!str) return fallback;\n try {\n return JSON.parse(str) as T;\n } catch {\n return fallback;\n }\n}\n\nconst SCHEMA_SQL = `\n-- Knowledge Graph Schema v1.0\n\n-- Nodes table\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n path TEXT NOT NULL UNIQUE,\n filename TEXT NOT NULL,\n title TEXT NOT NULL,\n type TEXT NOT NULL CHECK(type IN ('concept', 'technical', 'feature', 'primitive', 'service', 'guide', 'standard', 'integration')),\n status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('draft', 'active', 'deprecated', 'archived')),\n content TEXT,\n frontmatter TEXT,\n word_count INTEGER DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Tags table\nCREATE TABLE IF NOT EXISTS tags (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL UNIQUE\n);\n\n-- Node-Tags junction table\nCREATE TABLE IF NOT EXISTS node_tags (\n node_id TEXT NOT NULL,\n tag_id INTEGER NOT NULL,\n PRIMARY KEY (node_id, tag_id),\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE,\n FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE\n);\n\n-- Edges table\nCREATE TABLE IF NOT EXISTS edges (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n type TEXT NOT NULL CHECK(type IN ('link', 'reference', 'parent', 'related')),\n weight REAL DEFAULT 1.0,\n context TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (source_id) REFERENCES nodes(id) ON DELETE CASCADE\n);\n\n-- Metadata table\nCREATE TABLE IF NOT EXISTS metadata (\n key TEXT PRIMARY KEY,\n value TEXT,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Indexes for performance\nCREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);\nCREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);\nCREATE INDEX IF NOT EXISTS idx_nodes_path ON nodes(path);\nCREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);\nCREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);\nCREATE INDEX IF NOT EXISTS idx_node_tags_node ON node_tags(node_id);\nCREATE INDEX IF NOT EXISTS idx_node_tags_tag ON node_tags(tag_id);\n\n-- Full-text search\nCREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(\n title,\n content,\n content='nodes',\n content_rowid='rowid'\n);\n\n-- Triggers for FTS sync\nCREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN\n INSERT INTO nodes_fts(rowid, title, content) VALUES (NEW.rowid, NEW.title, NEW.content);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN\n INSERT INTO nodes_fts(nodes_fts, rowid, title, content) VALUES('delete', OLD.rowid, OLD.title, OLD.content);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN\n INSERT INTO nodes_fts(nodes_fts, rowid, title, content) VALUES('delete', OLD.rowid, OLD.title, OLD.content);\n INSERT INTO nodes_fts(rowid, title, content) VALUES (NEW.rowid, NEW.title, NEW.content);\nEND;\n\n-- Initialize metadata\nINSERT OR IGNORE INTO metadata (key, value) VALUES ('version', '1.0.0');\nINSERT OR IGNORE INTO metadata (key, value) VALUES ('created', datetime('now'));\n`;\n\n/**\n * Knowledge Graph Database\n */\nexport class KnowledgeGraphDatabase {\n private db: Database.Database;\n private dbPath: string;\n\n constructor(dbPath: string) {\n this.dbPath = dbPath;\n\n // Ensure directory exists\n const dir = dirname(dbPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Open database\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('busy_timeout = 5000');\n\n // Initialize schema\n this.db.exec(SCHEMA_SQL);\n }\n\n // ========================================================================\n // Node Operations\n // ========================================================================\n\n /**\n * Insert or update a node\n */\n upsertNode(node: KnowledgeNode): void {\n const stmt = this.db.prepare(`\n INSERT INTO nodes (id, path, filename, title, type, status, content, frontmatter, word_count, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))\n ON CONFLICT(id) DO UPDATE SET\n path = excluded.path,\n filename = excluded.filename,\n title = excluded.title,\n type = excluded.type,\n status = excluded.status,\n content = excluded.content,\n frontmatter = excluded.frontmatter,\n word_count = excluded.word_count,\n updated_at = datetime('now')\n `);\n\n stmt.run(\n node.id,\n node.path,\n node.filename,\n node.title,\n node.type,\n node.status,\n node.content,\n JSON.stringify(node.frontmatter),\n node.wordCount\n );\n\n // Update tags\n this.updateNodeTags(node.id, node.tags);\n }\n\n /**\n * Get node by ID\n */\n getNode(id: string): KnowledgeNode | null {\n const stmt = this.db.prepare('SELECT * FROM nodes WHERE id = ?');\n const row = stmt.get(id) as NodeRow | undefined;\n if (!row) return null;\n return this.rowToNode(row);\n }\n\n /**\n * Get node by path\n */\n getNodeByPath(path: string): KnowledgeNode | null {\n const stmt = this.db.prepare('SELECT * FROM nodes WHERE path = ?');\n const row = stmt.get(path) as NodeRow | undefined;\n if (!row) return null;\n return this.rowToNode(row);\n }\n\n /**\n * Get all nodes\n */\n getAllNodes(): KnowledgeNode[] {\n const stmt = this.db.prepare('SELECT * FROM nodes ORDER BY title');\n const rows = stmt.all() as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Get nodes by type\n */\n getNodesByType(type: NodeType): KnowledgeNode[] {\n const stmt = this.db.prepare('SELECT * FROM nodes WHERE type = ? ORDER BY title');\n const rows = stmt.all(type) as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Get nodes by status\n */\n getNodesByStatus(status: NodeStatus): KnowledgeNode[] {\n const stmt = this.db.prepare('SELECT * FROM nodes WHERE status = ? ORDER BY title');\n const rows = stmt.all(status) as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Get nodes by tag\n */\n getNodesByTag(tag: string): KnowledgeNode[] {\n const stmt = this.db.prepare(`\n SELECT n.* FROM nodes n\n JOIN node_tags nt ON n.id = nt.node_id\n JOIN tags t ON nt.tag_id = t.id\n WHERE t.name = ?\n ORDER BY n.title\n `);\n const rows = stmt.all(tag) as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Sanitize FTS5 query to prevent query injection\n * Escapes special FTS5 operators and quotes terms\n */\n private sanitizeFtsQuery(query: string): string {\n if (!query || typeof query !== 'string') return '';\n\n // Remove FTS5 special operators: * \" ( ) : ^ - AND OR NOT NEAR\n const sanitized = query\n .replace(/[*\"():^\\-]/g, ' ') // Remove special chars\n .replace(/\\b(AND|OR|NOT|NEAR)\\b/gi, '') // Remove boolean operators\n .trim()\n .split(/\\s+/)\n .filter(term => term.length > 0 && term.length < 100) // Limit term length\n .slice(0, 20) // Limit number of terms\n .map(term => `\"${term.replace(/\"/g, '')}\"`) // Quote each term safely\n .join(' ');\n\n return sanitized;\n }\n\n /**\n * Search nodes by title or content\n */\n searchNodes(query: string, limit = 50): KnowledgeNode[] {\n const sanitizedQuery = this.sanitizeFtsQuery(query);\n\n // Return empty if no valid search terms\n if (!sanitizedQuery) {\n return [];\n }\n\n // Enforce reasonable limit\n const safeLimit = Math.min(Math.max(1, limit), 100);\n\n const stmt = this.db.prepare(`\n SELECT n.* FROM nodes n\n JOIN nodes_fts fts ON n.rowid = fts.rowid\n WHERE nodes_fts MATCH ?\n ORDER BY rank\n LIMIT ?\n `);\n const rows = stmt.all(sanitizedQuery, safeLimit) as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Delete node\n */\n deleteNode(id: string): boolean {\n const stmt = this.db.prepare('DELETE FROM nodes WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n\n // ========================================================================\n // Tag Operations\n // ========================================================================\n\n /**\n * Update tags for a node\n */\n private updateNodeTags(nodeId: string, tags: string[]): void {\n // Remove existing tags\n this.db.prepare('DELETE FROM node_tags WHERE node_id = ?').run(nodeId);\n\n // Insert new tags\n const getOrCreateTag = this.db.prepare(`\n INSERT INTO tags (name) VALUES (?)\n ON CONFLICT(name) DO UPDATE SET name = excluded.name\n RETURNING id\n `);\n\n const insertNodeTag = this.db.prepare(\n 'INSERT OR IGNORE INTO node_tags (node_id, tag_id) VALUES (?, ?)'\n );\n\n for (const tag of tags) {\n const result = getOrCreateTag.get(tag) as { id: number };\n insertNodeTag.run(nodeId, result.id);\n }\n }\n\n /**\n * Get tags for a node\n */\n getNodeTags(nodeId: string): string[] {\n const stmt = this.db.prepare(`\n SELECT t.name FROM tags t\n JOIN node_tags nt ON t.id = nt.tag_id\n WHERE nt.node_id = ?\n ORDER BY t.name\n `);\n const rows = stmt.all(nodeId) as Array<{ name: string }>;\n return rows.map(r => r.name);\n }\n\n /**\n * Get all tags with counts\n */\n getAllTags(): Array<{ name: string; count: number }> {\n const stmt = this.db.prepare(`\n SELECT t.name, COUNT(nt.node_id) as count\n FROM tags t\n LEFT JOIN node_tags nt ON t.id = nt.tag_id\n GROUP BY t.id\n ORDER BY count DESC, t.name\n `);\n return stmt.all() as Array<{ name: string; count: number }>;\n }\n\n // ========================================================================\n // Edge Operations\n // ========================================================================\n\n /**\n * Add edge\n */\n addEdge(edge: GraphEdge): void {\n const stmt = this.db.prepare(`\n INSERT INTO edges (source_id, target_id, type, weight, context)\n VALUES (?, ?, ?, ?, ?)\n `);\n stmt.run(edge.source, edge.target, edge.type, edge.weight, edge.context);\n }\n\n /**\n * Get outgoing edges for a node\n */\n getOutgoingEdges(nodeId: string): GraphEdge[] {\n const stmt = this.db.prepare('SELECT * FROM edges WHERE source_id = ?');\n const rows = stmt.all(nodeId) as EdgeRow[];\n return rows.map(row => this.rowToEdge(row));\n }\n\n /**\n * Get incoming edges for a node\n */\n getIncomingEdges(nodeId: string): GraphEdge[] {\n const stmt = this.db.prepare('SELECT * FROM edges WHERE target_id = ?');\n const rows = stmt.all(nodeId) as EdgeRow[];\n return rows.map(row => this.rowToEdge(row));\n }\n\n /**\n * Delete edges for a node\n */\n deleteNodeEdges(nodeId: string): void {\n this.db.prepare('DELETE FROM edges WHERE source_id = ?').run(nodeId);\n }\n\n // ========================================================================\n // Statistics\n // ========================================================================\n\n /**\n * Get graph statistics\n */\n getStats(): GraphStats {\n const totalNodes = (this.db.prepare('SELECT COUNT(*) as count FROM nodes').get() as { count: number }).count;\n const totalEdges = (this.db.prepare('SELECT COUNT(*) as count FROM edges').get() as { count: number }).count;\n\n const typeStats = this.db.prepare(`\n SELECT type, COUNT(*) as count FROM nodes GROUP BY type\n `).all() as Array<{ type: NodeType; count: number }>;\n\n const statusStats = this.db.prepare(`\n SELECT status, COUNT(*) as count FROM nodes GROUP BY status\n `).all() as Array<{ status: NodeStatus; count: number }>;\n\n const nodesByType: Record<NodeType, number> = {\n concept: 0, technical: 0, feature: 0, primitive: 0,\n service: 0, guide: 0, standard: 0, integration: 0,\n };\n for (const { type, count } of typeStats) {\n nodesByType[type] = count;\n }\n\n const nodesByStatus: Record<NodeStatus, number> = {\n draft: 0, active: 0, deprecated: 0, archived: 0,\n };\n for (const { status, count } of statusStats) {\n nodesByStatus[status] = count;\n }\n\n const orphanNodes = (this.db.prepare(`\n SELECT COUNT(*) as count FROM nodes n\n WHERE NOT EXISTS (SELECT 1 FROM edges e WHERE e.source_id = n.id OR e.target_id = n.id)\n `).get() as { count: number }).count;\n\n const avgLinksPerNode = totalNodes > 0 ? totalEdges / totalNodes : 0;\n\n const mostConnected = this.db.prepare(`\n SELECT n.id, (\n (SELECT COUNT(*) FROM edges WHERE source_id = n.id) +\n (SELECT COUNT(*) FROM edges WHERE target_id = n.id)\n ) as connections\n FROM nodes n\n ORDER BY connections DESC\n LIMIT 5\n `).all() as Array<{ id: string; connections: number }>;\n\n return {\n totalNodes,\n totalEdges,\n nodesByType,\n nodesByStatus,\n orphanNodes,\n avgLinksPerNode: Math.round(avgLinksPerNode * 100) / 100,\n mostConnected,\n };\n }\n\n // ========================================================================\n // Metadata Operations\n // ========================================================================\n\n /**\n * Get metadata value\n */\n getMetadata(key: string): string | null {\n const stmt = this.db.prepare('SELECT value FROM metadata WHERE key = ?');\n const row = stmt.get(key) as { value: string } | undefined;\n return row?.value ?? null;\n }\n\n /**\n * Set metadata value\n */\n setMetadata(key: string, value: string): void {\n const stmt = this.db.prepare(`\n INSERT INTO metadata (key, value, updated_at) VALUES (?, ?, datetime('now'))\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = datetime('now')\n `);\n stmt.run(key, value);\n }\n\n // ========================================================================\n // Utilities\n // ========================================================================\n\n /**\n * Convert database row to KnowledgeNode\n */\n private rowToNode(row: NodeRow): KnowledgeNode {\n const tags = this.getNodeTags(row.id);\n const outgoingEdges = this.getOutgoingEdges(row.id);\n const incomingEdges = this.getIncomingEdges(row.id);\n\n return {\n id: row.id,\n path: row.path,\n filename: row.filename,\n title: row.title,\n type: row.type as NodeType,\n status: row.status as NodeStatus,\n content: row.content || '',\n frontmatter: safeJsonParse(row.frontmatter, {}),\n tags,\n outgoingLinks: outgoingEdges.map(e => ({\n target: e.target,\n type: 'wikilink' as const,\n context: e.context,\n })),\n incomingLinks: incomingEdges.map(e => ({\n target: e.source,\n type: 'backlink' as const,\n context: e.context,\n })),\n wordCount: row.word_count,\n lastModified: new Date(row.updated_at),\n };\n }\n\n /**\n * Convert database row to GraphEdge\n */\n private rowToEdge(row: EdgeRow): GraphEdge {\n return {\n source: row.source_id,\n target: row.target_id,\n type: row.type as GraphEdge['type'],\n weight: row.weight,\n context: row.context ?? undefined,\n };\n }\n\n /**\n * Close database connection\n */\n close(): void {\n this.db.close();\n }\n\n /**\n * Get raw database instance\n */\n getDatabase(): Database.Database {\n return this.db;\n }\n}\n\n// Row types for database queries\ninterface NodeRow {\n id: string;\n path: string;\n filename: string;\n title: string;\n type: string;\n status: string;\n content: string | null;\n frontmatter: string | null;\n word_count: number;\n created_at: string;\n updated_at: string;\n}\n\ninterface EdgeRow {\n id: number;\n source_id: string;\n target_id: string;\n type: string;\n weight: number;\n context: string | null;\n created_at: string;\n}\n\n/**\n * Create knowledge graph database instance\n */\nexport function createDatabase(dbPath: string): KnowledgeGraphDatabase {\n return new KnowledgeGraphDatabase(dbPath);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"database.js","sources":["../../src/core/database.ts"],"sourcesContent":["/**\n * Knowledge Graph Database\n *\n * SQLite database for persistent storage of knowledge graph data.\n * Compatible with claude-flow database patterns.\n */\n\nimport Database from 'better-sqlite3';\nimport { existsSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type {\n KnowledgeNode,\n GraphEdge,\n GraphStats,\n NodeType,\n NodeStatus,\n} from './types.js';\n\n/**\n * Safely parse JSON with fallback\n * Prevents error leakage from malformed JSON\n */\nfunction safeJsonParse<T>(str: string | null | undefined, fallback: T): T {\n if (!str) return fallback;\n try {\n return JSON.parse(str) as T;\n } catch {\n return fallback;\n }\n}\n\nconst SCHEMA_SQL = `\n-- Knowledge Graph Schema v1.0\n\n-- Nodes table\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n path TEXT NOT NULL UNIQUE,\n filename TEXT NOT NULL,\n title TEXT NOT NULL,\n type TEXT NOT NULL CHECK(type IN ('concept', 'technical', 'feature', 'primitive', 'service', 'guide', 'standard', 'integration')),\n status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('draft', 'active', 'deprecated', 'archived')),\n content TEXT,\n frontmatter TEXT,\n word_count INTEGER DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Tags table\nCREATE TABLE IF NOT EXISTS tags (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL UNIQUE\n);\n\n-- Node-Tags junction table\nCREATE TABLE IF NOT EXISTS node_tags (\n node_id TEXT NOT NULL,\n tag_id INTEGER NOT NULL,\n PRIMARY KEY (node_id, tag_id),\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE,\n FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE\n);\n\n-- Edges table\nCREATE TABLE IF NOT EXISTS edges (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n type TEXT NOT NULL CHECK(type IN ('link', 'reference', 'parent', 'related')),\n weight REAL DEFAULT 1.0,\n context TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (source_id) REFERENCES nodes(id) ON DELETE CASCADE\n);\n\n-- Metadata table\nCREATE TABLE IF NOT EXISTS metadata (\n key TEXT PRIMARY KEY,\n value TEXT,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Indexes for performance\nCREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);\nCREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);\nCREATE INDEX IF NOT EXISTS idx_nodes_path ON nodes(path);\nCREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);\nCREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);\nCREATE INDEX IF NOT EXISTS idx_node_tags_node ON node_tags(node_id);\nCREATE INDEX IF NOT EXISTS idx_node_tags_tag ON node_tags(tag_id);\n\n-- Full-text search\nCREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(\n title,\n content,\n content='nodes',\n content_rowid='rowid'\n);\n\n-- Triggers for FTS sync\nCREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN\n INSERT INTO nodes_fts(rowid, title, content) VALUES (NEW.rowid, NEW.title, NEW.content);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN\n INSERT INTO nodes_fts(nodes_fts, rowid, title, content) VALUES('delete', OLD.rowid, OLD.title, OLD.content);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN\n INSERT INTO nodes_fts(nodes_fts, rowid, title, content) VALUES('delete', OLD.rowid, OLD.title, OLD.content);\n INSERT INTO nodes_fts(rowid, title, content) VALUES (NEW.rowid, NEW.title, NEW.content);\nEND;\n\n-- Initialize metadata\nINSERT OR IGNORE INTO metadata (key, value) VALUES ('version', '1.0.0');\nINSERT OR IGNORE INTO metadata (key, value) VALUES ('created', datetime('now'));\n`;\n\n/**\n * Knowledge Graph Database\n */\nexport class KnowledgeGraphDatabase {\n private db: Database.Database;\n private dbPath: string;\n\n constructor(dbPath: string) {\n this.dbPath = dbPath;\n\n // Ensure directory exists\n const dir = dirname(dbPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Open database\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('busy_timeout = 5000');\n\n // Initialize schema\n this.db.exec(SCHEMA_SQL);\n }\n\n // ========================================================================\n // Node Operations\n // ========================================================================\n\n /**\n * Insert or update a node\n */\n upsertNode(node: KnowledgeNode): void {\n const stmt = this.db.prepare(`\n INSERT INTO nodes (id, path, filename, title, type, status, content, frontmatter, word_count, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))\n ON CONFLICT(id) DO UPDATE SET\n path = excluded.path,\n filename = excluded.filename,\n title = excluded.title,\n type = excluded.type,\n status = excluded.status,\n content = excluded.content,\n frontmatter = excluded.frontmatter,\n word_count = excluded.word_count,\n updated_at = datetime('now')\n `);\n\n stmt.run(\n node.id,\n node.path,\n node.filename,\n node.title,\n node.type,\n node.status,\n node.content,\n JSON.stringify(node.frontmatter),\n node.wordCount\n );\n\n // Update tags\n this.updateNodeTags(node.id, node.tags);\n }\n\n /**\n * Get node by ID\n */\n getNode(id: string): KnowledgeNode | null {\n const stmt = this.db.prepare('SELECT * FROM nodes WHERE id = ?');\n const row = stmt.get(id) as NodeRow | undefined;\n if (!row) return null;\n return this.rowToNode(row);\n }\n\n /**\n * Get node by path\n */\n getNodeByPath(path: string): KnowledgeNode | null {\n const stmt = this.db.prepare('SELECT * FROM nodes WHERE path = ?');\n const row = stmt.get(path) as NodeRow | undefined;\n if (!row) return null;\n return this.rowToNode(row);\n }\n\n /**\n * Get all nodes\n */\n getAllNodes(): KnowledgeNode[] {\n const stmt = this.db.prepare('SELECT * FROM nodes ORDER BY title');\n const rows = stmt.all() as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Get nodes by type\n */\n getNodesByType(type: NodeType): KnowledgeNode[] {\n const stmt = this.db.prepare('SELECT * FROM nodes WHERE type = ? ORDER BY title');\n const rows = stmt.all(type) as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Get nodes by status\n */\n getNodesByStatus(status: NodeStatus): KnowledgeNode[] {\n const stmt = this.db.prepare('SELECT * FROM nodes WHERE status = ? ORDER BY title');\n const rows = stmt.all(status) as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Get nodes by tag\n */\n getNodesByTag(tag: string): KnowledgeNode[] {\n const stmt = this.db.prepare(`\n SELECT n.* FROM nodes n\n JOIN node_tags nt ON n.id = nt.node_id\n JOIN tags t ON nt.tag_id = t.id\n WHERE t.name = ?\n ORDER BY n.title\n `);\n const rows = stmt.all(tag) as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Sanitize FTS5 query to prevent query injection\n * Escapes special FTS5 operators and quotes terms\n */\n private sanitizeFtsQuery(query: string): string {\n if (!query || typeof query !== 'string') return '';\n\n // Remove FTS5 special operators: * \" ( ) : ^ - AND OR NOT NEAR\n const sanitized = query\n .replace(/[*\"():^\\-]/g, ' ') // Remove special chars\n .replace(/\\b(AND|OR|NOT|NEAR)\\b/gi, '') // Remove boolean operators\n .trim()\n .split(/\\s+/)\n .filter(term => term.length > 0 && term.length < 100) // Limit term length\n .slice(0, 20) // Limit number of terms\n .map(term => `\"${term.replace(/\"/g, '')}\"`) // Quote each term safely\n .join(' ');\n\n return sanitized;\n }\n\n /**\n * Search nodes by title or content\n */\n searchNodes(query: string, limit = 50): KnowledgeNode[] {\n const sanitizedQuery = this.sanitizeFtsQuery(query);\n\n // Return empty if no valid search terms\n if (!sanitizedQuery) {\n return [];\n }\n\n // Enforce reasonable limit\n const safeLimit = Math.min(Math.max(1, limit), 100);\n\n const stmt = this.db.prepare(`\n SELECT n.* FROM nodes n\n JOIN nodes_fts fts ON n.rowid = fts.rowid\n WHERE nodes_fts MATCH ?\n ORDER BY rank\n LIMIT ?\n `);\n const rows = stmt.all(sanitizedQuery, safeLimit) as NodeRow[];\n return rows.map(row => this.rowToNode(row));\n }\n\n /**\n * Delete node\n */\n deleteNode(id: string): boolean {\n const stmt = this.db.prepare('DELETE FROM nodes WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n\n /**\n * Clear all nodes and edges (for full regeneration)\n */\n clearAll(): void {\n this.db.exec('DELETE FROM edges');\n this.db.exec('DELETE FROM node_tags');\n this.db.exec('DELETE FROM nodes');\n }\n\n // ========================================================================\n // Tag Operations\n // ========================================================================\n\n /**\n * Update tags for a node\n */\n private updateNodeTags(nodeId: string, tags: string[]): void {\n // Remove existing tags\n this.db.prepare('DELETE FROM node_tags WHERE node_id = ?').run(nodeId);\n\n // Insert new tags\n const getOrCreateTag = this.db.prepare(`\n INSERT INTO tags (name) VALUES (?)\n ON CONFLICT(name) DO UPDATE SET name = excluded.name\n RETURNING id\n `);\n\n const insertNodeTag = this.db.prepare(\n 'INSERT OR IGNORE INTO node_tags (node_id, tag_id) VALUES (?, ?)'\n );\n\n for (const tag of tags) {\n const result = getOrCreateTag.get(tag) as { id: number };\n insertNodeTag.run(nodeId, result.id);\n }\n }\n\n /**\n * Get tags for a node\n */\n getNodeTags(nodeId: string): string[] {\n const stmt = this.db.prepare(`\n SELECT t.name FROM tags t\n JOIN node_tags nt ON t.id = nt.tag_id\n WHERE nt.node_id = ?\n ORDER BY t.name\n `);\n const rows = stmt.all(nodeId) as Array<{ name: string }>;\n return rows.map(r => r.name);\n }\n\n /**\n * Get all tags with counts\n */\n getAllTags(): Array<{ name: string; count: number }> {\n const stmt = this.db.prepare(`\n SELECT t.name, COUNT(nt.node_id) as count\n FROM tags t\n LEFT JOIN node_tags nt ON t.id = nt.tag_id\n GROUP BY t.id\n ORDER BY count DESC, t.name\n `);\n return stmt.all() as Array<{ name: string; count: number }>;\n }\n\n // ========================================================================\n // Edge Operations\n // ========================================================================\n\n /**\n * Add edge\n */\n addEdge(edge: GraphEdge): void {\n const stmt = this.db.prepare(`\n INSERT INTO edges (source_id, target_id, type, weight, context)\n VALUES (?, ?, ?, ?, ?)\n `);\n stmt.run(edge.source, edge.target, edge.type, edge.weight, edge.context);\n }\n\n /**\n * Get outgoing edges for a node\n */\n getOutgoingEdges(nodeId: string): GraphEdge[] {\n const stmt = this.db.prepare('SELECT * FROM edges WHERE source_id = ?');\n const rows = stmt.all(nodeId) as EdgeRow[];\n return rows.map(row => this.rowToEdge(row));\n }\n\n /**\n * Get incoming edges for a node\n */\n getIncomingEdges(nodeId: string): GraphEdge[] {\n const stmt = this.db.prepare('SELECT * FROM edges WHERE target_id = ?');\n const rows = stmt.all(nodeId) as EdgeRow[];\n return rows.map(row => this.rowToEdge(row));\n }\n\n /**\n * Delete edges for a node\n */\n deleteNodeEdges(nodeId: string): void {\n this.db.prepare('DELETE FROM edges WHERE source_id = ?').run(nodeId);\n }\n\n // ========================================================================\n // Statistics\n // ========================================================================\n\n /**\n * Get graph statistics\n */\n getStats(): GraphStats {\n const totalNodes = (this.db.prepare('SELECT COUNT(*) as count FROM nodes').get() as { count: number }).count;\n const totalEdges = (this.db.prepare('SELECT COUNT(*) as count FROM edges').get() as { count: number }).count;\n\n const typeStats = this.db.prepare(`\n SELECT type, COUNT(*) as count FROM nodes GROUP BY type\n `).all() as Array<{ type: NodeType; count: number }>;\n\n const statusStats = this.db.prepare(`\n SELECT status, COUNT(*) as count FROM nodes GROUP BY status\n `).all() as Array<{ status: NodeStatus; count: number }>;\n\n const nodesByType: Record<NodeType, number> = {\n concept: 0, technical: 0, feature: 0, primitive: 0,\n service: 0, guide: 0, standard: 0, integration: 0,\n };\n for (const { type, count } of typeStats) {\n nodesByType[type] = count;\n }\n\n const nodesByStatus: Record<NodeStatus, number> = {\n draft: 0, active: 0, deprecated: 0, archived: 0,\n };\n for (const { status, count } of statusStats) {\n nodesByStatus[status] = count;\n }\n\n const orphanNodes = (this.db.prepare(`\n SELECT COUNT(*) as count FROM nodes n\n WHERE NOT EXISTS (SELECT 1 FROM edges e WHERE e.source_id = n.id OR e.target_id = n.id)\n `).get() as { count: number }).count;\n\n const avgLinksPerNode = totalNodes > 0 ? totalEdges / totalNodes : 0;\n\n const mostConnected = this.db.prepare(`\n SELECT n.id, (\n (SELECT COUNT(*) FROM edges WHERE source_id = n.id) +\n (SELECT COUNT(*) FROM edges WHERE target_id = n.id)\n ) as connections\n FROM nodes n\n ORDER BY connections DESC\n LIMIT 5\n `).all() as Array<{ id: string; connections: number }>;\n\n return {\n totalNodes,\n totalEdges,\n nodesByType,\n nodesByStatus,\n orphanNodes,\n avgLinksPerNode: Math.round(avgLinksPerNode * 100) / 100,\n mostConnected,\n };\n }\n\n // ========================================================================\n // Metadata Operations\n // ========================================================================\n\n /**\n * Get metadata value\n */\n getMetadata(key: string): string | null {\n const stmt = this.db.prepare('SELECT value FROM metadata WHERE key = ?');\n const row = stmt.get(key) as { value: string } | undefined;\n return row?.value ?? null;\n }\n\n /**\n * Set metadata value\n */\n setMetadata(key: string, value: string): void {\n const stmt = this.db.prepare(`\n INSERT INTO metadata (key, value, updated_at) VALUES (?, ?, datetime('now'))\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = datetime('now')\n `);\n stmt.run(key, value);\n }\n\n // ========================================================================\n // Utilities\n // ========================================================================\n\n /**\n * Convert database row to KnowledgeNode\n */\n private rowToNode(row: NodeRow): KnowledgeNode {\n const tags = this.getNodeTags(row.id);\n const outgoingEdges = this.getOutgoingEdges(row.id);\n const incomingEdges = this.getIncomingEdges(row.id);\n\n return {\n id: row.id,\n path: row.path,\n filename: row.filename,\n title: row.title,\n type: row.type as NodeType,\n status: row.status as NodeStatus,\n content: row.content || '',\n frontmatter: safeJsonParse(row.frontmatter, {}),\n tags,\n outgoingLinks: outgoingEdges.map(e => ({\n target: e.target,\n type: 'wikilink' as const,\n context: e.context,\n })),\n incomingLinks: incomingEdges.map(e => ({\n target: e.source,\n type: 'backlink' as const,\n context: e.context,\n })),\n wordCount: row.word_count,\n lastModified: new Date(row.updated_at),\n };\n }\n\n /**\n * Convert database row to GraphEdge\n */\n private rowToEdge(row: EdgeRow): GraphEdge {\n return {\n source: row.source_id,\n target: row.target_id,\n type: row.type as GraphEdge['type'],\n weight: row.weight,\n context: row.context ?? undefined,\n };\n }\n\n /**\n * Close database connection\n */\n close(): void {\n this.db.close();\n }\n\n /**\n * Get raw database instance\n */\n getDatabase(): Database.Database {\n return this.db;\n }\n}\n\n// Row types for database queries\ninterface NodeRow {\n id: string;\n path: string;\n filename: string;\n title: string;\n type: string;\n status: string;\n content: string | null;\n frontmatter: string | null;\n word_count: number;\n created_at: string;\n updated_at: string;\n}\n\ninterface EdgeRow {\n id: number;\n source_id: string;\n target_id: string;\n type: string;\n weight: number;\n context: string | null;\n created_at: string;\n}\n\n/**\n * Create knowledge graph database instance\n */\nexport function createDatabase(dbPath: string): KnowledgeGraphDatabase {\n return new KnowledgeGraphDatabase(dbPath);\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"graph-generator.d.ts","sourceRoot":"","sources":["../../src/generators/graph-generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAMV,gBAAgB,EAEjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AASzD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC;IACtE,KAAK,EAAE,qBAAqB,CAAC;IAC7B,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;CACH,CAAC,CAwFD;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;CACH,CAAC,CA6BD;AAgND;;GAEG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CAyED"}
1
+ {"version":3,"file":"graph-generator.d.ts","sourceRoot":"","sources":["../../src/generators/graph-generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAMV,gBAAgB,EAEjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AASzD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC;IACtE,KAAK,EAAE,qBAAqB,CAAC;IAC7B,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;CACH,CAAC,CAgGD;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;CACH,CAAC,CAgCD;AAgND;;GAEG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CAgFD"}
@@ -23,13 +23,22 @@ async function generateGraph(options) {
23
23
  for (const scanPath of pathsToScan) {
24
24
  const files = await fg("**/*.md", {
25
25
  cwd: scanPath,
26
- ignore: ["node_modules/**", ".git/**", "_templates/**", "dist/**", "build/**"],
26
+ ignore: [
27
+ "node_modules/**",
28
+ ".git/**",
29
+ "**/_templates/**",
30
+ // Templates anywhere in tree
31
+ "**/dist/**",
32
+ "**/build/**",
33
+ "**/.hive-mind/**"
34
+ // Hive mind internal files
35
+ ],
27
36
  absolute: true,
28
37
  dot: true
29
38
  // Include hidden directories like .claude/
30
39
  });
31
40
  for (const filePath of files) {
32
- allFiles.push({ filePath, baseDir: scanAll ? projectRoot : scanPath });
41
+ allFiles.push({ filePath, baseDir: projectRoot });
33
42
  }
34
43
  }
35
44
  stats.filesScanned = allFiles.length;
@@ -75,6 +84,7 @@ async function generateAndSave(options, dbPath) {
75
84
  const { graph, stats } = await generateGraph(options);
76
85
  const db = new KnowledgeGraphDatabase(dbPath);
77
86
  try {
87
+ db.clearAll();
78
88
  const nodes = graph.getAllNodes();
79
89
  const edges = graph.getAllEdges();
80
90
  for (const node of nodes) {
@@ -239,7 +249,16 @@ async function updateGraph(dbPath, docsRoot, allPaths) {
239
249
  for (const scanPath of pathsToScan) {
240
250
  const files = await fg("**/*.md", {
241
251
  cwd: scanPath,
242
- ignore: ["node_modules/**", ".git/**", "_templates/**", "dist/**", "build/**"],
252
+ ignore: [
253
+ "node_modules/**",
254
+ ".git/**",
255
+ "**/_templates/**",
256
+ // Templates anywhere in tree
257
+ "**/dist/**",
258
+ "**/build/**",
259
+ "**/.hive-mind/**"
260
+ // Hive mind internal files
261
+ ],
243
262
  dot: true
244
263
  // Include hidden directories like .claude/
245
264
  });
@@ -1 +1 @@
1
- {"version":3,"file":"graph-generator.js","sources":["../../src/generators/graph-generator.ts"],"sourcesContent":["/**\n * Knowledge Graph Generator\n *\n * Scans documentation and generates a knowledge graph from markdown files.\n */\n\nimport { readFileSync, statSync } from 'fs';\nimport { join, basename, relative, extname } from 'path';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\nimport type {\n KnowledgeNode,\n NodeType,\n NodeStatus,\n NodeLink,\n NodeFrontmatter,\n GeneratorOptions,\n GeneratedDocument,\n} from '../core/types.js';\nimport { KnowledgeGraphManager } from '../core/graph.js';\nimport { KnowledgeGraphDatabase } from '../core/database.js';\n\n/**\n * Link extraction patterns\n */\nconst WIKILINK_PATTERN = /\\[\\[([^\\]|]+)(?:\\|([^\\]]+))?\\]\\]/g;\nconst MARKDOWN_LINK_PATTERN = /\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n\n/**\n * Generate knowledge graph from docs directory (or multiple directories)\n */\nexport async function generateGraph(options: GeneratorOptions): Promise<{\n graph: KnowledgeGraphManager;\n stats: {\n filesScanned: number;\n nodesCreated: number;\n edgesCreated: number;\n errors: string[];\n };\n}> {\n const { projectRoot, outputPath, additionalPaths = [], scanAll } = options;\n\n const stats = {\n filesScanned: 0,\n nodesCreated: 0,\n edgesCreated: 0,\n errors: [] as string[],\n };\n\n // Create graph manager\n const graph = new KnowledgeGraphManager(\n basename(projectRoot),\n projectRoot\n );\n\n // Build list of all paths to scan\n const pathsToScan = scanAll ? [projectRoot] : [outputPath, ...additionalPaths];\n\n // Find all markdown files from all paths\n const allFiles: Array<{ filePath: string; baseDir: string }> = [];\n\n for (const scanPath of pathsToScan) {\n const files = await fg('**/*.md', {\n cwd: scanPath,\n ignore: ['node_modules/**', '.git/**', '_templates/**', 'dist/**', 'build/**'],\n absolute: true,\n dot: true, // Include hidden directories like .claude/\n });\n\n for (const filePath of files) {\n allFiles.push({ filePath, baseDir: scanAll ? projectRoot : scanPath });\n }\n }\n\n stats.filesScanned = allFiles.length;\n\n // First pass: Create all nodes\n const nodeMap = new Map<string, KnowledgeNode>();\n\n for (const { filePath, baseDir } of allFiles) {\n try {\n const node = await parseMarkdownFile(filePath, baseDir);\n // Avoid duplicate IDs by using full relative path from project root\n const uniqueId = scanAll ? node.id : node.id;\n if (!nodeMap.has(uniqueId)) {\n nodeMap.set(uniqueId, node);\n stats.nodesCreated++;\n }\n } catch (error) {\n stats.errors.push(`Failed to parse ${filePath}: ${error}`);\n }\n }\n\n // Second pass: Resolve links and add to graph\n for (const node of nodeMap.values()) {\n // Resolve outgoing links\n const resolvedLinks: NodeLink[] = [];\n\n for (const link of node.outgoingLinks) {\n // Try to resolve the link target\n const targetId = resolveLink(link.target, node.path, nodeMap);\n\n if (targetId) {\n resolvedLinks.push({\n ...link,\n target: targetId,\n });\n\n // Create backlink in target node\n const targetNode = nodeMap.get(targetId);\n if (targetNode) {\n targetNode.incomingLinks.push({\n target: node.id,\n type: 'backlink',\n text: node.title,\n });\n }\n\n stats.edgesCreated++;\n }\n }\n\n node.outgoingLinks = resolvedLinks;\n graph.addNode(node);\n }\n\n return { graph, stats };\n}\n\n/**\n * Generate graph and save to database\n */\nexport async function generateAndSave(\n options: GeneratorOptions,\n dbPath: string\n): Promise<{\n success: boolean;\n stats: {\n filesScanned: number;\n nodesCreated: number;\n edgesCreated: number;\n errors: string[];\n };\n}> {\n const { graph, stats } = await generateGraph(options);\n\n // Save to database\n const db = new KnowledgeGraphDatabase(dbPath);\n\n try {\n const nodes = graph.getAllNodes();\n const edges = graph.getAllEdges();\n\n for (const node of nodes) {\n db.upsertNode(node);\n }\n\n for (const edge of edges) {\n db.addEdge(edge);\n }\n\n db.setMetadata('lastGenerated', new Date().toISOString());\n db.setMetadata('nodeCount', String(stats.nodesCreated));\n db.setMetadata('edgeCount', String(stats.edgesCreated));\n\n return { success: true, stats };\n } catch (error) {\n stats.errors.push(`Database error: ${error}`);\n return { success: false, stats };\n } finally {\n db.close();\n }\n}\n\n/**\n * Parse a markdown file into a knowledge node\n */\nasync function parseMarkdownFile(\n filePath: string,\n docsRoot: string\n): Promise<KnowledgeNode> {\n const content = readFileSync(filePath, 'utf-8');\n const stat = statSync(filePath);\n const { data, content: body } = matter(content);\n\n // Extract filename and path\n const filename = basename(filePath, '.md');\n const relativePath = relative(docsRoot, filePath);\n\n // Generate ID from relative path\n const id = relativePath\n .replace(/\\.md$/, '')\n .replace(/\\\\/g, '/')\n .replace(/[^a-z0-9/]+/gi, '-')\n .toLowerCase();\n\n // Extract links from content\n const outgoingLinks = extractLinks(body);\n\n // Determine node type from frontmatter or path\n const type = inferNodeType(data.type, relativePath);\n const status = (data.status as NodeStatus) || 'active';\n\n // Build frontmatter\n const frontmatter: NodeFrontmatter = {\n title: data.title || formatTitle(filename),\n type,\n status,\n tags: Array.isArray(data.tags) ? data.tags : [],\n category: data.category,\n description: data.description,\n created: data.created || stat.birthtime.toISOString().split('T')[0],\n updated: data.updated || stat.mtime.toISOString().split('T')[0],\n aliases: data.aliases,\n related: data.related,\n ...data,\n };\n\n // Calculate word count\n const wordCount = body\n .replace(/[#*`\\[\\]()]/g, '')\n .split(/\\s+/)\n .filter(Boolean).length;\n\n return {\n id,\n path: relativePath,\n filename,\n title: frontmatter.title || formatTitle(filename),\n type,\n status,\n content: body,\n frontmatter,\n tags: frontmatter.tags || [],\n outgoingLinks,\n incomingLinks: [], // Will be filled in second pass\n wordCount,\n lastModified: stat.mtime,\n };\n}\n\n/**\n * Extract links from markdown content\n */\nfunction extractLinks(content: string): NodeLink[] {\n const links: NodeLink[] = [];\n const seen = new Set<string>();\n\n // Extract wikilinks\n let match: RegExpExecArray | null;\n while ((match = WIKILINK_PATTERN.exec(content)) !== null) {\n const target = match[1].trim();\n const text = match[2]?.trim();\n\n if (!seen.has(target)) {\n seen.add(target);\n links.push({\n target,\n type: 'wikilink',\n text,\n });\n }\n }\n\n // Extract markdown links (only internal ones)\n while ((match = MARKDOWN_LINK_PATTERN.exec(content)) !== null) {\n const text = match[1].trim();\n const target = match[2].trim();\n\n // Skip external URLs\n if (target.startsWith('http://') || target.startsWith('https://')) {\n continue;\n }\n\n if (!seen.has(target)) {\n seen.add(target);\n links.push({\n target,\n type: 'markdown',\n text,\n });\n }\n }\n\n return links;\n}\n\n/**\n * Resolve a link target to a node ID\n */\nfunction resolveLink(\n target: string,\n currentPath: string,\n nodeMap: Map<string, KnowledgeNode>\n): string | null {\n // Clean target\n const cleanTarget = target\n .replace(/\\.md$/, '')\n .replace(/^\\.\\//, '')\n .toLowerCase();\n\n // Try direct match\n for (const [id, node] of nodeMap) {\n // Match by ID\n if (id === cleanTarget) {\n return id;\n }\n\n // Match by filename\n if (node.filename.toLowerCase() === cleanTarget) {\n return id;\n }\n\n // Match by title\n if (node.title.toLowerCase() === cleanTarget) {\n return id;\n }\n\n // Match by alias\n if (node.frontmatter.aliases?.some(\n a => a.toLowerCase() === cleanTarget\n )) {\n return id;\n }\n }\n\n // Try relative path resolution\n const currentDir = currentPath.replace(/[^/]+$/, '');\n const relativePath = join(currentDir, cleanTarget)\n .replace(/\\\\/g, '/')\n .replace(/^\\//, '');\n\n for (const [id, node] of nodeMap) {\n if (id === relativePath || node.path.replace(/\\.md$/, '') === relativePath) {\n return id;\n }\n }\n\n return null;\n}\n\n/**\n * Infer node type from frontmatter or path\n */\nfunction inferNodeType(declaredType: unknown, path: string): NodeType {\n // Use declared type if valid\n const validTypes: NodeType[] = [\n 'concept', 'technical', 'feature', 'primitive',\n 'service', 'guide', 'standard', 'integration',\n ];\n\n if (typeof declaredType === 'string' && validTypes.includes(declaredType as NodeType)) {\n return declaredType as NodeType;\n }\n\n // Infer from path\n const pathLower = path.toLowerCase();\n\n if (pathLower.includes('concept')) return 'concept';\n if (pathLower.includes('component') || pathLower.includes('technical')) return 'technical';\n if (pathLower.includes('feature')) return 'feature';\n if (pathLower.includes('primitive') || pathLower.includes('integration')) return 'primitive';\n if (pathLower.includes('service') || pathLower.includes('api')) return 'service';\n if (pathLower.includes('guide') || pathLower.includes('tutorial')) return 'guide';\n if (pathLower.includes('standard')) return 'standard';\n if (pathLower.includes('reference')) return 'technical';\n\n return 'concept'; // Default\n}\n\n/**\n * Format filename as title\n */\nfunction formatTitle(filename: string): string {\n return filename\n .replace(/-/g, ' ')\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\n/**\n * Generate graph from existing database (incremental update)\n */\nexport async function updateGraph(\n dbPath: string,\n docsRoot: string,\n allPaths?: string[]\n): Promise<{\n added: number;\n updated: number;\n removed: number;\n errors: string[];\n}> {\n const result = {\n added: 0,\n updated: 0,\n removed: 0,\n errors: [] as string[],\n };\n\n const db = new KnowledgeGraphDatabase(dbPath);\n\n try {\n // Get existing nodes\n const existingNodes = db.getAllNodes();\n const existingPaths = new Set(existingNodes.map(n => n.path));\n\n // Paths to scan\n const pathsToScan = allPaths || [docsRoot];\n\n // Find current files from all paths\n const allCurrentFiles: Array<{ filePath: string; baseDir: string }> = [];\n\n for (const scanPath of pathsToScan) {\n const files = await fg('**/*.md', {\n cwd: scanPath,\n ignore: ['node_modules/**', '.git/**', '_templates/**', 'dist/**', 'build/**'],\n dot: true, // Include hidden directories like .claude/\n });\n\n for (const file of files) {\n allCurrentFiles.push({ filePath: file, baseDir: scanPath });\n }\n }\n\n const currentPaths = new Set(allCurrentFiles.map(f => f.filePath));\n\n // Find removed files\n for (const node of existingNodes) {\n if (!currentPaths.has(node.path)) {\n db.deleteNode(node.id);\n result.removed++;\n }\n }\n\n // Process current files\n for (const { filePath, baseDir } of allCurrentFiles) {\n const fullPath = join(baseDir, filePath);\n\n try {\n const node = await parseMarkdownFile(fullPath, baseDir);\n\n if (existingPaths.has(node.path)) {\n // Check if file was modified\n const existing = existingNodes.find(n => n.path === node.path);\n if (existing && node.lastModified > existing.lastModified) {\n db.deleteNodeEdges(node.id);\n db.upsertNode(node);\n result.updated++;\n }\n } else {\n db.upsertNode(node);\n result.added++;\n }\n } catch (error) {\n result.errors.push(`Failed to process ${filePath}: ${error}`);\n }\n }\n\n db.setMetadata('lastUpdated', new Date().toISOString());\n } finally {\n db.close();\n }\n\n return result;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"graph-generator.js","sources":["../../src/generators/graph-generator.ts"],"sourcesContent":["/**\n * Knowledge Graph Generator\n *\n * Scans documentation and generates a knowledge graph from markdown files.\n */\n\nimport { readFileSync, statSync } from 'fs';\nimport { join, basename, relative, extname } from 'path';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\nimport type {\n KnowledgeNode,\n NodeType,\n NodeStatus,\n NodeLink,\n NodeFrontmatter,\n GeneratorOptions,\n GeneratedDocument,\n} from '../core/types.js';\nimport { KnowledgeGraphManager } from '../core/graph.js';\nimport { KnowledgeGraphDatabase } from '../core/database.js';\n\n/**\n * Link extraction patterns\n */\nconst WIKILINK_PATTERN = /\\[\\[([^\\]|]+)(?:\\|([^\\]]+))?\\]\\]/g;\nconst MARKDOWN_LINK_PATTERN = /\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n\n/**\n * Generate knowledge graph from docs directory (or multiple directories)\n */\nexport async function generateGraph(options: GeneratorOptions): Promise<{\n graph: KnowledgeGraphManager;\n stats: {\n filesScanned: number;\n nodesCreated: number;\n edgesCreated: number;\n errors: string[];\n };\n}> {\n const { projectRoot, outputPath, additionalPaths = [], scanAll } = options;\n\n const stats = {\n filesScanned: 0,\n nodesCreated: 0,\n edgesCreated: 0,\n errors: [] as string[],\n };\n\n // Create graph manager\n const graph = new KnowledgeGraphManager(\n basename(projectRoot),\n projectRoot\n );\n\n // Build list of all paths to scan\n const pathsToScan = scanAll ? [projectRoot] : [outputPath, ...additionalPaths];\n\n // Find all markdown files from all paths\n const allFiles: Array<{ filePath: string; baseDir: string }> = [];\n\n for (const scanPath of pathsToScan) {\n const files = await fg('**/*.md', {\n cwd: scanPath,\n ignore: [\n 'node_modules/**',\n '.git/**',\n '**/_templates/**', // Templates anywhere in tree\n '**/dist/**',\n '**/build/**',\n '**/.hive-mind/**', // Hive mind internal files\n ],\n absolute: true,\n dot: true, // Include hidden directories like .claude/\n });\n\n for (const filePath of files) {\n // Always use project root as base for consistent IDs\n allFiles.push({ filePath, baseDir: projectRoot });\n }\n }\n\n stats.filesScanned = allFiles.length;\n\n // First pass: Create all nodes\n const nodeMap = new Map<string, KnowledgeNode>();\n\n for (const { filePath, baseDir } of allFiles) {\n try {\n const node = await parseMarkdownFile(filePath, baseDir);\n // Avoid duplicate IDs by using full relative path from project root\n const uniqueId = scanAll ? node.id : node.id;\n if (!nodeMap.has(uniqueId)) {\n nodeMap.set(uniqueId, node);\n stats.nodesCreated++;\n }\n } catch (error) {\n stats.errors.push(`Failed to parse ${filePath}: ${error}`);\n }\n }\n\n // Second pass: Resolve links and add to graph\n for (const node of nodeMap.values()) {\n // Resolve outgoing links\n const resolvedLinks: NodeLink[] = [];\n\n for (const link of node.outgoingLinks) {\n // Try to resolve the link target\n const targetId = resolveLink(link.target, node.path, nodeMap);\n\n if (targetId) {\n resolvedLinks.push({\n ...link,\n target: targetId,\n });\n\n // Create backlink in target node\n const targetNode = nodeMap.get(targetId);\n if (targetNode) {\n targetNode.incomingLinks.push({\n target: node.id,\n type: 'backlink',\n text: node.title,\n });\n }\n\n stats.edgesCreated++;\n }\n }\n\n node.outgoingLinks = resolvedLinks;\n graph.addNode(node);\n }\n\n return { graph, stats };\n}\n\n/**\n * Generate graph and save to database\n */\nexport async function generateAndSave(\n options: GeneratorOptions,\n dbPath: string\n): Promise<{\n success: boolean;\n stats: {\n filesScanned: number;\n nodesCreated: number;\n edgesCreated: number;\n errors: string[];\n };\n}> {\n const { graph, stats } = await generateGraph(options);\n\n // Save to database\n const db = new KnowledgeGraphDatabase(dbPath);\n\n try {\n // Clear existing data before full regeneration to prevent duplicates\n db.clearAll();\n\n const nodes = graph.getAllNodes();\n const edges = graph.getAllEdges();\n\n for (const node of nodes) {\n db.upsertNode(node);\n }\n\n for (const edge of edges) {\n db.addEdge(edge);\n }\n\n db.setMetadata('lastGenerated', new Date().toISOString());\n db.setMetadata('nodeCount', String(stats.nodesCreated));\n db.setMetadata('edgeCount', String(stats.edgesCreated));\n\n return { success: true, stats };\n } catch (error) {\n stats.errors.push(`Database error: ${error}`);\n return { success: false, stats };\n } finally {\n db.close();\n }\n}\n\n/**\n * Parse a markdown file into a knowledge node\n */\nasync function parseMarkdownFile(\n filePath: string,\n docsRoot: string\n): Promise<KnowledgeNode> {\n const content = readFileSync(filePath, 'utf-8');\n const stat = statSync(filePath);\n const { data, content: body } = matter(content);\n\n // Extract filename and path\n const filename = basename(filePath, '.md');\n const relativePath = relative(docsRoot, filePath);\n\n // Generate ID from relative path\n const id = relativePath\n .replace(/\\.md$/, '')\n .replace(/\\\\/g, '/')\n .replace(/[^a-z0-9/]+/gi, '-')\n .toLowerCase();\n\n // Extract links from content\n const outgoingLinks = extractLinks(body);\n\n // Determine node type from frontmatter or path\n const type = inferNodeType(data.type, relativePath);\n const status = (data.status as NodeStatus) || 'active';\n\n // Build frontmatter\n const frontmatter: NodeFrontmatter = {\n title: data.title || formatTitle(filename),\n type,\n status,\n tags: Array.isArray(data.tags) ? data.tags : [],\n category: data.category,\n description: data.description,\n created: data.created || stat.birthtime.toISOString().split('T')[0],\n updated: data.updated || stat.mtime.toISOString().split('T')[0],\n aliases: data.aliases,\n related: data.related,\n ...data,\n };\n\n // Calculate word count\n const wordCount = body\n .replace(/[#*`\\[\\]()]/g, '')\n .split(/\\s+/)\n .filter(Boolean).length;\n\n return {\n id,\n path: relativePath,\n filename,\n title: frontmatter.title || formatTitle(filename),\n type,\n status,\n content: body,\n frontmatter,\n tags: frontmatter.tags || [],\n outgoingLinks,\n incomingLinks: [], // Will be filled in second pass\n wordCount,\n lastModified: stat.mtime,\n };\n}\n\n/**\n * Extract links from markdown content\n */\nfunction extractLinks(content: string): NodeLink[] {\n const links: NodeLink[] = [];\n const seen = new Set<string>();\n\n // Extract wikilinks\n let match: RegExpExecArray | null;\n while ((match = WIKILINK_PATTERN.exec(content)) !== null) {\n const target = match[1].trim();\n const text = match[2]?.trim();\n\n if (!seen.has(target)) {\n seen.add(target);\n links.push({\n target,\n type: 'wikilink',\n text,\n });\n }\n }\n\n // Extract markdown links (only internal ones)\n while ((match = MARKDOWN_LINK_PATTERN.exec(content)) !== null) {\n const text = match[1].trim();\n const target = match[2].trim();\n\n // Skip external URLs\n if (target.startsWith('http://') || target.startsWith('https://')) {\n continue;\n }\n\n if (!seen.has(target)) {\n seen.add(target);\n links.push({\n target,\n type: 'markdown',\n text,\n });\n }\n }\n\n return links;\n}\n\n/**\n * Resolve a link target to a node ID\n */\nfunction resolveLink(\n target: string,\n currentPath: string,\n nodeMap: Map<string, KnowledgeNode>\n): string | null {\n // Clean target\n const cleanTarget = target\n .replace(/\\.md$/, '')\n .replace(/^\\.\\//, '')\n .toLowerCase();\n\n // Try direct match\n for (const [id, node] of nodeMap) {\n // Match by ID\n if (id === cleanTarget) {\n return id;\n }\n\n // Match by filename\n if (node.filename.toLowerCase() === cleanTarget) {\n return id;\n }\n\n // Match by title\n if (node.title.toLowerCase() === cleanTarget) {\n return id;\n }\n\n // Match by alias\n if (node.frontmatter.aliases?.some(\n a => a.toLowerCase() === cleanTarget\n )) {\n return id;\n }\n }\n\n // Try relative path resolution\n const currentDir = currentPath.replace(/[^/]+$/, '');\n const relativePath = join(currentDir, cleanTarget)\n .replace(/\\\\/g, '/')\n .replace(/^\\//, '');\n\n for (const [id, node] of nodeMap) {\n if (id === relativePath || node.path.replace(/\\.md$/, '') === relativePath) {\n return id;\n }\n }\n\n return null;\n}\n\n/**\n * Infer node type from frontmatter or path\n */\nfunction inferNodeType(declaredType: unknown, path: string): NodeType {\n // Use declared type if valid\n const validTypes: NodeType[] = [\n 'concept', 'technical', 'feature', 'primitive',\n 'service', 'guide', 'standard', 'integration',\n ];\n\n if (typeof declaredType === 'string' && validTypes.includes(declaredType as NodeType)) {\n return declaredType as NodeType;\n }\n\n // Infer from path\n const pathLower = path.toLowerCase();\n\n if (pathLower.includes('concept')) return 'concept';\n if (pathLower.includes('component') || pathLower.includes('technical')) return 'technical';\n if (pathLower.includes('feature')) return 'feature';\n if (pathLower.includes('primitive') || pathLower.includes('integration')) return 'primitive';\n if (pathLower.includes('service') || pathLower.includes('api')) return 'service';\n if (pathLower.includes('guide') || pathLower.includes('tutorial')) return 'guide';\n if (pathLower.includes('standard')) return 'standard';\n if (pathLower.includes('reference')) return 'technical';\n\n return 'concept'; // Default\n}\n\n/**\n * Format filename as title\n */\nfunction formatTitle(filename: string): string {\n return filename\n .replace(/-/g, ' ')\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\n/**\n * Generate graph from existing database (incremental update)\n */\nexport async function updateGraph(\n dbPath: string,\n docsRoot: string,\n allPaths?: string[]\n): Promise<{\n added: number;\n updated: number;\n removed: number;\n errors: string[];\n}> {\n const result = {\n added: 0,\n updated: 0,\n removed: 0,\n errors: [] as string[],\n };\n\n const db = new KnowledgeGraphDatabase(dbPath);\n\n try {\n // Get existing nodes\n const existingNodes = db.getAllNodes();\n const existingPaths = new Set(existingNodes.map(n => n.path));\n\n // Paths to scan\n const pathsToScan = allPaths || [docsRoot];\n\n // Find current files from all paths\n const allCurrentFiles: Array<{ filePath: string; baseDir: string }> = [];\n\n for (const scanPath of pathsToScan) {\n const files = await fg('**/*.md', {\n cwd: scanPath,\n ignore: [\n 'node_modules/**',\n '.git/**',\n '**/_templates/**', // Templates anywhere in tree\n '**/dist/**',\n '**/build/**',\n '**/.hive-mind/**', // Hive mind internal files\n ],\n dot: true, // Include hidden directories like .claude/\n });\n\n for (const file of files) {\n allCurrentFiles.push({ filePath: file, baseDir: scanPath });\n }\n }\n\n const currentPaths = new Set(allCurrentFiles.map(f => f.filePath));\n\n // Find removed files\n for (const node of existingNodes) {\n if (!currentPaths.has(node.path)) {\n db.deleteNode(node.id);\n result.removed++;\n }\n }\n\n // Process current files\n for (const { filePath, baseDir } of allCurrentFiles) {\n const fullPath = join(baseDir, filePath);\n\n try {\n const node = await parseMarkdownFile(fullPath, baseDir);\n\n if (existingPaths.has(node.path)) {\n // Check if file was modified\n const existing = existingNodes.find(n => n.path === node.path);\n if (existing && node.lastModified > existing.lastModified) {\n db.deleteNodeEdges(node.id);\n db.upsertNode(node);\n result.updated++;\n }\n } else {\n db.upsertNode(node);\n result.added++;\n }\n } catch (error) {\n result.errors.push(`Failed to process ${filePath}: ${error}`);\n }\n }\n\n db.setMetadata('lastUpdated', new Date().toISOString());\n } finally {\n db.close();\n }\n\n return result;\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { __exports as dist } from "../../../../_virtual/index8.js";
1
+ import { __exports as dist } from "../../../../_virtual/index7.js";
2
2
  import { __require as requireCreateProjectService } from "./createProjectService.js";
3
3
  var hasRequiredDist;
4
4
  function requireDist() {
@@ -1,4 +1,4 @@
1
- import { __exports as dist } from "../../../../_virtual/index6.js";
1
+ import { __exports as dist } from "../../../../_virtual/index5.js";
2
2
  import { __require as requireAstSpec } from "./generated/ast-spec.js";
3
3
  import { __require as requireLib } from "./lib.js";
4
4
  import { __require as requireParserOptions } from "./parser-options.js";
@@ -1,4 +1,4 @@
1
- import { __exports as dist } from "../../../../_virtual/index5.js";
1
+ import { __exports as dist } from "../../../../_virtual/index6.js";
2
2
  import { __require as requireGetKeys } from "./get-keys.js";
3
3
  import { __require as requireVisitorKeys } from "./visitor-keys.js";
4
4
  var hasRequiredDist;
@@ -1,4 +1,4 @@
1
- import { __exports as dist } from "../../../_virtual/index7.js";
1
+ import { __exports as dist } from "../../../_virtual/index8.js";
2
2
  import require$$1 from "fs";
3
3
  import path__default from "path";
4
4
  import require$$2 from "url";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weavelogic/knowledge-graph-agent",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Knowledge graph agent for Claude Code - generates knowledge graphs, initializes docs, and integrates with claude-flow",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",