(clusters: { files: FileInfo[]; pathPattern: string | null }[])
| 145 | } |
| 146 | |
| 147 | async function labelSiblingClusters(clusters: { files: FileInfo[]; pathPattern: string | null }[]): Promise<string[]> { |
| 148 | if (clusters.length === 0) return []; |
| 149 | if (clusters.length === 1) { |
| 150 | const pp = clusters[0].pathPattern; |
| 151 | if (pp) return [pp]; |
| 152 | return [clusters[0].files.map((f) => f.relativePath.split("/").pop()).join(", ").substring(0, 40)]; |
| 153 | } |
| 154 | |
| 155 | const clusterDescriptions = clusters.map((c, i) => { |
| 156 | const fileList = c.files.map((f) => `${f.relativePath}: ${f.header || "no description"}`).join("\n "); |
| 157 | const pattern = c.pathPattern ? ` (pattern: ${c.pathPattern})` : ""; |
| 158 | return `Cluster ${i + 1}${pattern}:\n ${fileList}`; |
| 159 | }); |
| 160 | |
| 161 | const prompt = `You are labeling clusters of code files. For each cluster below, produce EXACTLY one JSON array of objects, each with: |
| 162 | - "overarchingTheme": a sentence about the cluster's theme |
| 163 | - "distinguishingFeature": what makes this cluster unique vs siblings |
| 164 | - "label": EXACTLY 2 words describing the cluster |
| 165 | |
| 166 | ${clusterDescriptions.join("\n\n")} |
| 167 | |
| 168 | Respond with ONLY a JSON array of ${clusters.length} objects. No other text.`; |
| 169 | |
| 170 | try { |
| 171 | const response = await chatCompletion(prompt); |
| 172 | const jsonMatch = response.match(/\[[\s\S]*\]/); |
| 173 | if (!jsonMatch) return clusters.map((_, i) => `Cluster ${i + 1}`); |
| 174 | const labels = JSON.parse(jsonMatch[0]) as { label: string }[]; |
| 175 | return labels.map((l, i) => { |
| 176 | const pp = clusters[i].pathPattern; |
| 177 | const base = l.label || `Cluster ${i + 1}`; |
| 178 | return pp ? `${base} (${pp})` : base; |
| 179 | }); |
| 180 | } catch { |
| 181 | return clusters.map((c, i) => c.pathPattern ?? `Cluster ${i + 1}`); |
| 182 | } |
| 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) { |
no test coverage detected