* Store analysis result, splitting by file * Node IDs are deterministic (file__function format) so no prefixing needed
(
graph: WorkflowGraph,
contents: Record<string, string>
)
| 424 | * Node IDs are deterministic (file__function format) so no prefixing needed |
| 425 | */ |
| 426 | async setAnalysisResult( |
| 427 | graph: WorkflowGraph, |
| 428 | contents: Record<string, string> |
| 429 | ): Promise<void> { |
| 430 | await this.initPromise; |
| 431 | |
| 432 | // Helper to check if a relative path matches any content key |
| 433 | const isInBatch = (relativePath: string): boolean => { |
| 434 | if (contents[relativePath]) return true; |
| 435 | const normalizedRel = relativePath.replace(/\\/g, '/').replace(/^\//, ''); |
| 436 | for (const fullPath of Object.keys(contents)) { |
| 437 | const normalizedFull = fullPath.replace(/\\/g, '/'); |
| 438 | if (normalizedFull === normalizedRel) return true; |
| 439 | if (normalizedFull.endsWith('/' + normalizedRel)) return true; |
| 440 | if (normalizedFull.endsWith(normalizedRel)) return true; |
| 441 | } |
| 442 | return false; |
| 443 | }; |
| 444 | |
| 445 | // Filter nodes to only those for files in this batch |
| 446 | // LLM sometimes creates symbolic nodes (Frontend_UI, Telnyx_API) — skip them. |
| 447 | // Also skip nodes for files not in this batch. |
| 448 | const filteredNodes: WorkflowNode[] = []; |
| 449 | const skippedNodes: WorkflowNode[] = []; |
| 450 | for (const node of graph.nodes) { |
| 451 | const file = node.source?.file || 'unknown'; |
| 452 | // Skip symbolic/unknown nodes — they're not real code locations |
| 453 | if (file === 'unknown' || !file.includes('.')) { |
| 454 | skippedNodes.push(node); |
| 455 | } else if (isInBatch(file)) { |
| 456 | filteredNodes.push(node); |
| 457 | } else { |
| 458 | skippedNodes.push(node); |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | // Build node lookup from filtered nodes |
| 463 | const nodeById = new Map<string, WorkflowNode>(); |
| 464 | const nodeToFile = new Map<string, string>(); |
| 465 | |
| 466 | for (const node of filteredNodes) { |
| 467 | nodeById.set(node.id, node); |
| 468 | const file = node.source?.file || 'unknown'; |
| 469 | nodeToFile.set(node.id, file); |
| 470 | } |
| 471 | |
| 472 | // Group nodes by file (IDs are already deterministic) |
| 473 | const nodesByFile = new Map<string, WorkflowNode[]>(); |
| 474 | for (const node of filteredNodes) { |
| 475 | const file = node.source?.file || 'unknown'; |
| 476 | if (!nodesByFile.has(file)) nodesByFile.set(file, []); |
| 477 | nodesByFile.get(file)!.push(node); |
| 478 | } |
| 479 | |
| 480 | // Categorize edges |
| 481 | const internalEdgesByFile = new Map<string, WorkflowEdge[]>(); |
| 482 | const newCrossFileEdges: CrossFileEdge[] = []; |
| 483 |
no test coverage detected