@weavelogic/knowledge-graph-agent 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.
- package/README.md +264 -0
- package/dist/cli/bin.d.ts +8 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/claude.d.ts +11 -0
- package/dist/cli/commands/claude.d.ts.map +1 -0
- package/dist/cli/commands/claude.js +102 -0
- package/dist/cli/commands/claude.js.map +1 -0
- package/dist/cli/commands/docs.d.ts +11 -0
- package/dist/cli/commands/docs.d.ts.map +1 -0
- package/dist/cli/commands/docs.js +108 -0
- package/dist/cli/commands/docs.js.map +1 -0
- package/dist/cli/commands/graph.d.ts +11 -0
- package/dist/cli/commands/graph.d.ts.map +1 -0
- package/dist/cli/commands/graph.js +122 -0
- package/dist/cli/commands/graph.js.map +1 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +80 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/search.d.ts +11 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +80 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +11 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +84 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +11 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +76 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +45 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/database.d.ts +121 -0
- package/dist/core/database.d.ts.map +1 -0
- package/dist/core/database.js +470 -0
- package/dist/core/database.js.map +1 -0
- package/dist/core/graph.d.ts +109 -0
- package/dist/core/graph.d.ts.map +1 -0
- package/dist/core/graph.js +343 -0
- package/dist/core/graph.js.map +1 -0
- package/dist/core/security.d.ts +62 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/core/security.js +31 -0
- package/dist/core/security.js.map +1 -0
- package/dist/core/types.d.ts +232 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +37 -0
- package/dist/core/types.js.map +1 -0
- package/dist/generators/claude-md.d.ts +33 -0
- package/dist/generators/claude-md.d.ts.map +1 -0
- package/dist/generators/claude-md.js +410 -0
- package/dist/generators/claude-md.js.map +1 -0
- package/dist/generators/docs-init.d.ts +20 -0
- package/dist/generators/docs-init.d.ts.map +1 -0
- package/dist/generators/docs-init.js +625 -0
- package/dist/generators/docs-init.js.map +1 -0
- package/dist/generators/graph-generator.d.ts +41 -0
- package/dist/generators/graph-generator.d.ts.map +1 -0
- package/dist/generators/graph-generator.js +266 -0
- package/dist/generators/graph-generator.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/claude-flow.d.ts +62 -0
- package/dist/integrations/claude-flow.d.ts.map +1 -0
- package/dist/integrations/claude-flow.js +243 -0
- package/dist/integrations/claude-flow.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Graph Core
|
|
3
|
+
*
|
|
4
|
+
* Core graph data structure and operations for managing knowledge nodes.
|
|
5
|
+
*/
|
|
6
|
+
import type { KnowledgeNode, KnowledgeGraph, GraphEdge, GraphMetadata, GraphStats, NodeType, NodeStatus } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Knowledge Graph Manager
|
|
9
|
+
*
|
|
10
|
+
* Manages the in-memory knowledge graph with efficient operations
|
|
11
|
+
* for node/edge management, traversal, and analysis.
|
|
12
|
+
*/
|
|
13
|
+
export declare class KnowledgeGraphManager {
|
|
14
|
+
private nodes;
|
|
15
|
+
private edges;
|
|
16
|
+
private incomingIndex;
|
|
17
|
+
private outgoingIndex;
|
|
18
|
+
private tagIndex;
|
|
19
|
+
private metadata;
|
|
20
|
+
constructor(name: string, rootPath: string);
|
|
21
|
+
/**
|
|
22
|
+
* Add a node to the graph
|
|
23
|
+
*/
|
|
24
|
+
addNode(node: KnowledgeNode): void;
|
|
25
|
+
/**
|
|
26
|
+
* Get a node by ID
|
|
27
|
+
*/
|
|
28
|
+
getNode(id: string): KnowledgeNode | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Get all nodes
|
|
31
|
+
*/
|
|
32
|
+
getAllNodes(): KnowledgeNode[];
|
|
33
|
+
/**
|
|
34
|
+
* Get nodes by type
|
|
35
|
+
*/
|
|
36
|
+
getNodesByType(type: NodeType): KnowledgeNode[];
|
|
37
|
+
/**
|
|
38
|
+
* Get nodes by status
|
|
39
|
+
*/
|
|
40
|
+
getNodesByStatus(status: NodeStatus): KnowledgeNode[];
|
|
41
|
+
/**
|
|
42
|
+
* Get nodes by tag
|
|
43
|
+
*/
|
|
44
|
+
getNodesByTag(tag: string): KnowledgeNode[];
|
|
45
|
+
/**
|
|
46
|
+
* Update a node
|
|
47
|
+
*/
|
|
48
|
+
updateNode(id: string, updates: Partial<KnowledgeNode>): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Remove a node
|
|
51
|
+
*/
|
|
52
|
+
removeNode(id: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Add an edge to the graph
|
|
55
|
+
*/
|
|
56
|
+
addEdge(edge: GraphEdge): void;
|
|
57
|
+
/**
|
|
58
|
+
* Get incoming edges for a node
|
|
59
|
+
*/
|
|
60
|
+
getIncomingEdges(nodeId: string): GraphEdge[];
|
|
61
|
+
/**
|
|
62
|
+
* Get outgoing edges for a node
|
|
63
|
+
*/
|
|
64
|
+
getOutgoingEdges(nodeId: string): GraphEdge[];
|
|
65
|
+
/**
|
|
66
|
+
* Get all edges
|
|
67
|
+
*/
|
|
68
|
+
getAllEdges(): GraphEdge[];
|
|
69
|
+
/**
|
|
70
|
+
* Find orphan nodes (no incoming or outgoing links)
|
|
71
|
+
*/
|
|
72
|
+
findOrphanNodes(): KnowledgeNode[];
|
|
73
|
+
/**
|
|
74
|
+
* Find most connected nodes
|
|
75
|
+
*/
|
|
76
|
+
findMostConnected(limit?: number): Array<{
|
|
77
|
+
node: KnowledgeNode;
|
|
78
|
+
connections: number;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Find path between two nodes (BFS)
|
|
82
|
+
*/
|
|
83
|
+
findPath(sourceId: string, targetId: string): string[] | null;
|
|
84
|
+
/**
|
|
85
|
+
* Find related nodes (nodes connected within n hops)
|
|
86
|
+
*/
|
|
87
|
+
findRelated(nodeId: string, maxHops?: number): KnowledgeNode[];
|
|
88
|
+
/**
|
|
89
|
+
* Get graph statistics
|
|
90
|
+
*/
|
|
91
|
+
getStats(): GraphStats;
|
|
92
|
+
/**
|
|
93
|
+
* Export graph to JSON
|
|
94
|
+
*/
|
|
95
|
+
toJSON(): KnowledgeGraph;
|
|
96
|
+
/**
|
|
97
|
+
* Import graph from JSON
|
|
98
|
+
*/
|
|
99
|
+
static fromJSON(data: KnowledgeGraph): KnowledgeGraphManager;
|
|
100
|
+
/**
|
|
101
|
+
* Get graph metadata
|
|
102
|
+
*/
|
|
103
|
+
getMetadata(): GraphMetadata;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create a new knowledge graph manager
|
|
107
|
+
*/
|
|
108
|
+
export declare function createKnowledgeGraph(name: string, rootPath: string): KnowledgeGraphManager;
|
|
109
|
+
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/core/graph.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,SAAS,EACT,aAAa,EACb,UAAU,EACV,QAAQ,EACR,UAAU,EAEX,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,QAAQ,CAAgB;gBAEpB,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAgB1C;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAyBlC;;OAEG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI9C;;OAEG;IACH,WAAW,IAAI,aAAa,EAAE;IAI9B;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,aAAa,EAAE;IAI/C;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,EAAE;IAIrD;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,EAAE;IAQ3C;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,OAAO;IA6BhE;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAiC/B;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAyB9B;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAI7C;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAI7C;;OAEG;IACH,WAAW,IAAI,SAAS,EAAE;IAQ1B;;OAEG;IACH,eAAe,IAAI,aAAa,EAAE;IAQlC;;OAEG;IACH,iBAAiB,CAAC,KAAK,SAAK,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAkBlF;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;IAkC7D;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,aAAa,EAAE;IAiCzD;;OAEG;IACH,QAAQ,IAAI,UAAU;IAiDtB;;OAEG;IACH,MAAM,IAAI,cAAc;IAQxB;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,qBAAqB;IA4B5D;;OAEG;IACH,WAAW,IAAI,aAAa;CAG7B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAE1F"}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
class KnowledgeGraphManager {
|
|
2
|
+
nodes = /* @__PURE__ */ new Map();
|
|
3
|
+
edges = [];
|
|
4
|
+
incomingIndex = /* @__PURE__ */ new Map();
|
|
5
|
+
outgoingIndex = /* @__PURE__ */ new Map();
|
|
6
|
+
tagIndex = /* @__PURE__ */ new Map();
|
|
7
|
+
metadata;
|
|
8
|
+
constructor(name, rootPath) {
|
|
9
|
+
this.metadata = {
|
|
10
|
+
name,
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13
|
+
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14
|
+
nodeCount: 0,
|
|
15
|
+
edgeCount: 0,
|
|
16
|
+
rootPath
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// ========================================================================
|
|
20
|
+
// Node Operations
|
|
21
|
+
// ========================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Add a node to the graph
|
|
24
|
+
*/
|
|
25
|
+
addNode(node) {
|
|
26
|
+
this.nodes.set(node.id, node);
|
|
27
|
+
this.metadata.nodeCount = this.nodes.size;
|
|
28
|
+
this.metadata.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
29
|
+
for (const tag of node.tags) {
|
|
30
|
+
if (!this.tagIndex.has(tag)) {
|
|
31
|
+
this.tagIndex.set(tag, /* @__PURE__ */ new Set());
|
|
32
|
+
}
|
|
33
|
+
this.tagIndex.get(tag).add(node.id);
|
|
34
|
+
}
|
|
35
|
+
for (const link of node.outgoingLinks) {
|
|
36
|
+
this.addEdge({
|
|
37
|
+
source: node.id,
|
|
38
|
+
target: link.target,
|
|
39
|
+
type: "link",
|
|
40
|
+
weight: 1,
|
|
41
|
+
context: link.context
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get a node by ID
|
|
47
|
+
*/
|
|
48
|
+
getNode(id) {
|
|
49
|
+
return this.nodes.get(id);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get all nodes
|
|
53
|
+
*/
|
|
54
|
+
getAllNodes() {
|
|
55
|
+
return Array.from(this.nodes.values());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get nodes by type
|
|
59
|
+
*/
|
|
60
|
+
getNodesByType(type) {
|
|
61
|
+
return this.getAllNodes().filter((node) => node.type === type);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get nodes by status
|
|
65
|
+
*/
|
|
66
|
+
getNodesByStatus(status) {
|
|
67
|
+
return this.getAllNodes().filter((node) => node.status === status);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get nodes by tag
|
|
71
|
+
*/
|
|
72
|
+
getNodesByTag(tag) {
|
|
73
|
+
const nodeIds = this.tagIndex.get(tag);
|
|
74
|
+
if (!nodeIds) return [];
|
|
75
|
+
return Array.from(nodeIds).map((id) => this.nodes.get(id)).filter((n) => n !== void 0);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Update a node
|
|
79
|
+
*/
|
|
80
|
+
updateNode(id, updates) {
|
|
81
|
+
const existing = this.nodes.get(id);
|
|
82
|
+
if (!existing) return false;
|
|
83
|
+
if (updates.tags) {
|
|
84
|
+
for (const tag of existing.tags) {
|
|
85
|
+
this.tagIndex.get(tag)?.delete(id);
|
|
86
|
+
}
|
|
87
|
+
for (const tag of updates.tags) {
|
|
88
|
+
if (!this.tagIndex.has(tag)) {
|
|
89
|
+
this.tagIndex.set(tag, /* @__PURE__ */ new Set());
|
|
90
|
+
}
|
|
91
|
+
this.tagIndex.get(tag).add(id);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const updated = {
|
|
95
|
+
...existing,
|
|
96
|
+
...updates,
|
|
97
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
98
|
+
};
|
|
99
|
+
this.nodes.set(id, updated);
|
|
100
|
+
this.metadata.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Remove a node
|
|
105
|
+
*/
|
|
106
|
+
removeNode(id) {
|
|
107
|
+
const node = this.nodes.get(id);
|
|
108
|
+
if (!node) return false;
|
|
109
|
+
for (const tag of node.tags) {
|
|
110
|
+
this.tagIndex.get(tag)?.delete(id);
|
|
111
|
+
}
|
|
112
|
+
this.edges = this.edges.filter((e) => e.source !== id && e.target !== id);
|
|
113
|
+
this.incomingIndex.delete(id);
|
|
114
|
+
this.outgoingIndex.delete(id);
|
|
115
|
+
for (const [targetId, edges] of this.incomingIndex) {
|
|
116
|
+
this.incomingIndex.set(targetId, edges.filter((e) => e.source !== id));
|
|
117
|
+
}
|
|
118
|
+
for (const [sourceId, edges] of this.outgoingIndex) {
|
|
119
|
+
this.outgoingIndex.set(sourceId, edges.filter((e) => e.target !== id));
|
|
120
|
+
}
|
|
121
|
+
this.nodes.delete(id);
|
|
122
|
+
this.metadata.nodeCount = this.nodes.size;
|
|
123
|
+
this.metadata.edgeCount = this.edges.length;
|
|
124
|
+
this.metadata.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
// ========================================================================
|
|
128
|
+
// Edge Operations
|
|
129
|
+
// ========================================================================
|
|
130
|
+
/**
|
|
131
|
+
* Add an edge to the graph
|
|
132
|
+
*/
|
|
133
|
+
addEdge(edge) {
|
|
134
|
+
const exists = this.edges.some(
|
|
135
|
+
(e) => e.source === edge.source && e.target === edge.target && e.type === edge.type
|
|
136
|
+
);
|
|
137
|
+
if (exists) return;
|
|
138
|
+
this.edges.push(edge);
|
|
139
|
+
if (!this.incomingIndex.has(edge.target)) {
|
|
140
|
+
this.incomingIndex.set(edge.target, []);
|
|
141
|
+
}
|
|
142
|
+
this.incomingIndex.get(edge.target).push(edge);
|
|
143
|
+
if (!this.outgoingIndex.has(edge.source)) {
|
|
144
|
+
this.outgoingIndex.set(edge.source, []);
|
|
145
|
+
}
|
|
146
|
+
this.outgoingIndex.get(edge.source).push(edge);
|
|
147
|
+
this.metadata.edgeCount = this.edges.length;
|
|
148
|
+
this.metadata.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get incoming edges for a node
|
|
152
|
+
*/
|
|
153
|
+
getIncomingEdges(nodeId) {
|
|
154
|
+
return this.incomingIndex.get(nodeId) || [];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get outgoing edges for a node
|
|
158
|
+
*/
|
|
159
|
+
getOutgoingEdges(nodeId) {
|
|
160
|
+
return this.outgoingIndex.get(nodeId) || [];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get all edges
|
|
164
|
+
*/
|
|
165
|
+
getAllEdges() {
|
|
166
|
+
return [...this.edges];
|
|
167
|
+
}
|
|
168
|
+
// ========================================================================
|
|
169
|
+
// Graph Analysis
|
|
170
|
+
// ========================================================================
|
|
171
|
+
/**
|
|
172
|
+
* Find orphan nodes (no incoming or outgoing links)
|
|
173
|
+
*/
|
|
174
|
+
findOrphanNodes() {
|
|
175
|
+
return this.getAllNodes().filter((node) => {
|
|
176
|
+
const incoming = this.getIncomingEdges(node.id);
|
|
177
|
+
const outgoing = this.getOutgoingEdges(node.id);
|
|
178
|
+
return incoming.length === 0 && outgoing.length === 0;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Find most connected nodes
|
|
183
|
+
*/
|
|
184
|
+
findMostConnected(limit = 10) {
|
|
185
|
+
const connections = /* @__PURE__ */ new Map();
|
|
186
|
+
for (const node of this.nodes.keys()) {
|
|
187
|
+
const incoming = this.getIncomingEdges(node).length;
|
|
188
|
+
const outgoing = this.getOutgoingEdges(node).length;
|
|
189
|
+
connections.set(node, incoming + outgoing);
|
|
190
|
+
}
|
|
191
|
+
return Array.from(connections.entries()).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([id, count]) => ({
|
|
192
|
+
node: this.nodes.get(id),
|
|
193
|
+
connections: count
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Find path between two nodes (BFS)
|
|
198
|
+
*/
|
|
199
|
+
findPath(sourceId, targetId) {
|
|
200
|
+
if (!this.nodes.has(sourceId) || !this.nodes.has(targetId)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (sourceId === targetId) {
|
|
204
|
+
return [sourceId];
|
|
205
|
+
}
|
|
206
|
+
const visited = /* @__PURE__ */ new Set();
|
|
207
|
+
const queue = [
|
|
208
|
+
{ node: sourceId, path: [sourceId] }
|
|
209
|
+
];
|
|
210
|
+
while (queue.length > 0) {
|
|
211
|
+
const { node, path } = queue.shift();
|
|
212
|
+
if (visited.has(node)) continue;
|
|
213
|
+
visited.add(node);
|
|
214
|
+
const outgoing = this.getOutgoingEdges(node);
|
|
215
|
+
for (const edge of outgoing) {
|
|
216
|
+
if (edge.target === targetId) {
|
|
217
|
+
return [...path, targetId];
|
|
218
|
+
}
|
|
219
|
+
if (!visited.has(edge.target)) {
|
|
220
|
+
queue.push({ node: edge.target, path: [...path, edge.target] });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Find related nodes (nodes connected within n hops)
|
|
228
|
+
*/
|
|
229
|
+
findRelated(nodeId, maxHops = 2) {
|
|
230
|
+
const related = /* @__PURE__ */ new Set();
|
|
231
|
+
const visited = /* @__PURE__ */ new Set();
|
|
232
|
+
const queue = [{ node: nodeId, hops: 0 }];
|
|
233
|
+
while (queue.length > 0) {
|
|
234
|
+
const { node, hops } = queue.shift();
|
|
235
|
+
if (visited.has(node) || hops > maxHops) continue;
|
|
236
|
+
visited.add(node);
|
|
237
|
+
if (node !== nodeId) {
|
|
238
|
+
related.add(node);
|
|
239
|
+
}
|
|
240
|
+
if (hops < maxHops) {
|
|
241
|
+
const outgoing = this.getOutgoingEdges(node);
|
|
242
|
+
const incoming = this.getIncomingEdges(node);
|
|
243
|
+
for (const edge of [...outgoing, ...incoming]) {
|
|
244
|
+
const neighbor = edge.source === node ? edge.target : edge.source;
|
|
245
|
+
if (!visited.has(neighbor)) {
|
|
246
|
+
queue.push({ node: neighbor, hops: hops + 1 });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return Array.from(related).map((id) => this.nodes.get(id)).filter((n) => n !== void 0);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get graph statistics
|
|
255
|
+
*/
|
|
256
|
+
getStats() {
|
|
257
|
+
const nodesByType = {
|
|
258
|
+
concept: 0,
|
|
259
|
+
technical: 0,
|
|
260
|
+
feature: 0,
|
|
261
|
+
primitive: 0,
|
|
262
|
+
service: 0,
|
|
263
|
+
guide: 0,
|
|
264
|
+
standard: 0,
|
|
265
|
+
integration: 0
|
|
266
|
+
};
|
|
267
|
+
const nodesByStatus = {
|
|
268
|
+
draft: 0,
|
|
269
|
+
active: 0,
|
|
270
|
+
deprecated: 0,
|
|
271
|
+
archived: 0
|
|
272
|
+
};
|
|
273
|
+
for (const node of this.nodes.values()) {
|
|
274
|
+
nodesByType[node.type]++;
|
|
275
|
+
nodesByStatus[node.status]++;
|
|
276
|
+
}
|
|
277
|
+
const orphanNodes = this.findOrphanNodes().length;
|
|
278
|
+
const avgLinksPerNode = this.nodes.size > 0 ? this.edges.length / this.nodes.size : 0;
|
|
279
|
+
const mostConnected = this.findMostConnected(5).map(({ node, connections }) => ({
|
|
280
|
+
id: node.id,
|
|
281
|
+
connections
|
|
282
|
+
}));
|
|
283
|
+
return {
|
|
284
|
+
totalNodes: this.nodes.size,
|
|
285
|
+
totalEdges: this.edges.length,
|
|
286
|
+
nodesByType,
|
|
287
|
+
nodesByStatus,
|
|
288
|
+
orphanNodes,
|
|
289
|
+
avgLinksPerNode: Math.round(avgLinksPerNode * 100) / 100,
|
|
290
|
+
mostConnected
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
// ========================================================================
|
|
294
|
+
// Serialization
|
|
295
|
+
// ========================================================================
|
|
296
|
+
/**
|
|
297
|
+
* Export graph to JSON
|
|
298
|
+
*/
|
|
299
|
+
toJSON() {
|
|
300
|
+
return {
|
|
301
|
+
nodes: this.nodes,
|
|
302
|
+
edges: [...this.edges],
|
|
303
|
+
metadata: { ...this.metadata }
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Import graph from JSON
|
|
308
|
+
*/
|
|
309
|
+
static fromJSON(data) {
|
|
310
|
+
const manager = new KnowledgeGraphManager(
|
|
311
|
+
data.metadata.name,
|
|
312
|
+
data.metadata.rootPath
|
|
313
|
+
);
|
|
314
|
+
for (const [id, node] of data.nodes) {
|
|
315
|
+
manager.nodes.set(id, node);
|
|
316
|
+
for (const tag of node.tags) {
|
|
317
|
+
if (!manager.tagIndex.has(tag)) {
|
|
318
|
+
manager.tagIndex.set(tag, /* @__PURE__ */ new Set());
|
|
319
|
+
}
|
|
320
|
+
manager.tagIndex.get(tag).add(id);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
for (const edge of data.edges) {
|
|
324
|
+
manager.addEdge(edge);
|
|
325
|
+
}
|
|
326
|
+
manager.metadata = { ...data.metadata };
|
|
327
|
+
return manager;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get graph metadata
|
|
331
|
+
*/
|
|
332
|
+
getMetadata() {
|
|
333
|
+
return { ...this.metadata };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function createKnowledgeGraph(name, rootPath) {
|
|
337
|
+
return new KnowledgeGraphManager(name, rootPath);
|
|
338
|
+
}
|
|
339
|
+
export {
|
|
340
|
+
KnowledgeGraphManager,
|
|
341
|
+
createKnowledgeGraph
|
|
342
|
+
};
|
|
343
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sources":["../../src/core/graph.ts"],"sourcesContent":["/**\n * Knowledge Graph Core\n *\n * Core graph data structure and operations for managing knowledge nodes.\n */\n\nimport type {\n KnowledgeNode,\n KnowledgeGraph,\n GraphEdge,\n GraphMetadata,\n GraphStats,\n NodeType,\n NodeStatus,\n NodeLink,\n} from './types.js';\n\n/**\n * Knowledge Graph Manager\n *\n * Manages the in-memory knowledge graph with efficient operations\n * for node/edge management, traversal, and analysis.\n */\nexport class KnowledgeGraphManager {\n private nodes: Map<string, KnowledgeNode> = new Map();\n private edges: GraphEdge[] = [];\n private incomingIndex: Map<string, GraphEdge[]> = new Map();\n private outgoingIndex: Map<string, GraphEdge[]> = new Map();\n private tagIndex: Map<string, Set<string>> = new Map();\n private metadata: GraphMetadata;\n\n constructor(name: string, rootPath: string) {\n this.metadata = {\n name,\n version: '1.0.0',\n created: new Date().toISOString(),\n updated: new Date().toISOString(),\n nodeCount: 0,\n edgeCount: 0,\n rootPath,\n };\n }\n\n // ========================================================================\n // Node Operations\n // ========================================================================\n\n /**\n * Add a node to the graph\n */\n addNode(node: KnowledgeNode): void {\n this.nodes.set(node.id, node);\n this.metadata.nodeCount = this.nodes.size;\n this.metadata.updated = new Date().toISOString();\n\n // Index tags\n for (const tag of node.tags) {\n if (!this.tagIndex.has(tag)) {\n this.tagIndex.set(tag, new Set());\n }\n this.tagIndex.get(tag)!.add(node.id);\n }\n\n // Create edges from outgoing links\n for (const link of node.outgoingLinks) {\n this.addEdge({\n source: node.id,\n target: link.target,\n type: 'link',\n weight: 1,\n context: link.context,\n });\n }\n }\n\n /**\n * Get a node by ID\n */\n getNode(id: string): KnowledgeNode | undefined {\n return this.nodes.get(id);\n }\n\n /**\n * Get all nodes\n */\n getAllNodes(): KnowledgeNode[] {\n return Array.from(this.nodes.values());\n }\n\n /**\n * Get nodes by type\n */\n getNodesByType(type: NodeType): KnowledgeNode[] {\n return this.getAllNodes().filter(node => node.type === type);\n }\n\n /**\n * Get nodes by status\n */\n getNodesByStatus(status: NodeStatus): KnowledgeNode[] {\n return this.getAllNodes().filter(node => node.status === status);\n }\n\n /**\n * Get nodes by tag\n */\n getNodesByTag(tag: string): KnowledgeNode[] {\n const nodeIds = this.tagIndex.get(tag);\n if (!nodeIds) return [];\n return Array.from(nodeIds)\n .map(id => this.nodes.get(id))\n .filter((n): n is KnowledgeNode => n !== undefined);\n }\n\n /**\n * Update a node\n */\n updateNode(id: string, updates: Partial<KnowledgeNode>): boolean {\n const existing = this.nodes.get(id);\n if (!existing) return false;\n\n // Handle tag changes\n if (updates.tags) {\n // Remove old tags from index\n for (const tag of existing.tags) {\n this.tagIndex.get(tag)?.delete(id);\n }\n // Add new tags to index\n for (const tag of updates.tags) {\n if (!this.tagIndex.has(tag)) {\n this.tagIndex.set(tag, new Set());\n }\n this.tagIndex.get(tag)!.add(id);\n }\n }\n\n const updated: KnowledgeNode = {\n ...existing,\n ...updates,\n lastModified: new Date(),\n };\n this.nodes.set(id, updated);\n this.metadata.updated = new Date().toISOString();\n return true;\n }\n\n /**\n * Remove a node\n */\n removeNode(id: string): boolean {\n const node = this.nodes.get(id);\n if (!node) return false;\n\n // Remove from tag index\n for (const tag of node.tags) {\n this.tagIndex.get(tag)?.delete(id);\n }\n\n // Remove related edges\n this.edges = this.edges.filter(e => e.source !== id && e.target !== id);\n this.incomingIndex.delete(id);\n this.outgoingIndex.delete(id);\n\n // Update indices for remaining edges\n for (const [targetId, edges] of this.incomingIndex) {\n this.incomingIndex.set(targetId, edges.filter(e => e.source !== id));\n }\n for (const [sourceId, edges] of this.outgoingIndex) {\n this.outgoingIndex.set(sourceId, edges.filter(e => e.target !== id));\n }\n\n this.nodes.delete(id);\n this.metadata.nodeCount = this.nodes.size;\n this.metadata.edgeCount = this.edges.length;\n this.metadata.updated = new Date().toISOString();\n return true;\n }\n\n // ========================================================================\n // Edge Operations\n // ========================================================================\n\n /**\n * Add an edge to the graph\n */\n addEdge(edge: GraphEdge): void {\n // Check for duplicates\n const exists = this.edges.some(\n e => e.source === edge.source && e.target === edge.target && e.type === edge.type\n );\n if (exists) return;\n\n this.edges.push(edge);\n\n // Update incoming index\n if (!this.incomingIndex.has(edge.target)) {\n this.incomingIndex.set(edge.target, []);\n }\n this.incomingIndex.get(edge.target)!.push(edge);\n\n // Update outgoing index\n if (!this.outgoingIndex.has(edge.source)) {\n this.outgoingIndex.set(edge.source, []);\n }\n this.outgoingIndex.get(edge.source)!.push(edge);\n\n this.metadata.edgeCount = this.edges.length;\n this.metadata.updated = new Date().toISOString();\n }\n\n /**\n * Get incoming edges for a node\n */\n getIncomingEdges(nodeId: string): GraphEdge[] {\n return this.incomingIndex.get(nodeId) || [];\n }\n\n /**\n * Get outgoing edges for a node\n */\n getOutgoingEdges(nodeId: string): GraphEdge[] {\n return this.outgoingIndex.get(nodeId) || [];\n }\n\n /**\n * Get all edges\n */\n getAllEdges(): GraphEdge[] {\n return [...this.edges];\n }\n\n // ========================================================================\n // Graph Analysis\n // ========================================================================\n\n /**\n * Find orphan nodes (no incoming or outgoing links)\n */\n findOrphanNodes(): KnowledgeNode[] {\n return this.getAllNodes().filter(node => {\n const incoming = this.getIncomingEdges(node.id);\n const outgoing = this.getOutgoingEdges(node.id);\n return incoming.length === 0 && outgoing.length === 0;\n });\n }\n\n /**\n * Find most connected nodes\n */\n findMostConnected(limit = 10): Array<{ node: KnowledgeNode; connections: number }> {\n const connections = new Map<string, number>();\n\n for (const node of this.nodes.keys()) {\n const incoming = this.getIncomingEdges(node).length;\n const outgoing = this.getOutgoingEdges(node).length;\n connections.set(node, incoming + outgoing);\n }\n\n return Array.from(connections.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, limit)\n .map(([id, count]) => ({\n node: this.nodes.get(id)!,\n connections: count,\n }));\n }\n\n /**\n * Find path between two nodes (BFS)\n */\n findPath(sourceId: string, targetId: string): string[] | null {\n if (!this.nodes.has(sourceId) || !this.nodes.has(targetId)) {\n return null;\n }\n\n if (sourceId === targetId) {\n return [sourceId];\n }\n\n const visited = new Set<string>();\n const queue: Array<{ node: string; path: string[] }> = [\n { node: sourceId, path: [sourceId] },\n ];\n\n while (queue.length > 0) {\n const { node, path } = queue.shift()!;\n\n if (visited.has(node)) continue;\n visited.add(node);\n\n const outgoing = this.getOutgoingEdges(node);\n for (const edge of outgoing) {\n if (edge.target === targetId) {\n return [...path, targetId];\n }\n if (!visited.has(edge.target)) {\n queue.push({ node: edge.target, path: [...path, edge.target] });\n }\n }\n }\n\n return null;\n }\n\n /**\n * Find related nodes (nodes connected within n hops)\n */\n findRelated(nodeId: string, maxHops = 2): KnowledgeNode[] {\n const related = new Set<string>();\n const visited = new Set<string>();\n const queue: Array<{ node: string; hops: number }> = [{ node: nodeId, hops: 0 }];\n\n while (queue.length > 0) {\n const { node, hops } = queue.shift()!;\n\n if (visited.has(node) || hops > maxHops) continue;\n visited.add(node);\n\n if (node !== nodeId) {\n related.add(node);\n }\n\n if (hops < maxHops) {\n const outgoing = this.getOutgoingEdges(node);\n const incoming = this.getIncomingEdges(node);\n\n for (const edge of [...outgoing, ...incoming]) {\n const neighbor = edge.source === node ? edge.target : edge.source;\n if (!visited.has(neighbor)) {\n queue.push({ node: neighbor, hops: hops + 1 });\n }\n }\n }\n }\n\n return Array.from(related)\n .map(id => this.nodes.get(id))\n .filter((n): n is KnowledgeNode => n !== undefined);\n }\n\n /**\n * Get graph statistics\n */\n getStats(): GraphStats {\n const nodesByType: Record<NodeType, number> = {\n concept: 0,\n technical: 0,\n feature: 0,\n primitive: 0,\n service: 0,\n guide: 0,\n standard: 0,\n integration: 0,\n };\n\n const nodesByStatus: Record<NodeStatus, number> = {\n draft: 0,\n active: 0,\n deprecated: 0,\n archived: 0,\n };\n\n for (const node of this.nodes.values()) {\n nodesByType[node.type]++;\n nodesByStatus[node.status]++;\n }\n\n const orphanNodes = this.findOrphanNodes().length;\n const avgLinksPerNode = this.nodes.size > 0\n ? this.edges.length / this.nodes.size\n : 0;\n\n const mostConnected = this.findMostConnected(5).map(({ node, connections }) => ({\n id: node.id,\n connections,\n }));\n\n return {\n totalNodes: this.nodes.size,\n totalEdges: this.edges.length,\n nodesByType,\n nodesByStatus,\n orphanNodes,\n avgLinksPerNode: Math.round(avgLinksPerNode * 100) / 100,\n mostConnected,\n };\n }\n\n // ========================================================================\n // Serialization\n // ========================================================================\n\n /**\n * Export graph to JSON\n */\n toJSON(): KnowledgeGraph {\n return {\n nodes: this.nodes,\n edges: [...this.edges],\n metadata: { ...this.metadata },\n };\n }\n\n /**\n * Import graph from JSON\n */\n static fromJSON(data: KnowledgeGraph): KnowledgeGraphManager {\n const manager = new KnowledgeGraphManager(\n data.metadata.name,\n data.metadata.rootPath\n );\n\n // Add nodes (this will also create edges from outgoing links)\n for (const [id, node] of data.nodes) {\n manager.nodes.set(id, node);\n\n // Index tags\n for (const tag of node.tags) {\n if (!manager.tagIndex.has(tag)) {\n manager.tagIndex.set(tag, new Set());\n }\n manager.tagIndex.get(tag)!.add(id);\n }\n }\n\n // Add edges\n for (const edge of data.edges) {\n manager.addEdge(edge);\n }\n\n manager.metadata = { ...data.metadata };\n return manager;\n }\n\n /**\n * Get graph metadata\n */\n getMetadata(): GraphMetadata {\n return { ...this.metadata };\n }\n}\n\n/**\n * Create a new knowledge graph manager\n */\nexport function createKnowledgeGraph(name: string, rootPath: string): KnowledgeGraphManager {\n return new KnowledgeGraphManager(name, rootPath);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for input validation and security.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Validate that a path stays within a base directory
|
|
8
|
+
* Prevents path traversal attacks
|
|
9
|
+
*
|
|
10
|
+
* @param basePath - The base directory that paths must stay within
|
|
11
|
+
* @param relativePath - The relative path to validate
|
|
12
|
+
* @returns The resolved absolute path
|
|
13
|
+
* @throws Error if path traversal is detected
|
|
14
|
+
*/
|
|
15
|
+
export declare function validatePath(basePath: string, relativePath: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Validate and sanitize a project root path
|
|
18
|
+
* Ensures the path exists and is a directory
|
|
19
|
+
*
|
|
20
|
+
* @param projectRoot - The project root path
|
|
21
|
+
* @returns The resolved absolute path
|
|
22
|
+
* @throws Error if invalid
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateProjectRoot(projectRoot: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Validate a docs path relative to project root
|
|
27
|
+
*
|
|
28
|
+
* @param projectRoot - The project root path
|
|
29
|
+
* @param docsPath - The docs directory path (relative or absolute)
|
|
30
|
+
* @returns The validated absolute path
|
|
31
|
+
* @throws Error if path traversal detected
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateDocsPath(projectRoot: string, docsPath: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Sanitize a filename to prevent directory traversal
|
|
36
|
+
*
|
|
37
|
+
* @param filename - The filename to sanitize
|
|
38
|
+
* @returns The sanitized filename
|
|
39
|
+
*/
|
|
40
|
+
export declare function sanitizeFilename(filename: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Sanitize user input for safe display
|
|
43
|
+
* Removes potentially dangerous characters
|
|
44
|
+
*
|
|
45
|
+
* @param input - The input string to sanitize
|
|
46
|
+
* @param maxLength - Maximum length (default 1000)
|
|
47
|
+
* @returns The sanitized string
|
|
48
|
+
*/
|
|
49
|
+
export declare function sanitizeInput(input: string, maxLength?: number): string;
|
|
50
|
+
/**
|
|
51
|
+
* Validate a template path - must be within project and be a .md file
|
|
52
|
+
*
|
|
53
|
+
* @param projectRoot - The project root path
|
|
54
|
+
* @param templatePath - The template path to validate
|
|
55
|
+
* @returns The validated absolute path or null if invalid
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateTemplatePath(projectRoot: string, templatePath: string): string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Check if a string looks like a file path
|
|
60
|
+
*/
|
|
61
|
+
export declare function looksLikePath(str: string): boolean;
|
|
62
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/core/security.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAiB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAc/D;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ9E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUzD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,MAAM,CASrE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,MAAM,GAAG,IAAI,CAcf;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolve, isAbsolute, normalize } from "path";
|
|
2
|
+
function validatePath(basePath, relativePath) {
|
|
3
|
+
const resolvedBase = resolve(basePath);
|
|
4
|
+
const targetPath = isAbsolute(relativePath) ? normalize(relativePath) : resolve(resolvedBase, relativePath);
|
|
5
|
+
const normalizedTarget = normalize(targetPath);
|
|
6
|
+
if (!normalizedTarget.startsWith(resolvedBase + "/") && normalizedTarget !== resolvedBase) {
|
|
7
|
+
throw new Error(`Path traversal detected: "${relativePath}" escapes base directory`);
|
|
8
|
+
}
|
|
9
|
+
return normalizedTarget;
|
|
10
|
+
}
|
|
11
|
+
function validateProjectRoot(projectRoot) {
|
|
12
|
+
if (!projectRoot || typeof projectRoot !== "string") {
|
|
13
|
+
throw new Error("Project root path is required");
|
|
14
|
+
}
|
|
15
|
+
const resolved = resolve(projectRoot);
|
|
16
|
+
if (resolved.includes("\0")) {
|
|
17
|
+
throw new Error("Invalid null byte in path");
|
|
18
|
+
}
|
|
19
|
+
return resolved;
|
|
20
|
+
}
|
|
21
|
+
function validateDocsPath(projectRoot, docsPath) {
|
|
22
|
+
const resolvedRoot = validateProjectRoot(projectRoot);
|
|
23
|
+
const safeDocs = docsPath?.trim() || "docs";
|
|
24
|
+
return validatePath(resolvedRoot, safeDocs);
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
validateDocsPath,
|
|
28
|
+
validatePath,
|
|
29
|
+
validateProjectRoot
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sources":["../../src/core/security.ts"],"sourcesContent":["/**\n * Security Utilities\n *\n * Helper functions for input validation and security.\n */\n\nimport { resolve, normalize, isAbsolute } from 'path';\n\n/**\n * Validate that a path stays within a base directory\n * Prevents path traversal attacks\n *\n * @param basePath - The base directory that paths must stay within\n * @param relativePath - The relative path to validate\n * @returns The resolved absolute path\n * @throws Error if path traversal is detected\n */\nexport function validatePath(basePath: string, relativePath: string): string {\n // Resolve the base path to absolute\n const resolvedBase = resolve(basePath);\n\n // Handle absolute paths - must still be within base\n const targetPath = isAbsolute(relativePath)\n ? normalize(relativePath)\n : resolve(resolvedBase, relativePath);\n\n const normalizedTarget = normalize(targetPath);\n\n // Ensure the resolved path is within the base path\n if (!normalizedTarget.startsWith(resolvedBase + '/') && normalizedTarget !== resolvedBase) {\n throw new Error(`Path traversal detected: \"${relativePath}\" escapes base directory`);\n }\n\n return normalizedTarget;\n}\n\n/**\n * Validate and sanitize a project root path\n * Ensures the path exists and is a directory\n *\n * @param projectRoot - The project root path\n * @returns The resolved absolute path\n * @throws Error if invalid\n */\nexport function validateProjectRoot(projectRoot: string): string {\n if (!projectRoot || typeof projectRoot !== 'string') {\n throw new Error('Project root path is required');\n }\n\n // Resolve to absolute path\n const resolved = resolve(projectRoot);\n\n // Check for suspicious patterns\n if (resolved.includes('\\0')) {\n throw new Error('Invalid null byte in path');\n }\n\n return resolved;\n}\n\n/**\n * Validate a docs path relative to project root\n *\n * @param projectRoot - The project root path\n * @param docsPath - The docs directory path (relative or absolute)\n * @returns The validated absolute path\n * @throws Error if path traversal detected\n */\nexport function validateDocsPath(projectRoot: string, docsPath: string): string {\n const resolvedRoot = validateProjectRoot(projectRoot);\n\n // Default to 'docs' if empty\n const safeDocs = docsPath?.trim() || 'docs';\n\n // Validate it stays within project\n return validatePath(resolvedRoot, safeDocs);\n}\n\n/**\n * Sanitize a filename to prevent directory traversal\n *\n * @param filename - The filename to sanitize\n * @returns The sanitized filename\n */\nexport function sanitizeFilename(filename: string): string {\n if (!filename || typeof filename !== 'string') {\n return 'untitled';\n }\n\n return filename\n .replace(/\\.\\./g, '') // Remove parent directory references\n .replace(/[<>:\"|?*\\0]/g, '') // Remove invalid filename chars\n .replace(/^\\/+|\\/+$/g, '') // Remove leading/trailing slashes\n .slice(0, 255); // Limit length\n}\n\n/**\n * Sanitize user input for safe display\n * Removes potentially dangerous characters\n *\n * @param input - The input string to sanitize\n * @param maxLength - Maximum length (default 1000)\n * @returns The sanitized string\n */\nexport function sanitizeInput(input: string, maxLength = 1000): string {\n if (!input || typeof input !== 'string') {\n return '';\n }\n\n return input\n .replace(/[<>&\"'`]/g, '') // Remove HTML-sensitive chars\n .slice(0, maxLength)\n .trim();\n}\n\n/**\n * Validate a template path - must be within project and be a .md file\n *\n * @param projectRoot - The project root path\n * @param templatePath - The template path to validate\n * @returns The validated absolute path or null if invalid\n */\nexport function validateTemplatePath(\n projectRoot: string,\n templatePath: string\n): string | null {\n try {\n const resolvedRoot = validateProjectRoot(projectRoot);\n const validated = validatePath(resolvedRoot, templatePath);\n\n // Must be a markdown file\n if (!validated.endsWith('.md')) {\n return null;\n }\n\n return validated;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if a string looks like a file path\n */\nexport function looksLikePath(str: string): boolean {\n return str.includes('/') || str.includes('\\\\') || str.startsWith('.');\n}\n"],"names":[],"mappings":";AAiBO,SAAS,aAAa,UAAkB,cAA8B;AAE3E,QAAM,eAAe,QAAQ,QAAQ;AAGrC,QAAM,aAAa,WAAW,YAAY,IACtC,UAAU,YAAY,IACtB,QAAQ,cAAc,YAAY;AAEtC,QAAM,mBAAmB,UAAU,UAAU;AAG7C,MAAI,CAAC,iBAAiB,WAAW,eAAe,GAAG,KAAK,qBAAqB,cAAc;AACzF,UAAM,IAAI,MAAM,6BAA6B,YAAY,0BAA0B;AAAA,EACrF;AAEA,SAAO;AACT;AAUO,SAAS,oBAAoB,aAA6B;AAC/D,MAAI,CAAC,eAAe,OAAO,gBAAgB,UAAU;AACnD,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AAGA,QAAM,WAAW,QAAQ,WAAW;AAGpC,MAAI,SAAS,SAAS,IAAI,GAAG;AAC3B,UAAM,IAAI,MAAM,2BAA2B;AAAA,EAC7C;AAEA,SAAO;AACT;AAUO,SAAS,iBAAiB,aAAqB,UAA0B;AAC9E,QAAM,eAAe,oBAAoB,WAAW;AAGpD,QAAM,WAAW,UAAU,KAAA,KAAU;AAGrC,SAAO,aAAa,cAAc,QAAQ;AAC5C;"}
|