(rootDir: string, query: string, maxDepth: number = 1, topK: number = 5, edgeFilter?: RelationType[])
| 181 | } |
| 182 | |
| 183 | export async function searchGraph(rootDir: string, query: string, maxDepth: number = 1, topK: number = 5, edgeFilter?: RelationType[]): Promise<GraphSearchResult> { |
| 184 | const graph = await loadGraph(rootDir); |
| 185 | const nodes = Object.values(graph.nodes); |
| 186 | if (nodes.length === 0) return { direct: [], neighbors: [], totalNodes: 0, totalEdges: 0 }; |
| 187 | |
| 188 | const [queryVec] = await fetchEmbedding(query); |
| 189 | const scored = nodes.map(n => ({ node: n, score: cosine(queryVec, n.embedding) })) |
| 190 | .sort((a, b) => b.score - a.score); |
| 191 | |
| 192 | const directHits = scored.slice(0, topK).map(({ node, score }) => { |
| 193 | node.lastAccessed = Date.now(); |
| 194 | return { |
| 195 | node, |
| 196 | depth: 0, |
| 197 | pathRelations: [] as string[], |
| 198 | relevanceScore: Math.round(score * 1000) / 10, |
| 199 | }; |
| 200 | }); |
| 201 | |
| 202 | const neighborResults: TraversalResult[] = []; |
| 203 | const visited = new Set(directHits.map(h => h.node.id)); |
| 204 | |
| 205 | for (const hit of directHits) { |
| 206 | traverseNeighbors(graph, hit.node.id, queryVec, 1, maxDepth, [hit.node.label], visited, neighborResults, edgeFilter); |
| 207 | } |
| 208 | |
| 209 | neighborResults.sort((a, b) => b.relevanceScore - a.relevanceScore); |
| 210 | |
| 211 | scheduleSave(rootDir); |
| 212 | return { |
| 213 | direct: directHits, |
| 214 | neighbors: neighborResults.slice(0, topK * 2), |
| 215 | totalNodes: nodes.length, |
| 216 | totalEdges: Object.keys(graph.edges).length, |
| 217 | }; |
| 218 | } |
| 219 | |
| 220 | function traverseNeighbors( |
| 221 | graph: GraphStore, nodeId: string, queryVec: number[], depth: number, maxDepth: number, |
no test coverage detected