File size: 3,999 Bytes
fd8cdf5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/** 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);
}