(options: SemanticNavigateOptions)
| 250 | } |
| 251 | |
| 252 | export async function semanticNavigate(options: SemanticNavigateOptions): Promise<string> { |
| 253 | const maxClusters = options.maxClusters ?? 20; |
| 254 | const maxDepth = options.maxDepth ?? 3; |
| 255 | |
| 256 | const entries = await walkDirectory({ rootDir: options.rootDir, depthLimit: 0 }); |
| 257 | const fileEntries = entries.filter((e) => !e.isDirectory && isNavigableSourceCandidate(e.path)); |
| 258 | |
| 259 | if (fileEntries.length === 0) return "No supported source files found in the project."; |
| 260 | |
| 261 | const files: FileInfo[] = []; |
| 262 | for (const entry of fileEntries) { |
| 263 | try { |
| 264 | const content = await readFile(entry.path, "utf-8"); |
| 265 | let header = extractHeader(content); |
| 266 | let symbolPreview: string[] = []; |
| 267 | try { |
| 268 | const analysis = await analyzeFile(entry.path); |
| 269 | if (analysis.header) header = analysis.header; |
| 270 | symbolPreview = flattenSymbols(analysis.symbols) |
| 271 | .slice(0, 3) |
| 272 | .map((s) => `${s.name}@${formatLineRange(s.line, s.endLine)}`); |
| 273 | } catch { |
| 274 | } |
| 275 | files.push({ |
| 276 | relativePath: entry.relativePath, |
| 277 | header, |
| 278 | content: content.substring(0, 500), |
| 279 | symbolPreview, |
| 280 | }); |
| 281 | } catch { |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | if (files.length === 0) return "Could not read any source files."; |
| 286 | |
| 287 | let embeddableFiles: FileInfo[] = files; |
| 288 | let vectors: number[][] = []; |
| 289 | let skippedForEmbedding = 0; |
| 290 | try { |
| 291 | const embedded = await embedFilesWithFallback(files); |
| 292 | embeddableFiles = embedded.files; |
| 293 | vectors = embedded.vectors; |
| 294 | skippedForEmbedding = embedded.skipped; |
| 295 | } catch (err) { |
| 296 | const providerHint = EMBED_PROVIDER === "openai" |
| 297 | ? `Check CONTEXTPLUS_OPENAI_API_KEY and CONTEXTPLUS_OPENAI_BASE_URL.` |
| 298 | : `Make sure Ollama is running (check OLLAMA_HOST) and that the embedding model configured in OLLAMA_EMBED_MODEL is available.`; |
| 299 | return `Embedding provider (${EMBED_PROVIDER}) not available: ${err instanceof Error ? err.message : String(err)}\n${providerHint}`; |
| 300 | } |
| 301 | |
| 302 | if (embeddableFiles.length === 0) return "No embeddable source files found in the project."; |
| 303 | |
| 304 | if (embeddableFiles.length <= MAX_FILES_PER_LEAF) { |
| 305 | let fileLabels: string[]; |
| 306 | try { |
| 307 | const prompt = `For each file below, produce a 3-7 word description. Return ONLY a JSON array of strings.\n\n${embeddableFiles.map((f) => `${f.relativePath}: ${f.header}`).join("\n")}`; |
| 308 | const response = await chatCompletion(prompt); |
| 309 | const match = response.match(/\[[\s\S]*\]/); |
no test coverage detected