@wrongstack/tools 0.267.0 → 0.269.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -339,7 +339,7 @@ var IndexStore = class {
339
339
  throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
340
340
  }
341
341
  const delay = Math.min(
342
- LOCK_RETRY_BASE_DELAY_MS * Math.pow(2, attempt),
342
+ LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
343
343
  LOCK_RETRY_MAX_DELAY_MS
344
344
  );
345
345
  sleepSync(delay);
@@ -632,14 +632,15 @@ var IndexStore = class {
632
632
  if (!query.trim()) {
633
633
  return { results: candidates.slice(0, limit), total: candidates.length };
634
634
  }
635
+ const candidateById = new Map(candidates.map((c) => [c.id, c]));
635
636
  const bm25 = buildBm25Index(
636
637
  candidates.map((c) => ({ id: c.id, text: buildIndexableText(c.name, c.signature, c.docComment) }))
637
638
  );
638
- const scored = bm25.score(query, (id) => candidates.some((c) => c.id === id));
639
+ const scored = bm25.score(query, (id) => candidateById.has(id));
639
640
  scored.sort((a, b) => b.score - a.score);
640
641
  const qTokens = tokenise(query);
641
642
  const results = scored.slice(0, limit).map(({ id, score }) => {
642
- const c = expectDefined(candidates.find((cand) => cand.id === id));
643
+ const c = expectDefined(candidateById.get(id));
643
644
  return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
644
645
  });
645
646
  return { results, total: candidates.length };
@@ -2056,6 +2057,7 @@ async function loadGitignoreMatcher(projectRoot) {
2056
2057
 
2057
2058
  // src/codebase-index/indexer.ts
2058
2059
  var YIELD_EVERY_N = 50;
2060
+ var PARALLEL_BATCH = 20;
2059
2061
  function yieldEventLoop() {
2060
2062
  return new Promise((resolve2) => setImmediate(resolve2));
2061
2063
  }
@@ -2189,97 +2191,111 @@ async function runIndexerWithStore(store, opts) {
2189
2191
  if (!force) {
2190
2192
  for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
2191
2193
  }
2192
- for (let fi = 0; fi < files.length; fi++) {
2193
- const file = expectDefined(files[fi]);
2194
- opts.onProgress?.(fi + 1, files.length);
2195
- if (fi > 0 && fi % YIELD_EVERY_N === 0) {
2194
+ for (let batchStart = 0; batchStart < files.length; batchStart += PARALLEL_BATCH) {
2195
+ const batchEnd = Math.min(batchStart + PARALLEL_BATCH, files.length);
2196
+ const batchFiles = files.slice(batchStart, batchEnd);
2197
+ opts.onProgress?.(batchEnd, files.length);
2198
+ if (batchStart > 0 && batchStart % YIELD_EVERY_N === 0) {
2196
2199
  await yieldEventLoop();
2197
2200
  throwIfAborted(signal);
2198
2201
  }
2199
- let stat2;
2200
- try {
2201
- const statOpts = signal ? { signal } : {};
2202
- stat2 = await fs3.stat(file, statOpts);
2203
- } catch (e) {
2204
- if (isAbortError(e)) throw e;
2205
- store.deleteFile(file);
2206
- continue;
2207
- }
2208
- if (!stat2.isFile()) continue;
2209
- const lang = detectLang(file);
2210
- if (!lang) continue;
2211
- const meta = existingMeta.get(file);
2212
- if (!force && meta && meta.mtimeMs === Math.floor(stat2.mtimeMs)) {
2213
- langStats[lang] = (langStats[lang] ?? 0) + meta.symbolCount;
2214
- symbolsIndexed += meta.symbolCount;
2215
- filesIndexed++;
2216
- continue;
2217
- }
2218
- store.deleteRefsForFile(file);
2219
- store.deleteSymbolsForFile(file);
2220
- let content;
2221
- try {
2222
- content = await fs3.readFile(file, { encoding: "utf8", signal });
2223
- } catch (e) {
2224
- if (isAbortError(e)) throw e;
2225
- errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
2226
- continue;
2227
- }
2228
- let parsed;
2229
- try {
2230
- parsed = await parseFile(file, content, lang);
2231
- } catch (e) {
2232
- errors.push(`parse error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
2233
- continue;
2234
- }
2235
- if (parsed.symbols.length === 0) {
2236
- store.upsertFile({
2237
- file,
2238
- lang,
2239
- mtimeMs: Math.floor(stat2.mtimeMs),
2240
- symbolCount: 0,
2241
- lastIndexed: Date.now()
2242
- });
2243
- filesIndexed++;
2244
- continue;
2245
- }
2246
- const nextId = store.getMaxSymbolId() + 1;
2247
- const symbolsWithIds = parsed.symbols.map((s, i) => ({ ...s, id: nextId + i }));
2248
- store.insertSymbols(symbolsWithIds, nextId);
2249
- const count = symbolsWithIds.length;
2250
- symbolsIndexed += count;
2251
- langStats[lang] = (langStats[lang] ?? 0) + count;
2252
- if (parsed.refs && parsed.refs.length > 0) {
2253
- const refsByLine = /* @__PURE__ */ new Map();
2254
- for (const r of parsed.refs) {
2255
- let arr = refsByLine.get(r.line);
2256
- if (!arr) {
2257
- arr = [];
2258
- refsByLine.set(r.line, arr);
2202
+ const statOpts = signal ? { signal } : {};
2203
+ const statReadParse = await Promise.allSettled(
2204
+ batchFiles.map(async (file) => {
2205
+ let stat2;
2206
+ try {
2207
+ stat2 = await fs3.stat(file, statOpts);
2208
+ } catch (e) {
2209
+ if (isAbortError(e)) throw e;
2210
+ return { file, stat: null, lang: "", parsed: null, error: `stat error: ${e instanceof Error ? e.message : String(e)}` };
2211
+ }
2212
+ if (!stat2.isFile()) return { file, stat: stat2, lang: "", parsed: null };
2213
+ const lang = detectLang(file);
2214
+ if (!lang) return { file, stat: stat2, lang: "", parsed: null };
2215
+ const meta = existingMeta.get(file);
2216
+ if (!force && meta && meta.mtimeMs === Math.floor(stat2.mtimeMs)) {
2217
+ return { file, stat: stat2, lang, parsed: null, skippedMeta: meta };
2218
+ }
2219
+ let content;
2220
+ try {
2221
+ content = await fs3.readFile(file, { encoding: "utf8", signal });
2222
+ } catch (e) {
2223
+ if (isAbortError(e)) throw e;
2224
+ return { file, stat: stat2, lang, parsed: null, error: `read error: ${e instanceof Error ? e.message : String(e)}` };
2225
+ }
2226
+ let parsed;
2227
+ try {
2228
+ parsed = await parseFile(file, content, lang);
2229
+ } catch (e) {
2230
+ return { file, stat: stat2, lang, parsed: null, error: `parse error: ${e instanceof Error ? e.message : String(e)}` };
2259
2231
  }
2260
- arr.push(r);
2232
+ return { file, stat: stat2, lang, parsed, content };
2233
+ })
2234
+ );
2235
+ for (let fi = 0; fi < statReadParse.length; fi++) {
2236
+ const settled = statReadParse[fi];
2237
+ const file = expectDefined(batchFiles[fi]);
2238
+ if (settled.status === "rejected") {
2239
+ const err = settled.reason;
2240
+ if (err instanceof Error && isAbortError(err)) throw err;
2241
+ errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
2242
+ continue;
2261
2243
  }
2262
- const batch = [];
2263
- for (const sym of symbolsWithIds) {
2264
- const symRefs = refsByLine.get(sym.line);
2265
- if (symRefs) {
2266
- for (const r of symRefs) {
2267
- batch.push({ ...r, fromId: sym.id });
2268
- }
2244
+ const result = settled.value;
2245
+ if (result.error) {
2246
+ if (result.stat) store.deleteFile(file);
2247
+ if (result.error.includes("error:")) errors.push(result.error);
2248
+ continue;
2249
+ }
2250
+ const { stat: stat2, lang, parsed } = result;
2251
+ if (result.skippedMeta) {
2252
+ langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
2253
+ symbolsIndexed += result.skippedMeta.symbolCount;
2254
+ filesIndexed++;
2255
+ continue;
2256
+ }
2257
+ if (!lang || !parsed) {
2258
+ if (lang) {
2259
+ store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: 0, lastIndexed: Date.now() });
2260
+ filesIndexed++;
2269
2261
  }
2262
+ continue;
2270
2263
  }
2271
- if (batch.length > 0) {
2272
- store.insertRefsBatch(batch);
2264
+ store.deleteRefsForFile(file);
2265
+ store.deleteSymbolsForFile(file);
2266
+ if (parsed.symbols.length === 0) {
2267
+ store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: 0, lastIndexed: Date.now() });
2268
+ filesIndexed++;
2269
+ continue;
2273
2270
  }
2271
+ const nextId = store.getMaxSymbolId() + 1;
2272
+ const symbolsWithIds = parsed.symbols.map((s, i) => ({ ...s, id: nextId + i }));
2273
+ store.insertSymbols(symbolsWithIds, nextId);
2274
+ const count = symbolsWithIds.length;
2275
+ symbolsIndexed += count;
2276
+ langStats[lang] = (langStats[lang] ?? 0) + count;
2277
+ if (parsed.refs && parsed.refs.length > 0) {
2278
+ const refsByLine = /* @__PURE__ */ new Map();
2279
+ for (const r of parsed.refs) {
2280
+ let arr = refsByLine.get(r.line);
2281
+ if (!arr) {
2282
+ arr = [];
2283
+ refsByLine.set(r.line, arr);
2284
+ }
2285
+ arr.push(r);
2286
+ }
2287
+ const batch = [];
2288
+ for (const sym of symbolsWithIds) {
2289
+ const symRefs = refsByLine.get(sym.line);
2290
+ if (symRefs) {
2291
+ for (const r of symRefs) batch.push({ ...r, fromId: sym.id });
2292
+ }
2293
+ }
2294
+ if (batch.length > 0) store.insertRefsBatch(batch);
2295
+ }
2296
+ store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: count, lastIndexed: Date.now() });
2297
+ filesIndexed++;
2274
2298
  }
2275
- store.upsertFile({
2276
- file,
2277
- lang,
2278
- mtimeMs: Math.floor(stat2.mtimeMs),
2279
- symbolCount: count,
2280
- lastIndexed: Date.now()
2281
- });
2282
- filesIndexed++;
2283
2299
  }
2284
2300
  for (const [file_] of existingMeta) {
2285
2301
  try {