* Failed refs whose name tail matches one of the given symbol names — the * candidates a sync should retry after files carrying those names changed * (#1240). Names matching more than `perNameCeiling` failed refs are * skipped entirely: at that population a name is external/builtin noise
(names: string[], perNameCeiling: number = 500)
| 2341 | * arbitrary subset would be both wasted work and incoherent coverage. |
| 2342 | */ |
| 2343 | getRetryableFailedReferences(names: string[], perNameCeiling: number = 500): UnresolvedReference[] { |
| 2344 | if (names.length === 0) return []; |
| 2345 | |
| 2346 | // Pass 1: per-tail counts, chunked under the SQLite parameter limit. |
| 2347 | const retryNames: string[] = []; |
| 2348 | for (let i = 0; i < names.length; i += SQLITE_PARAM_CHUNK_SIZE) { |
| 2349 | const chunk = names.slice(i, i + SQLITE_PARAM_CHUNK_SIZE); |
| 2350 | const placeholders = chunk.map(() => '?').join(','); |
| 2351 | const counts = this.db |
| 2352 | .prepare( |
| 2353 | `SELECT name_tail, COUNT(*) as count FROM unresolved_refs WHERE status = 'failed' AND name_tail IN (${placeholders}) GROUP BY name_tail` |
| 2354 | ) |
| 2355 | .all(...chunk) as Array<{ name_tail: string; count: number }>; |
| 2356 | for (const row of counts) { |
| 2357 | if (row.count <= perNameCeiling) retryNames.push(row.name_tail); |
| 2358 | } |
| 2359 | } |
| 2360 | if (retryNames.length === 0) return []; |
| 2361 | |
| 2362 | // Pass 2: load the surviving rows. |
| 2363 | const rows: UnresolvedRefRow[] = []; |
| 2364 | for (let i = 0; i < retryNames.length; i += SQLITE_PARAM_CHUNK_SIZE) { |
| 2365 | const chunk = retryNames.slice(i, i + SQLITE_PARAM_CHUNK_SIZE); |
| 2366 | const placeholders = chunk.map(() => '?').join(','); |
| 2367 | const chunkRows = this.db |
| 2368 | .prepare(`SELECT * FROM unresolved_refs WHERE status = 'failed' AND name_tail IN (${placeholders})`) |
| 2369 | .all(...chunk) as UnresolvedRefRow[]; |
| 2370 | rows.push(...chunkRows); |
| 2371 | } |
| 2372 | |
| 2373 | return rows.map((row) => ({ |
| 2374 | fromNodeId: row.from_node_id, |
| 2375 | referenceName: row.reference_name, |
| 2376 | referenceKind: row.reference_kind as EdgeKind, |
| 2377 | line: row.line, |
| 2378 | column: row.col, |
| 2379 | candidates: row.candidates ? safeJsonParse(row.candidates, undefined) : undefined, |
| 2380 | filePath: row.file_path, |
| 2381 | language: row.language as Language, |
| 2382 | rowId: row.id, |
| 2383 | })); |
| 2384 | } |
| 2385 | |
| 2386 | /** |
| 2387 | * Distinct node names present in the given files — the symbol names a sync |
no test coverage detected