@wrongstack/tools 0.289.0 → 0.291.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.
@@ -1 +1 @@
1
- {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/codebase-index/indexer.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,KAAK,EAAY,WAAW,EAA0C,MAAM,aAAa,CAAC;AA8DjG,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACrE;AA+HD,6DAA6D;AAC7D,wBAAsB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAa1F"}
1
+ {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/codebase-index/indexer.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,KAAK,EAAY,WAAW,EAA0C,MAAM,aAAa,CAAC;AAuEjG,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACrE;AA+HD,6DAA6D;AAC7D,wBAAsB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAa1F"}
@@ -1,25 +1,10 @@
1
- /**
2
- * Unified cross-reference extraction across all supported languages.
3
- *
4
- * `extractRefs(file, content, lang)` → returns `Ref[]`
5
- *
6
- * Each language has its own approach:
7
- * - TS/JS: uses the TypeScript Compiler API (imported from ts-parser.ts)
8
- * - Go: inline `go run <script>` script using go/ast to walk CallExpr, SelectorExpr, Ident, ImportSpec
9
- * - Python: inline `python -c` script using ast to walk Call, Name, Attribute, Subscript, Import, ImportFrom
10
- */
1
+ /** Cross-reference extraction for languages not handled by ts-parser.ts. */
11
2
  import type { Ref, SymbolLang } from './schema.js';
12
3
  export interface ExtractRefsOptions {
13
4
  file: string;
14
5
  content: string;
15
6
  lang: SymbolLang;
16
7
  }
17
- /**
18
- * Extract cross-references (calls, type refs, imports) from a source file.
19
- * Returns an array of `Ref` objects with `fromId: 0` (caller fills this in).
20
- *
21
- * Falls back to an empty array if the language is not supported or the
22
- * child process fails.
23
- */
8
+ /** Return refs with `fromId: 0`; the indexer replaces it with the owning symbol id. */
24
9
  export declare function extractRefs(opts: ExtractRefsOptions): Promise<Ref[]>;
25
10
  //# sourceMappingURL=refs-extractor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"refs-extractor.d.ts","sourceRoot":"","sources":["../../src/codebase-index/refs-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAkKnD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAmB1E"}
1
+ {"version":3,"file":"refs-extractor.d.ts","sourceRoot":"","sources":["../../src/codebase-index/refs-extractor.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAK5E,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAsFnD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,CAAC;CAClB;AAuGD,uFAAuF;AACvF,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAU1E"}
@@ -1 +1 @@
1
- {"version":3,"file":"ts-parser.d.ts","sourceRoot":"","sources":["../../src/codebase-index/ts-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,WAAW,EAA0C,UAAU,EAAE,MAAM,aAAa,CAAC;AA6HnG,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CA8D5D;AAqED,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAI1D"}
1
+ {"version":3,"file":"ts-parser.d.ts","sourceRoot":"","sources":["../../src/codebase-index/ts-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,WAAW,EAA0C,UAAU,EAAE,MAAM,aAAa,CAAC;AA6GnG,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CA0G5D;AA+BD,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAI1D"}
@@ -5,6 +5,7 @@ import { parentPort } from "node:worker_threads";
5
5
  import { expectDefined as expectDefined5 } from "@wrongstack/core";
6
6
  import * as fs7 from "node:fs/promises";
7
7
  import * as path8 from "node:path";
8
+ import { availableParallelism } from "node:os";
8
9
  import { compileGlob as compileGlob2 } from "@wrongstack/core";
9
10
 
10
11
  // src/codebase-index/writer.ts
@@ -244,6 +245,53 @@ function assignRefsToSymbols(refs, symbols) {
244
245
  function resolveIndexDir(projectRoot, override) {
245
246
  return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
246
247
  }
248
+ var StorePool = class {
249
+ stores = /* @__PURE__ */ new Map();
250
+ key(projectRoot, indexDir) {
251
+ return `${projectRoot}\0${indexDir ?? ""}`;
252
+ }
253
+ /** Borrow a store. Creates it on first access for this key. */
254
+ acquire(projectRoot, opts) {
255
+ const k = this.key(projectRoot, opts?.indexDir);
256
+ let store = this.stores.get(k);
257
+ if (!store) {
258
+ store = new IndexStore(projectRoot, { indexDir: opts?.indexDir });
259
+ this.stores.set(k, store);
260
+ }
261
+ return store;
262
+ }
263
+ /** Return the store to the pool. The connection stays warm for subsequent
264
+ * operations on the same (projectRoot, indexDir). */
265
+ release(_store) {
266
+ }
267
+ /** Close every pooled connection and drain the pool. Call on shutdown. */
268
+ closeAll() {
269
+ for (const store of this.stores.values()) {
270
+ try {
271
+ store.close();
272
+ } catch {
273
+ }
274
+ }
275
+ this.stores.clear();
276
+ }
277
+ /** Remove one store from the pool. Used by tests that need isolation. */
278
+ evict(projectRoot, indexDir) {
279
+ const k = this.key(projectRoot, indexDir);
280
+ const store = this.stores.get(k);
281
+ if (store) {
282
+ try {
283
+ store.close();
284
+ } catch {
285
+ }
286
+ this.stores.delete(k);
287
+ }
288
+ }
289
+ /** True when the pool holds a connection for the given key. */
290
+ has(projectRoot, indexDir) {
291
+ return this.stores.has(this.key(projectRoot, indexDir));
292
+ }
293
+ };
294
+ var indexStorePool = new StorePool();
247
295
  var warningSilenced = false;
248
296
  function silenceSqliteExperimentalWarning() {
249
297
  if (warningSilenced) return;
@@ -461,7 +509,7 @@ var IndexStore = class _IndexStore {
461
509
  return this.runWithRetry(() => {
462
510
  this.db.exec("BEGIN IMMEDIATE");
463
511
  try {
464
- const maxRows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
512
+ const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
465
513
  let nextId = (maxRows[0]?.m ?? 0) + 1;
466
514
  const stmt = this.db.prepare(
467
515
  `INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
@@ -733,7 +781,7 @@ var IndexStore = class _IndexStore {
733
781
  return { results, total: candidates.length };
734
782
  }
735
783
  getAllIndexable() {
736
- return this.db.prepare("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
784
+ return this.stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
737
785
  }
738
786
  /**
739
787
  * Largest symbol id currently in the table (0 when empty). New ids must be
@@ -743,7 +791,7 @@ var IndexStore = class _IndexStore {
743
791
  * `symbols.id`). Ids may have gaps — that is fine.
744
792
  */
745
793
  getMaxSymbolId() {
746
- const rows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
794
+ const rows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
747
795
  return rows[0]?.m ?? 0;
748
796
  }
749
797
  // ─── Stats ───────────────────────────────────────────────────────────────────
@@ -751,14 +799,14 @@ var IndexStore = class _IndexStore {
751
799
  const sizeBytes = this.sizeBytes();
752
800
  const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
753
801
  const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
754
- const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
802
+ const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
755
803
  const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
756
- const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
804
+ const fileRows = this.stmt("SELECT COUNT(*) FROM files").all();
757
805
  const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
758
- const langRows = this.db.prepare("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
806
+ const langRows = this.stmt("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
759
807
  const byLang = {};
760
808
  for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
761
- const kindRows = this.db.prepare("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
809
+ const kindRows = this.stmt("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
762
810
  const byKind = {};
763
811
  for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
764
812
  return {
@@ -790,11 +838,14 @@ var IndexStore = class _IndexStore {
790
838
  this.runWithRetry(() => {
791
839
  this.db.exec("BEGIN IMMEDIATE");
792
840
  try {
793
- this.db.exec("DELETE FROM refs");
794
- this.db.exec("DELETE FROM symbols");
795
- this.db.exec("DELETE FROM files");
796
- if (this.ftsAvailable) this.db.exec("DELETE FROM symbols_fts");
841
+ this.db.exec("DROP TABLE IF EXISTS refs");
842
+ this.db.exec("DROP TABLE IF EXISTS symbols");
843
+ this.db.exec("DROP TABLE IF EXISTS files");
844
+ this.db.exec("DROP TABLE IF EXISTS metadata");
845
+ if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
797
846
  this.db.exec("COMMIT");
847
+ this.stmtCache.clear();
848
+ this.initSchema();
798
849
  } catch (err) {
799
850
  this.db.exec("ROLLBACK");
800
851
  throw err;
@@ -879,7 +930,7 @@ var IndexStore = class _IndexStore {
879
930
  ).run(...options.deleteForFiles);
880
931
  this.db.prepare(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
881
932
  }
882
- const maxRows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
933
+ const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
883
934
  let nextId = (maxRows[0]?.m ?? 0) + 1;
884
935
  const symStmt = this.db.prepare(
885
936
  `INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
@@ -1063,7 +1114,7 @@ var IndexStore = class _IndexStore {
1063
1114
  * symbol resolved in package B). Node metadata includes symbol/file counts.
1064
1115
  */
1065
1116
  getPackageGraph() {
1066
- const symbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
1117
+ const symbols = this.db.prepare("SELECT file, id FROM symbols ORDER BY id").all();
1067
1118
  const pkgNodes = /* @__PURE__ */ new Map();
1068
1119
  const fileToPkg = /* @__PURE__ */ new Map();
1069
1120
  const symbolToPkg = /* @__PURE__ */ new Map();
@@ -1154,15 +1205,18 @@ var IndexStore = class _IndexStore {
1154
1205
  * derived from cross-file symbol references within the package.
1155
1206
  */
1156
1207
  getFileGraph(packageFilter) {
1157
- const allSymbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
1158
- const pkgSyms = allSymbols.filter(
1159
- (s) => (_IndexStore.derivePackage(s.file) ?? "(root)") === packageFilter
1160
- );
1161
- if (pkgSyms.length === 0) return { nodes: [], edges: [] };
1208
+ const allFiles = this.db.prepare("SELECT DISTINCT file FROM symbols").all();
1209
+ const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
1210
+ const localFiles = new Set(pkgFilePaths);
1211
+ if (localFiles.size === 0) return { nodes: [], edges: [] };
1212
+ const filePlaceholders = [...localFiles].map(() => "?").join(",");
1213
+ const pkgSyms = this.db.prepare(
1214
+ `SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
1215
+ ).all(...pkgFilePaths);
1162
1216
  const fileNodes = /* @__PURE__ */ new Map();
1163
1217
  const symToFile = /* @__PURE__ */ new Map();
1164
1218
  const fileStats = /* @__PURE__ */ new Map();
1165
- for (const s of allSymbols) {
1219
+ for (const s of pkgSyms) {
1166
1220
  symToFile.set(s.id, s.file);
1167
1221
  const current = fileStats.get(s.file);
1168
1222
  fileStats.set(s.file, {
@@ -1170,8 +1224,6 @@ var IndexStore = class _IndexStore {
1170
1224
  lang: current?.lang ?? s.lang
1171
1225
  });
1172
1226
  }
1173
- const localFiles = new Set(pkgSyms.map((s) => s.file));
1174
- const indexedFiles = new Set(allSymbols.map((s) => s.file));
1175
1227
  const ensureFileNode = (file) => {
1176
1228
  if (fileNodes.has(file)) return;
1177
1229
  const stats = fileStats.get(file);
@@ -1189,11 +1241,32 @@ var IndexStore = class _IndexStore {
1189
1241
  for (const file of localFiles) {
1190
1242
  ensureFileNode(file);
1191
1243
  }
1244
+ const indexedFiles = new Set(allFiles.map((f) => f.file));
1192
1245
  const refRows = this.db.prepare(
1193
1246
  `SELECT r.from_id, r.to_id, r.call_type
1194
1247
  FROM refs r
1195
- WHERE r.to_id IS NOT NULL`
1196
- ).all();
1248
+ WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
1249
+ OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
1250
+ AND r.to_id IS NOT NULL`
1251
+ ).all(...pkgFilePaths, ...pkgFilePaths);
1252
+ const knownSymIds = new Set(pkgSyms.map((s) => s.id));
1253
+ const crossRefIds = /* @__PURE__ */ new Set();
1254
+ for (const r of refRows) {
1255
+ if (!knownSymIds.has(r.from_id)) crossRefIds.add(r.from_id);
1256
+ if (!knownSymIds.has(r.to_id)) crossRefIds.add(r.to_id);
1257
+ }
1258
+ if (crossRefIds.size > 0) {
1259
+ const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
1260
+ const extras = this.db.prepare(
1261
+ `SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
1262
+ ).all(...crossRefIds);
1263
+ for (const x of extras) {
1264
+ symToFile.set(x.id, x.file);
1265
+ if (!fileStats.has(x.file)) {
1266
+ fileStats.set(x.file, { count: 0, lang: "ts" });
1267
+ }
1268
+ }
1269
+ }
1197
1270
  const edgeMap = /* @__PURE__ */ new Map();
1198
1271
  for (const r of refRows) {
1199
1272
  if (r.call_type === "import") continue;
@@ -1215,8 +1288,9 @@ var IndexStore = class _IndexStore {
1215
1288
  const importRows = this.db.prepare(
1216
1289
  `SELECT r.from_id, r.to_name
1217
1290
  FROM refs r
1218
- WHERE r.call_type = 'import'`
1219
- ).all();
1291
+ WHERE r.call_type = 'import'
1292
+ AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))`
1293
+ ).all(...pkgFilePaths);
1220
1294
  for (const r of importRows) {
1221
1295
  const fromFile = symToFile.get(r.from_id);
1222
1296
  if (!fromFile || !localFiles.has(fromFile)) continue;
@@ -1258,12 +1332,11 @@ var IndexStore = class _IndexStore {
1258
1332
  * derived from intra-file and cross-file symbol references (who calls whom).
1259
1333
  */
1260
1334
  getSymbolGraph(fileFilter) {
1261
- const allSymbols = this.db.prepare(
1262
- "SELECT id, name, kind, lang, file, line, signature, scope FROM symbols ORDER BY file, line, id"
1263
- ).all();
1264
- const syms = allSymbols.filter((symbol) => symbol.file === fileFilter);
1335
+ const syms = this.db.prepare(
1336
+ "SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
1337
+ ).all(fileFilter);
1265
1338
  if (syms.length === 0) return { nodes: [], edges: [] };
1266
- const symById = new Map(allSymbols.map((symbol) => [symbol.id, symbol]));
1339
+ const symById = new Map(syms.map((symbol) => [symbol.id, symbol]));
1267
1340
  const relatedIds = new Set(syms.map((symbol) => symbol.id));
1268
1341
  const toGraphNode = (s) => ({
1269
1342
  id: `sym:${s.id}`,
@@ -1319,6 +1392,15 @@ var IndexStore = class _IndexStore {
1319
1392
  refType: bestType
1320
1393
  });
1321
1394
  }
1395
+ const loadedIds = new Set(syms.map((s) => s.id));
1396
+ const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
1397
+ if (missingIds.length > 0) {
1398
+ const placeholders = missingIds.map(() => "?").join(",");
1399
+ const extras = this.db.prepare(
1400
+ `SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
1401
+ ).all(...missingIds);
1402
+ for (const s of extras) symById.set(s.id, s);
1403
+ }
1322
1404
  const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
1323
1405
  const aExternal = a.file === fileFilter ? 0 : 1;
1324
1406
  const bExternal = b.file === fileFilter ? 0 : 1;
@@ -1389,8 +1471,7 @@ function extToLang(ext) {
1389
1471
  return null;
1390
1472
  }
1391
1473
  }
1392
- function getSignature(node, sourceFile) {
1393
- const printer = ts.createPrinter({});
1474
+ function getSignature(printer, node, sourceFile) {
1394
1475
  const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
1395
1476
  return raw.replace(/\s+/g, " ").slice(0, 500);
1396
1477
  }
@@ -1409,28 +1490,14 @@ function getJsDoc(node, sourceFile) {
1409
1490
  }
1410
1491
  return "";
1411
1492
  }
1412
- function hasFunctionLikeAncestor(node) {
1413
- let current = node.parent;
1414
- while (current) {
1415
- if (ts.isFunctionLike(current)) return true;
1416
- current = current.parent;
1417
- }
1418
- return false;
1419
- }
1420
- function buildScope(node) {
1421
- const parts = [];
1422
- let current = node.parent;
1423
- while (current) {
1424
- if (ts.isClassDeclaration(current) || ts.isInterfaceDeclaration(current) || ts.isEnumDeclaration(current) || ts.isTypeAliasDeclaration(current)) {
1425
- parts.unshift(current.name?.text ?? "Anon");
1426
- } else if (ts.isMethodDeclaration(current) || ts.isGetAccessor(current) || ts.isSetAccessor(current) || ts.isPropertyDeclaration(current) || ts.isFunctionDeclaration(current)) {
1427
- if (current.name && ts.isIdentifier(current.name)) {
1428
- parts.unshift(current.name.text);
1429
- }
1493
+ function pushScopeName(node, parts) {
1494
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
1495
+ parts.push(node.name?.text ?? "Anon");
1496
+ } else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
1497
+ if (node.name && ts.isIdentifier(node.name)) {
1498
+ parts.push(node.name.text);
1430
1499
  }
1431
- current = current.parent;
1432
1500
  }
1433
- return parts.join(".");
1434
1501
  }
1435
1502
  function parseSymbols(opts) {
1436
1503
  const { file, content, lang } = opts;
@@ -1441,45 +1508,39 @@ function parseSymbols(opts) {
1441
1508
  return { file, lang, symbols: [], mtimeMs: Date.now() };
1442
1509
  }
1443
1510
  const symbols = [];
1444
- function visit(node) {
1511
+ const refs = [];
1512
+ const printer = ts.createPrinter({});
1513
+ function visit(node, funcDepth, scopeParts) {
1445
1514
  const kind = kindOf(node);
1446
1515
  if (kind) {
1447
- if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && hasFunctionLikeAncestor(node)) {
1448
- ts.forEachChild(node, visit);
1449
- return;
1516
+ if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
1517
+ } else {
1518
+ const nameNode = node.name;
1519
+ if (!nameNode || !ts.isIdentifier(nameNode)) {
1520
+ return;
1521
+ }
1522
+ const name = nameNode.text;
1523
+ const pos2 = nameNode.getStart(sourceFile);
1524
+ const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
1525
+ const scope = scopeParts.join(".");
1526
+ const signature = getSignature(printer, node, sourceFile);
1527
+ const docComment = getJsDoc(node, sourceFile);
1528
+ const text = [name, signature, docComment].filter(Boolean).join(" | ");
1529
+ symbols.push({
1530
+ id: 0,
1531
+ lang,
1532
+ kind,
1533
+ name,
1534
+ file,
1535
+ line: line2 + 1,
1536
+ col: character,
1537
+ signature,
1538
+ docComment,
1539
+ scope,
1540
+ text
1541
+ });
1450
1542
  }
1451
- const nameNode = node.name;
1452
- if (!nameNode || !ts.isIdentifier(nameNode)) return;
1453
- const name = nameNode.text;
1454
- const pos = nameNode.getStart(sourceFile);
1455
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos);
1456
- const scope = buildScope(node);
1457
- const signature = getSignature(node, sourceFile);
1458
- const docComment = getJsDoc(node, sourceFile);
1459
- const text = [name, signature, docComment].filter(Boolean).join(" | ");
1460
- symbols.push({
1461
- id: 0,
1462
- lang,
1463
- kind,
1464
- name,
1465
- file,
1466
- line: line + 1,
1467
- col: character,
1468
- signature,
1469
- docComment,
1470
- scope,
1471
- text
1472
- });
1473
1543
  }
1474
- ts.forEachChild(node, visit);
1475
- }
1476
- visit(sourceFile);
1477
- const refs = extractRefs(sourceFile);
1478
- return { file, lang, symbols, refs, mtimeMs: Date.now() };
1479
- }
1480
- function extractRefs(sourceFile) {
1481
- const refs = [];
1482
- function visit(node) {
1483
1544
  const pos = node.getStart(sourceFile);
1484
1545
  const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
1485
1546
  const lineNum = line + 1;
@@ -1504,10 +1565,14 @@ function extractRefs(sourceFile) {
1504
1565
  const moduleName = getModuleName(node);
1505
1566
  if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
1506
1567
  }
1507
- ts.forEachChild(node, visit);
1568
+ const scopeIdx = scopeParts.length;
1569
+ pushScopeName(node, scopeParts);
1570
+ const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
1571
+ ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
1572
+ scopeParts.length = scopeIdx;
1508
1573
  }
1509
- visit(sourceFile);
1510
- return deduplicateRefs(refs);
1574
+ visit(sourceFile, 0, []);
1575
+ return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
1511
1576
  }
1512
1577
  function getTypeName(name) {
1513
1578
  if (ts.isIdentifier(name)) return name.text;
@@ -2812,7 +2877,7 @@ async function loadGitignoreMatcher(projectRoot) {
2812
2877
 
2813
2878
  // src/codebase-index/indexer.ts
2814
2879
  var YIELD_EVERY_N = 50;
2815
- var PARALLEL_BATCH = 20;
2880
+ var PARALLEL_BATCH = Math.min(availableParallelism() * 4, 40);
2816
2881
  function yieldEventLoop() {
2817
2882
  return new Promise((resolve2) => setImmediate(resolve2));
2818
2883
  }
@@ -2990,11 +3055,14 @@ async function runIndexerWithStore(store, opts) {
2990
3055
  if (!force) {
2991
3056
  for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
2992
3057
  }
3058
+ let filesSinceLastYield = 0;
2993
3059
  for (let batchStart = 0; batchStart < files.length; batchStart += PARALLEL_BATCH) {
2994
3060
  const batchEnd = Math.min(batchStart + PARALLEL_BATCH, files.length);
2995
3061
  const batchFiles = files.slice(batchStart, batchEnd);
2996
3062
  opts.onProgress?.(batchEnd, files.length);
2997
- if (batchStart > 0 && batchStart % YIELD_EVERY_N === 0) {
3063
+ filesSinceLastYield += batchFiles.length;
3064
+ if (filesSinceLastYield >= YIELD_EVERY_N) {
3065
+ filesSinceLastYield = 0;
2998
3066
  await yieldEventLoop();
2999
3067
  throwIfAborted(signal);
3000
3068
  }
@@ -3205,7 +3273,7 @@ async function indexService(args, hooks = {}) {
3205
3273
  });
3206
3274
  }
3207
3275
  function searchService(args) {
3208
- const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
3276
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
3209
3277
  try {
3210
3278
  return store.searchRanked(
3211
3279
  args.query,
@@ -3218,15 +3286,15 @@ function searchService(args) {
3218
3286
  args.limit
3219
3287
  );
3220
3288
  } finally {
3221
- store.close();
3289
+ indexStorePool.release(store);
3222
3290
  }
3223
3291
  }
3224
3292
  function statsService(args) {
3225
- const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
3293
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
3226
3294
  try {
3227
3295
  return store.getStats();
3228
3296
  } finally {
3229
- store.close();
3297
+ indexStorePool.release(store);
3230
3298
  }
3231
3299
  }
3232
3300