Spaces:
Running
Running
| /** Known tag-to-layer mappings in priority order */ | |
| const TAG_LAYER_MAP = { | |
| component: { id: 'presentation', name: 'Presentation', description: 'UI components, views, and pages' }, | |
| api: { id: 'api', name: 'API', description: 'Routes, controllers, and API endpoints' }, | |
| model: { id: 'data', name: 'Data', description: 'Models, entities, and data schemas' }, | |
| util: { id: 'utilities', name: 'Utilities', description: 'Utility functions, helpers, and shared libraries' }, | |
| test: { id: 'testing', name: 'Testing', description: 'Test files and test utilities' }, | |
| }; | |
| /** | |
| * Detects architectural layers by grouping nodes based on tags | |
| * and directory structure. | |
| * | |
| * Layer detection strategy (priority order): | |
| * 1. Check node tags for known patterns (component, api, model, util, test) | |
| * 2. Fall back to grouping by first directory segment | |
| * 3. Root files (no directory) go to "Root" layer | |
| * | |
| * @param nodes - All graph nodes to classify into layers | |
| * @param edges - Graph edges (unused, reserved for future dependency analysis) | |
| * @returns Array of detected layers with assigned node IDs | |
| */ | |
| export function detectLayers(nodes, edges) { | |
| void edges; | |
| // Map from layer id → { name, description, nodeIds } | |
| const layerMap = new Map(); | |
| for (const node of nodes) { | |
| // Only process file nodes and nodes with "::" (function/class children) | |
| if (node.type !== 'file' && !node.id.includes('::')) { | |
| continue; | |
| } | |
| // For function/class nodes (contain "::"), extract the file path | |
| const filePath = node.id.includes('::') ? node.id.split('::')[0] : node.id; | |
| // Determine layer from tags first | |
| const layer = getLayerFromTags(node, nodes, filePath) ?? getLayerFromPath(filePath); | |
| if (!layerMap.has(layer.id)) { | |
| layerMap.set(layer.id, { name: layer.name, description: layer.description, nodeIds: [] }); | |
| } | |
| layerMap.get(layer.id).nodeIds.push(node.id); | |
| } | |
| // Convert map to array, skip empty layers | |
| const layers = []; | |
| for (const [id, data] of layerMap) { | |
| if (data.nodeIds.length > 0) { | |
| layers.push({ id, name: data.name, description: data.description, nodeIds: data.nodeIds }); | |
| } | |
| } | |
| return layers; | |
| } | |
| /** | |
| * Attempts to determine the layer from a node's tags. | |
| * For child nodes (function/class), looks up the parent file node's tags. | |
| */ | |
| function getLayerFromTags(node, allNodes, filePath) { | |
| // For file nodes, check their own tags | |
| if (node.type === 'file') { | |
| return matchTagToLayer(node.tags); | |
| } | |
| // For function/class nodes, find the parent file node and use its tags | |
| const parentFile = allNodes.find(n => n.type === 'file' && n.id === filePath); | |
| if (parentFile) { | |
| return matchTagToLayer(parentFile.tags); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Matches a tags array against known layer patterns. | |
| */ | |
| function matchTagToLayer(tags) { | |
| for (const tag of Object.keys(TAG_LAYER_MAP)) { | |
| if (tags.includes(tag)) { | |
| return TAG_LAYER_MAP[tag]; | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * Determines the layer from the file path's first directory segment. | |
| */ | |
| function getLayerFromPath(filePath) { | |
| const segments = filePath.split('/'); | |
| if (segments.length === 1) { | |
| // Root file (no directory) | |
| return { id: 'root', name: 'Root', description: 'Root-level files' }; | |
| } | |
| // Find the first meaningful directory segment (skip "src" as it's a common wrapper) | |
| let dirSegment; | |
| if (segments[0] === 'src' && segments.length > 2) { | |
| dirSegment = segments[1]; | |
| } | |
| else { | |
| dirSegment = segments[0]; | |
| } | |
| const name = capitalize(dirSegment); | |
| const id = dirSegment.toLowerCase(); | |
| return { id, name, description: `Files in the ${dirSegment}/ directory` }; | |
| } | |
| /** | |
| * Capitalizes the first letter of a string. | |
| */ | |
| function capitalize(str) { | |
| if (!str) | |
| return str; | |
| return str.charAt(0).toUpperCase() + str.slice(1); | |
| } | |