( projectRoot: string, embeddingModel: string, baseUrl: string, maxFiles: number, signal: AbortSignal | undefined, onProgress: (msg: string) => void, )
| 246 | } |
| 247 | |
| 248 | export async function buildIndex( |
| 249 | projectRoot: string, |
| 250 | embeddingModel: string, |
| 251 | baseUrl: string, |
| 252 | maxFiles: number, |
| 253 | signal: AbortSignal | undefined, |
| 254 | onProgress: (msg: string) => void, |
| 255 | ): Promise<Index> { |
| 256 | const files = await walkSource(projectRoot, maxFiles); |
| 257 | onProgress(`Indexing ${files.length} file(s)…`); |
| 258 | const chunks: Chunk[] = []; |
| 259 | // AST-aware chunking (semantic boundaries) with line-based fallback per file. |
| 260 | const { astChunkFile } = await import('./ast-chunk.js'); |
| 261 | for (const f of files) { |
| 262 | try { |
| 263 | chunks.push(...await astChunkFile(f.rel, f.content)); |
| 264 | } catch { |
| 265 | chunks.push(...chunkFile(f.rel, f.content, 40, 8)); |
| 266 | } |
| 267 | } |
| 268 | onProgress(`Embedding ${chunks.length} chunk(s) with ${embeddingModel}…`); |
| 269 | const { skipped } = await embedChunksResilient( |
| 270 | chunks, |
| 271 | (texts) => ollamaEmbed(baseUrl, embeddingModel, texts, signal), |
| 272 | { batchSize: 32, onProgress }, |
| 273 | ); |
| 274 | if (skipped > 0) onProgress(` (skipped ${skipped} oversized/unembeddable chunk(s))`); |
| 275 | return { projectRoot, embeddingModel, chunks, builtAt: Date.now() }; |
| 276 | } |
| 277 | |
| 278 | // ── Pure ranking / aggregation (unit-testable, no model) ──────────────────────── |
| 279 |
no test coverage detected