diff --git a/src/visualizer/public/index.html b/src/visualizer/public/index.html index bc736b7..f961eef 100644 --- a/src/visualizer/public/index.html +++ b/src/visualizer/public/index.html @@ -1414,7 +1414,7 @@ } addSubgraph(data.nodes, data.edges); - // Highlight root/entry-point nodes + // Highlight root nodes, with entry point getting special treatment if (data.roots && data.roots.length > 0) { for (const rootId of data.roots) { const ele = cy.getElementById(rootId); @@ -1423,6 +1423,19 @@ } runLayout(); + + // Center on entry point if available + if (data.entryPoint) { + const entryEle = cy.getElementById(data.entryPoint); + if (entryEle.length > 0) { + entryEle.select(); + setTimeout(() => { + cy.animate({ center: { eles: entryEle } }, { duration: 400 }); + showNodeDetails(data.entryPoint); + }, 350); + } + } + const source = data.usedClaude ? ' (via Claude)' : ''; showToast(`Found ${data.nodes.length} related symbols${source}`); } catch (err) { diff --git a/src/visualizer/server.ts b/src/visualizer/server.ts index 0fc1a9b..e4c934e 100644 --- a/src/visualizer/server.ts +++ b/src/visualizer/server.ts @@ -80,10 +80,16 @@ export class VisualizerServer { const symbolIndex = this.buildSymbolIndex(); - const prompt = `You are analyzing a codebase to help a developer understand it visually. Given the question and symbol index below, identify the 8-12 most relevant symbols that would help answer the question. + const prompt = `You are analyzing a codebase to help a developer visually trace a code flow. Given the question and symbol index below, identify the entry point and the key symbols in the flow. -IMPORTANT: Return ONLY a JSON array of symbol names. No explanation, no markdown, no code fences. Just the array. -Example: ["requireAuth", "LoginPage", "getSession", "UserService"] +IMPORTANT: Return ONLY a JSON object with this exact format. No explanation, no markdown, no code fences. +{"entry": "symbolName", "flow": ["symbol1", "symbol2", "symbol3", ...]} + +- "entry" is THE single starting point the user would trigger (e.g., a page component, route handler, button click handler) +- "flow" is the symbols in rough execution order, starting from the entry point (max 8-10 symbols) +- Include the entry point in the flow array too + +Example: {"entry": "LoginPage", "flow": ["LoginPage", "handleSubmit", "authenticateUser", "createSession", "redirect"]} Question: "${question}" @@ -109,13 +115,27 @@ ${symbolIndex}`; this.claudeAvailable = true; - // Parse the JSON array from Claude's response + // Parse Claude's response — try object format first, then array fallback try { const text = stdout.trim(); - // Try to extract JSON array from response (Claude might wrap it) - const jsonMatch = text.match(/\[[\s\S]*\]/); - if (jsonMatch) { - const names = JSON.parse(jsonMatch[0]) as string[]; + // Try to extract JSON object {"entry": ..., "flow": [...]} + const objMatch = text.match(/\{[\s\S]*\}/); + if (objMatch) { + const parsed = JSON.parse(objMatch[0]) as { entry?: string; flow?: string[] }; + if (parsed.flow && Array.isArray(parsed.flow) && parsed.flow.length > 0) { + // Return flow with entry first + const names = parsed.flow.map(String); + if (parsed.entry && !names.includes(parsed.entry)) { + names.unshift(String(parsed.entry)); + } + resolve(names); + return; + } + } + // Fallback: try JSON array + const arrMatch = text.match(/\[[\s\S]*\]/); + if (arrMatch) { + const names = JSON.parse(arrMatch[0]) as string[]; if (Array.isArray(names) && names.length > 0) { resolve(names.map(String)); return; @@ -322,6 +342,7 @@ ${symbolIndex}`; let usedClaude = false; // Try Claude CLI first for intelligent query interpretation + let entryNodeId: string | null = null; const claudeNames = await this.askClaude(q); if (claudeNames && claudeNames.length > 0) { usedClaude = true; @@ -332,6 +353,10 @@ ${symbolIndex}`; if (r.node.name.toLowerCase().includes(name.toLowerCase()) || name.toLowerCase().includes(r.node.name.toLowerCase())) { seedMap.set(r.node.id, r.node); + // First match of first name = entry point + if (!entryNodeId && name === claudeNames[0]) { + entryNodeId = r.node.id; + } } } } @@ -448,7 +473,7 @@ ${symbolIndex}`; const finalNodes = Array.from(nodeMap.values()).filter(n => connectedIds.has(n.id)); - json({ nodes: finalNodes, edges: finalEdges, roots: rootIds, usedClaude }); + json({ nodes: finalNodes, edges: finalEdges, roots: rootIds, entryPoint: entryNodeId, usedClaude }); return; }