(files: FileInfo[], vectors: number[][], maxClusters: number, depth: number, maxDepth: number)
| 183 | } |
| 184 | |
| 185 | async function buildHierarchy(files: FileInfo[], vectors: number[][], maxClusters: number, depth: number, maxDepth: number): Promise<ClusterNode> { |
| 186 | if (files.length <= MAX_FILES_PER_LEAF || depth >= maxDepth) { |
| 187 | return { |
| 188 | label: "", |
| 189 | pathPattern: findPathPattern(files.map((f) => f.relativePath)), |
| 190 | files, |
| 191 | children: [], |
| 192 | }; |
| 193 | } |
| 194 | |
| 195 | const clusterResults = spectralCluster(vectors, maxClusters); |
| 196 | |
| 197 | if (clusterResults.length <= 1) { |
| 198 | return { |
| 199 | label: "", |
| 200 | pathPattern: findPathPattern(files.map((f) => f.relativePath)), |
| 201 | files, |
| 202 | children: [], |
| 203 | }; |
| 204 | } |
| 205 | |
| 206 | const childMetas = clusterResults.map((cluster) => ({ |
| 207 | files: cluster.indices.map((i) => files[i]), |
| 208 | vectors: cluster.indices.map((i) => vectors[i]), |
| 209 | pathPattern: findPathPattern(cluster.indices.map((i) => files[i].relativePath)), |
| 210 | })); |
| 211 | |
| 212 | const labels = await labelSiblingClusters(childMetas.map((c) => ({ files: c.files, pathPattern: c.pathPattern }))); |
| 213 | |
| 214 | const children: ClusterNode[] = []; |
| 215 | for (let i = 0; i < childMetas.length; i++) { |
| 216 | const child = await buildHierarchy(childMetas[i].files, childMetas[i].vectors, maxClusters, depth + 1, maxDepth); |
| 217 | child.label = labels[i]; |
| 218 | children.push(child); |
| 219 | } |
| 220 | |
| 221 | return { |
| 222 | label: "", |
| 223 | pathPattern: findPathPattern(files.map((f) => f.relativePath)), |
| 224 | files: [], |
| 225 | children, |
| 226 | }; |
| 227 | } |
| 228 | |
| 229 | function renderClusterTree(node: ClusterNode, indent: number = 0): string { |
| 230 | const pad = " ".repeat(indent); |
no test coverage detected