UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

290 lines 9.02 kB
/** * Graph-based memory system for code intelligence * Explores different database approaches for agent memory */ /** * In-memory graph database for code relationships * Lightweight alternative to Neo4j for local use */ export class CodeGraphMemory { nodes = new Map(); edges = new Map(); temporalIndex = new Map(); // timestamp -> node IDs /** * Add a node to the graph */ addNode(node) { this.nodes.set(node.id, node); // Add to temporal index const timestamp = node.timestamp.getTime(); if (!this.temporalIndex.has(timestamp)) { this.temporalIndex.set(timestamp, []); } this.temporalIndex.get(timestamp).push(node.id); } /** * Add an edge between nodes */ addEdge(edge) { if (!this.edges.has(edge.from)) { this.edges.set(edge.from, []); } this.edges.get(edge.from).push(edge); } /** * Find all nodes connected to a given node */ getConnectedNodes(nodeId, edgeType, depth = 1) { const visited = new Set(); const result = []; const traverse = (id, currentDepth) => { if (visited.has(id) || currentDepth > depth) return; visited.add(id); const node = this.nodes.get(id); if (node && currentDepth > 0) { result.push(node); } const edges = this.edges.get(id) || []; for (const edge of edges) { if (!edgeType || edge.type === edgeType) { traverse(edge.to, currentDepth + 1); } } }; traverse(nodeId, 0); return result; } /** * Find nodes modified within a time range */ getNodesInTimeRange(start, end) { const startTime = start.getTime(); const endTime = end.getTime(); const result = []; for (const [timestamp, nodeIds] of this.temporalIndex) { if (timestamp >= startTime && timestamp <= endTime) { for (const nodeId of nodeIds) { const node = this.nodes.get(nodeId); if (node) result.push(node); } } } return result; } /** * Find dependency chains */ findDependencyChain(fromId, toId) { const paths = []; const visited = new Set(); const dfs = (current, path) => { if (current === toId) { paths.push([...path]); return; } if (visited.has(current)) return; visited.add(current); const edges = this.edges.get(current) || []; for (const edge of edges) { if (edge.type === 'depends_on' || edge.type === 'imports') { const nextNode = this.nodes.get(edge.to); if (nextNode) { dfs(edge.to, [...path, nextNode]); } } } visited.delete(current); }; const startNode = this.nodes.get(fromId); if (startNode) { dfs(fromId, [startNode]); } return paths; } /** * Detect circular dependencies */ detectCycles() { const cycles = []; const visited = new Set(); const recursionStack = new Set(); const dfs = (nodeId, path) => { visited.add(nodeId); recursionStack.add(nodeId); const edges = this.edges.get(nodeId) || []; for (const edge of edges) { if (!visited.has(edge.to)) { dfs(edge.to, [...path, edge.to]); } else if (recursionStack.has(edge.to)) { // Found a cycle const cycleStart = path.indexOf(edge.to); if (cycleStart !== -1) { cycles.push(path.slice(cycleStart)); } } } recursionStack.delete(nodeId); }; for (const nodeId of this.nodes.keys()) { if (!visited.has(nodeId)) { dfs(nodeId, [nodeId]); } } return cycles; } } /** * Document-based memory using embeddings and metadata */ export class DocumentMemory { documents = new Map(); addDocument(id, content, metadata) { this.documents.set(id, { content, metadata, timestamp: new Date(), }); } searchByMetadata(query) { const results = []; for (const [id, doc] of this.documents) { let matches = true; for (const [key, value] of Object.entries(query)) { if (doc.metadata[key] !== value) { matches = false; break; } } if (matches) { results.push([id, doc]); } } return results; } } /** * Time-series memory for tracking code evolution */ export class TimeSeriesMemory { events = []; addEvent(type, data, tags = []) { this.events.push({ timestamp: new Date(), type, data, tags, }); } queryByTimeRange(start, end, type) { return this.events.filter(event => { const inRange = event.timestamp >= start && event.timestamp <= end; const matchesType = !type || event.type === type; return inRange && matchesType; }); } getEventPatterns(windowSize = 3600000) { const patterns = {}; const now = Date.now(); for (const event of this.events) { const age = now - event.timestamp.getTime(); if (age <= windowSize) { patterns[event.type] = (patterns[event.type] || 0) + 1; } } return patterns; } } /** * Unified memory interface combining all approaches */ export class HybridCodeMemory { graph; documents; timeSeries; constructor() { this.graph = new CodeGraphMemory(); this.documents = new DocumentMemory(); this.timeSeries = new TimeSeriesMemory(); } /** * Record a code change with full context */ recordCodeChange(change) { // Add to graph const nodeId = `file:${change.file}:${Date.now()}`; this.graph.addNode({ id: nodeId, type: 'file', name: change.file, properties: { changeType: change.type }, timestamp: new Date(), }); // Create relationships if (change.imports) { for (const imp of change.imports) { this.graph.addEdge({ from: nodeId, to: `import:${imp}`, type: 'imports', timestamp: new Date(), }); } } // Add to time series this.timeSeries.addEvent('code_change', change, [change.type, change.file]); // Add to documents this.documents.addDocument(nodeId, JSON.stringify(change), { file: change.file, type: change.type, timestamp: new Date(), }); } /** * Query memory with context awareness */ query(query) { switch (query.type) { case 'graph': // Use graph for relationship queries return this.graph.getConnectedNodes(query.query, undefined, query.depth); case 'temporal': // Use time series for time-based queries if (query.timeRange) { return this.timeSeries.queryByTimeRange(query.timeRange.start, query.timeRange.end); } break; case 'semantic': // Use document store for content queries return this.documents.searchByMetadata({ type: query.query }); } } /** * Get coding patterns and insights */ getInsights() { return { recentPatterns: this.timeSeries.getEventPatterns(), dependencies: this.graph.detectCycles(), hotspots: this.findHotspots(), }; } findHotspots() { const changeFrequency = {}; const recentEvents = this.timeSeries.queryByTimeRange(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Last week new Date()); for (const event of recentEvents) { if (event.data.file) { changeFrequency[event.data.file] = (changeFrequency[event.data.file] || 0) + 1; } } return Object.entries(changeFrequency) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([file]) => file); } } //# sourceMappingURL=graph-memory.js.map