@wrongstack/tools 0.293.0 → 0.295.1
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.
- package/dist/_env.d.ts +1 -1
- package/dist/_env.d.ts.map +1 -1
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/_util.d.ts +1 -1
- package/dist/_util.d.ts.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +81 -16
- package/dist/audit.js.map +2 -2
- package/dist/auto-proceed-loop-guard.d.ts +31 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -1
- package/dist/auto-proceed-loop-guard.js +24 -1
- package/dist/auto-proceed-loop-guard.js.map +2 -2
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +105 -75
- package/dist/bash.js.map +3 -3
- package/dist/batch-tool-use.d.ts +1 -1
- package/dist/batch-tool-use.d.ts.map +1 -1
- package/dist/batch-tool-use.js +1 -1
- package/dist/batch-tool-use.js.map +1 -1
- package/dist/browser/index.js +10 -1
- package/dist/browser/index.js.map +2 -2
- package/dist/browser/manager.d.ts +2 -0
- package/dist/browser/manager.d.ts.map +1 -1
- package/dist/browser/tools.d.ts +1 -1
- package/dist/browser/tools.d.ts.map +1 -1
- package/dist/builtin.d.ts +1 -1
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1650 -640
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts +2 -2
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +3 -1
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/generic-parser.d.ts +34 -0
- package/dist/codebase-index/generic-parser.d.ts.map +1 -0
- package/dist/codebase-index/go-parser.d.ts +1 -1
- package/dist/codebase-index/go-parser.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +4 -2
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +1068 -372
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts +6 -1
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/json-parser.d.ts +1 -1
- package/dist/codebase-index/languages.d.ts +25 -0
- package/dist/codebase-index/languages.d.ts.map +1 -0
- package/dist/codebase-index/py-parser.d.ts +9 -1
- package/dist/codebase-index/py-parser.d.ts.map +1 -1
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/rs-parser.d.ts +1 -1
- package/dist/codebase-index/rs-parser.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +9 -2
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/spawn-gate.d.ts +15 -0
- package/dist/codebase-index/spawn-gate.d.ts.map +1 -0
- package/dist/codebase-index/ts-parser.d.ts +2 -2
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +993 -348
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer.d.ts +83 -4
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/codebase-index/yaml-parser.d.ts +1 -1
- package/dist/design.d.ts +5 -2
- package/dist/design.d.ts.map +1 -1
- package/dist/design.js +36 -3
- package/dist/design.js.map +2 -2
- package/dist/diff.d.ts +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +2 -2
- package/dist/diff.js.map +1 -1
- package/dist/document.d.ts +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +1 -1
- package/dist/document.js.map +1 -1
- package/dist/e2e.d.ts +1 -1
- package/dist/e2e.d.ts.map +1 -1
- package/dist/e2e.js +1 -1
- package/dist/e2e.js.map +1 -1
- package/dist/edit.d.ts +1 -1
- package/dist/edit.d.ts.map +1 -1
- package/dist/edit.js +3 -3
- package/dist/edit.js.map +2 -2
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +99 -44
- package/dist/exec.js.map +3 -3
- package/dist/fetch.d.ts +1 -1
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +4 -5
- package/dist/fetch.js.map +2 -2
- package/dist/format.d.ts +1 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +81 -16
- package/dist/format.js.map +2 -2
- package/dist/git.d.ts +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +3 -2
- package/dist/git.js.map +2 -2
- package/dist/glob.d.ts +1 -1
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +81 -31
- package/dist/glob.js.map +4 -4
- package/dist/grep.d.ts +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +90 -22
- package/dist/grep.js.map +4 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1895 -771
- package/dist/index.js.map +4 -4
- package/dist/install.d.ts +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +85 -17
- package/dist/install.js.map +2 -2
- package/dist/json.d.ts +1 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -2
- package/dist/json.js.map +2 -2
- package/dist/kanban-evidence-bridge.d.ts +21 -0
- package/dist/kanban-evidence-bridge.d.ts.map +1 -0
- package/dist/kanban.d.ts +20 -3
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +256 -32
- package/dist/kanban.js.map +3 -3
- package/dist/languages/execute-tool.d.ts +1 -1
- package/dist/languages/execute-tool.d.ts.map +1 -1
- package/dist/languages/execute.d.ts +1 -1
- package/dist/languages/execute.d.ts.map +1 -1
- package/dist/languages/index.d.ts +1 -1
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +99 -17
- package/dist/languages/index.js.map +2 -2
- package/dist/languages/package-tool.d.ts +3 -4
- package/dist/languages/package-tool.d.ts.map +1 -1
- package/dist/languages/plan.d.ts.map +1 -1
- package/dist/languages/profiles/additional.d.ts.map +1 -1
- package/dist/languages/profiles/primary.d.ts.map +1 -1
- package/dist/languages/tool.d.ts +1 -1
- package/dist/languages/tool.d.ts.map +1 -1
- package/dist/lint.d.ts +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +81 -16
- package/dist/lint.js.map +2 -2
- package/dist/logs.d.ts +1 -1
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +2 -2
- package/dist/logs.js.map +1 -1
- package/dist/memory.d.ts +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +1 -1
- package/dist/memory.js.map +1 -1
- package/dist/mode.d.ts +1 -1
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/outdated.d.ts +1 -1
- package/dist/outdated.d.ts.map +1 -1
- package/dist/outdated.js +101 -19
- package/dist/outdated.js.map +2 -2
- package/dist/pack.d.ts +1 -1
- package/dist/pack.js +1650 -640
- package/dist/pack.js.map +4 -4
- package/dist/patch.d.ts +1 -1
- package/dist/patch.d.ts.map +1 -1
- package/dist/patch.js +2 -2
- package/dist/patch.js.map +1 -1
- package/dist/plan.d.ts +1 -1
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +7 -7
- package/dist/plan.js.map +2 -2
- package/dist/process-guardian.d.ts +7 -0
- package/dist/process-guardian.d.ts.map +1 -1
- package/dist/process-registry-persistent.d.ts +5 -0
- package/dist/process-registry-persistent.d.ts.map +1 -1
- package/dist/process-registry.d.ts +9 -1
- package/dist/process-registry.d.ts.map +1 -1
- package/dist/process-registry.js +18 -3
- package/dist/process-registry.js.map +2 -2
- package/dist/ps-slash.js +93 -38
- package/dist/ps-slash.js.map +2 -2
- package/dist/read.d.ts +1 -1
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +3 -2
- package/dist/read.js.map +2 -2
- package/dist/replace.d.ts +1 -1
- package/dist/replace.d.ts.map +1 -1
- package/dist/replace.js +4 -4
- package/dist/replace.js.map +2 -2
- package/dist/scaffold.d.ts +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +2 -2
- package/dist/scaffold.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +4 -4
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +4 -1
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +23 -15
- package/dist/session-kanban.js.map +2 -2
- package/dist/set-working-dir.d.ts +1 -1
- package/dist/set-working-dir.d.ts.map +1 -1
- package/dist/skill.d.ts +1 -1
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +2 -5
- package/dist/skill.js.map +2 -2
- package/dist/spawn-background.d.ts.map +1 -1
- package/dist/task.d.ts +3 -2
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +6 -8
- package/dist/task.js.map +2 -2
- package/dist/test.d.ts +1 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +81 -16
- package/dist/test.js.map +2 -2
- package/dist/todo.d.ts +2 -1
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +4 -4
- package/dist/todo.js.map +2 -2
- package/dist/tool-help.d.ts +1 -1
- package/dist/tool-help.d.ts.map +1 -1
- package/dist/tool-help.js.map +1 -1
- package/dist/tool-search.d.ts +1 -1
- package/dist/tool-search.d.ts.map +1 -1
- package/dist/tool-search.js.map +1 -1
- package/dist/tool-tier.d.ts +16 -0
- package/dist/tool-tier.d.ts.map +1 -0
- package/dist/tool-tier.js +22402 -0
- package/dist/tool-tier.js.map +7 -0
- package/dist/tool-use.d.ts +1 -1
- package/dist/tool-use.d.ts.map +1 -1
- package/dist/tool-use.js.map +1 -1
- package/dist/tree.d.ts +1 -1
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +3 -15
- package/dist/tree.js.map +2 -2
- package/dist/typecheck.d.ts +1 -1
- package/dist/typecheck.d.ts.map +1 -1
- package/dist/typecheck.js +81 -16
- package/dist/typecheck.js.map +2 -2
- package/dist/win32.d.ts +10 -0
- package/dist/win32.d.ts.map +1 -0
- package/dist/win32.js +67 -0
- package/dist/win32.js.map +7 -0
- package/dist/write.d.ts +1 -1
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +3 -2
- package/dist/write.js.map +2 -2
- package/package.json +11 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/codebase-index/writer.ts
|
|
2
|
-
import { expectDefined } from "@wrongstack/core";
|
|
2
|
+
import { expectDefined } from "@wrongstack/core/utils";
|
|
3
3
|
|
|
4
4
|
// src/codebase-index/circuit-breaker.ts
|
|
5
5
|
var CircuitOpenError = class extends Error {
|
|
@@ -89,7 +89,7 @@ import { toErrorMessage } from "@wrongstack/core/utils";
|
|
|
89
89
|
import { createRequire } from "node:module";
|
|
90
90
|
import * as fs from "node:fs";
|
|
91
91
|
import * as path from "node:path";
|
|
92
|
-
import { resolveWstackPaths } from "@wrongstack/core";
|
|
92
|
+
import { resolveWstackPaths, sqliteCachePragmas } from "@wrongstack/core/utils";
|
|
93
93
|
|
|
94
94
|
// src/codebase-index/schema.ts
|
|
95
95
|
var SCHEMA_VERSION = 3;
|
|
@@ -174,23 +174,13 @@ function buildBm25Index(docs) {
|
|
|
174
174
|
const tokens = tokenise(d.text);
|
|
175
175
|
return { id: d.id, tokens, raw: d.text, len: tokens.length };
|
|
176
176
|
});
|
|
177
|
-
const df = {};
|
|
178
|
-
for (const doc of documents) {
|
|
179
|
-
const seen = /* @__PURE__ */ new Set();
|
|
180
|
-
for (const t of doc.tokens) {
|
|
181
|
-
if (!seen.has(t)) {
|
|
182
|
-
df[t] = (df[t] ?? 0) + 1;
|
|
183
|
-
seen.add(t);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
177
|
const N = documents.length;
|
|
188
178
|
const totalLen = documents.reduce((sum, d) => sum + d.len, 0);
|
|
189
179
|
const avgLen = N === 0 ? 0 : totalLen / N;
|
|
190
|
-
return new Bm25Index(documents,
|
|
180
|
+
return new Bm25Index(documents, N, avgLen);
|
|
191
181
|
}
|
|
192
182
|
var Bm25Index = class {
|
|
193
|
-
constructor(documents,
|
|
183
|
+
constructor(documents, N, avgLen) {
|
|
194
184
|
this.documents = documents;
|
|
195
185
|
this.N = N;
|
|
196
186
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
@@ -198,9 +188,19 @@ var Bm25Index = class {
|
|
|
198
188
|
documents;
|
|
199
189
|
N;
|
|
200
190
|
safeAvgLen;
|
|
191
|
+
/** Lazily-built id→doc index so getDoc is O(1) instead of an O(D) linear find. */
|
|
192
|
+
_byId;
|
|
201
193
|
score(query, filter) {
|
|
202
194
|
const qTokens = tokenise(query);
|
|
203
195
|
if (qTokens.length === 0) return [];
|
|
196
|
+
const dfByTerm = /* @__PURE__ */ new Map();
|
|
197
|
+
for (const qTerm of qTokens) {
|
|
198
|
+
let dfVal = 0;
|
|
199
|
+
for (const candidate of this.documents) {
|
|
200
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
201
|
+
}
|
|
202
|
+
dfByTerm.set(qTerm, dfVal);
|
|
203
|
+
}
|
|
204
204
|
const results = [];
|
|
205
205
|
for (const doc of this.documents) {
|
|
206
206
|
if (filter && !filter(doc.id)) continue;
|
|
@@ -211,10 +211,7 @@ var Bm25Index = class {
|
|
|
211
211
|
if (t.startsWith(qTerm)) tf++;
|
|
212
212
|
}
|
|
213
213
|
if (tf === 0) continue;
|
|
214
|
-
|
|
215
|
-
for (const candidate of this.documents) {
|
|
216
|
-
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
217
|
-
}
|
|
214
|
+
const dfVal = dfByTerm.get(qTerm) ?? 0;
|
|
218
215
|
if (dfVal === 0) continue;
|
|
219
216
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
220
217
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -226,7 +223,10 @@ var Bm25Index = class {
|
|
|
226
223
|
return results;
|
|
227
224
|
}
|
|
228
225
|
getDoc(id) {
|
|
229
|
-
|
|
226
|
+
if (!this._byId) {
|
|
227
|
+
this._byId = new Map(this.documents.map((d) => [d.id, d]));
|
|
228
|
+
}
|
|
229
|
+
return this._byId.get(id);
|
|
230
230
|
}
|
|
231
231
|
extractSnippet(docId, queryTokens, radius = 40) {
|
|
232
232
|
const doc = this.getDoc(docId);
|
|
@@ -388,6 +388,25 @@ var IndexStore = class _IndexStore {
|
|
|
388
388
|
* and reuse it. Cleared in {@link close} when the connection is torn down.
|
|
389
389
|
*/
|
|
390
390
|
stmtCache = /* @__PURE__ */ new Map();
|
|
391
|
+
/**
|
|
392
|
+
* Cached full-corpus BM25 index for the FTS5-unavailable fallback path.
|
|
393
|
+
* Built lazily on the first `searchRankedFallback` call and invalidated
|
|
394
|
+
* (via `bm25Dirty`) whenever the `symbols` table is mutated. Computing
|
|
395
|
+
* IDF over the full corpus is also more correct than the old per-query
|
|
396
|
+
* candidate-subset IDF.
|
|
397
|
+
*
|
|
398
|
+
* Cache-lifecycle invariants (single source of truth lives at the
|
|
399
|
+
* `invalidateBm25()` helper — see its docblock for the "every mutation
|
|
400
|
+
* MUST call this" contract):
|
|
401
|
+
* - declaration: this field + `bm25Dirty` (here)
|
|
402
|
+
* - invalidation: `invalidateBm25()` flips the flag and nulls the cache
|
|
403
|
+
* - build: `getOrBuildBm25()` rebuilds against current `symbols` rows
|
|
404
|
+
* - teardown: `close()` resets the flag and nulls the cache
|
|
405
|
+
*/
|
|
406
|
+
bm25Cache = null;
|
|
407
|
+
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
408
|
+
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
409
|
+
bm25Dirty = true;
|
|
391
410
|
/** Prepare-once helper: compile `sql` on first use, reuse thereafter. */
|
|
392
411
|
stmt(sql) {
|
|
393
412
|
let s = this.stmtCache.get(sql);
|
|
@@ -435,7 +454,15 @@ var IndexStore = class _IndexStore {
|
|
|
435
454
|
this.db = new Database(path.join(this.indexDir, DB_FILE));
|
|
436
455
|
try {
|
|
437
456
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
438
|
-
this.db.exec("PRAGMA
|
|
457
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
458
|
+
this.db.exec("PRAGMA busy_timeout = 15000");
|
|
459
|
+
this.db.exec("PRAGMA temp_store = MEMORY");
|
|
460
|
+
const cache = sqliteCachePragmas();
|
|
461
|
+
this.db.exec(`PRAGMA cache_size = -${cache.cacheSizeKiB}`);
|
|
462
|
+
this.db.exec(`PRAGMA mmap_size = ${cache.mmapBytes}`);
|
|
463
|
+
this.db.exec("PRAGMA foreign_keys = ON");
|
|
464
|
+
this.db.exec("PRAGMA journal_size_limit = 67108864");
|
|
465
|
+
this.db.exec("PRAGMA wal_autocheckpoint = 1000");
|
|
439
466
|
} catch {
|
|
440
467
|
}
|
|
441
468
|
this.initSchema();
|
|
@@ -447,7 +474,7 @@ var IndexStore = class _IndexStore {
|
|
|
447
474
|
value TEXT NOT NULL
|
|
448
475
|
);
|
|
449
476
|
`);
|
|
450
|
-
const storedRows = this.
|
|
477
|
+
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
451
478
|
const storedVersion = storedRows.length ? Number(storedRows[0]?.value) : null;
|
|
452
479
|
if (storedVersion !== null && storedVersion !== SCHEMA_VERSION) {
|
|
453
480
|
this.db.exec(`
|
|
@@ -456,9 +483,9 @@ var IndexStore = class _IndexStore {
|
|
|
456
483
|
DROP TABLE IF EXISTS refs;
|
|
457
484
|
`);
|
|
458
485
|
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
459
|
-
this.
|
|
486
|
+
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
|
|
460
487
|
} else if (storedVersion === null) {
|
|
461
|
-
this.
|
|
488
|
+
this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
|
|
462
489
|
}
|
|
463
490
|
this.db.exec(`
|
|
464
491
|
CREATE TABLE IF NOT EXISTS files (
|
|
@@ -489,6 +516,7 @@ var IndexStore = class _IndexStore {
|
|
|
489
516
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
|
|
490
517
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
|
|
491
518
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
|
|
519
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)");
|
|
492
520
|
this.db.exec(`
|
|
493
521
|
CREATE TABLE IF NOT EXISTS refs (
|
|
494
522
|
id INTEGER PRIMARY KEY,
|
|
@@ -509,65 +537,167 @@ var IndexStore = class _IndexStore {
|
|
|
509
537
|
);
|
|
510
538
|
this.ftsAvailable = true;
|
|
511
539
|
const symbolCount = Number(
|
|
512
|
-
this.
|
|
540
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
513
541
|
);
|
|
514
542
|
const ftsCount = Number(
|
|
515
|
-
this.
|
|
543
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
516
544
|
);
|
|
517
545
|
if (symbolCount !== ftsCount) {
|
|
518
546
|
this.db.exec("DELETE FROM symbols_fts");
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
547
|
+
const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
548
|
+
this.bulkInsertFts(
|
|
549
|
+
rows.map((row) => ({
|
|
550
|
+
id: row.id,
|
|
551
|
+
text: buildIndexableText(row.name, row.signature, row.doc_comment)
|
|
552
|
+
}))
|
|
553
|
+
);
|
|
554
|
+
this.invalidateBm25();
|
|
524
555
|
}
|
|
525
556
|
} catch {
|
|
526
557
|
this.ftsAvailable = false;
|
|
527
558
|
}
|
|
559
|
+
this.ensureNextSymbolIdSeeded();
|
|
560
|
+
}
|
|
561
|
+
// ─── ID allocation & bulk helpers ────────────────────────────────────────────
|
|
562
|
+
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
563
|
+
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
564
|
+
static MAX_SQL_VARS = 900;
|
|
565
|
+
/**
|
|
566
|
+
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
567
|
+
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
568
|
+
* re-reads and advances the counter atomically.
|
|
569
|
+
*/
|
|
570
|
+
ensureNextSymbolIdSeeded() {
|
|
571
|
+
const existing = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
572
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
573
|
+
);
|
|
574
|
+
if (existing?.value !== void 0) return;
|
|
575
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
576
|
+
const next = (maxRows[0]?.m ?? 0) + 1;
|
|
577
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
578
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
579
|
+
String(next)
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Reserve `count` consecutive symbol ids. MUST run inside BEGIN IMMEDIATE
|
|
584
|
+
* so concurrent indexers cannot hand out overlapping ranges.
|
|
585
|
+
*/
|
|
586
|
+
allocateSymbolIds(count) {
|
|
587
|
+
if (count <= 0) return this.getMaxSymbolId() + 1;
|
|
588
|
+
this.ensureNextSymbolIdSeeded();
|
|
589
|
+
const row = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
590
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
591
|
+
);
|
|
592
|
+
const start = Math.max(1, Number(row?.value ?? 1) || 1);
|
|
593
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
594
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
595
|
+
String(start + count)
|
|
596
|
+
);
|
|
597
|
+
return start;
|
|
598
|
+
}
|
|
599
|
+
/** Multi-row INSERT for symbols — amortizes prepare/bind overhead in large batches. */
|
|
600
|
+
bulkInsertSymbols(rows) {
|
|
601
|
+
if (rows.length === 0) return;
|
|
602
|
+
const cols = 12;
|
|
603
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
604
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
605
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
606
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
607
|
+
const stmt = this.stmt(
|
|
608
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
609
|
+
VALUES ${placeholders}`
|
|
610
|
+
);
|
|
611
|
+
const binds = [];
|
|
612
|
+
for (const r of chunk) {
|
|
613
|
+
binds.push(
|
|
614
|
+
r.id,
|
|
615
|
+
r.lang,
|
|
616
|
+
r.kind,
|
|
617
|
+
r.name,
|
|
618
|
+
r.file,
|
|
619
|
+
r.line,
|
|
620
|
+
r.col,
|
|
621
|
+
r.signature,
|
|
622
|
+
r.docComment,
|
|
623
|
+
r.scope,
|
|
624
|
+
r.text,
|
|
625
|
+
r.file
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
stmt.run(...binds);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
bulkInsertFts(rows) {
|
|
632
|
+
if (!this.ftsAvailable || rows.length === 0) return;
|
|
633
|
+
const cols = 2;
|
|
634
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
635
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
636
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
637
|
+
const placeholders = chunk.map(() => "(?, ?)").join(", ");
|
|
638
|
+
const stmt = this.stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders}`);
|
|
639
|
+
const binds = [];
|
|
640
|
+
for (const r of chunk) binds.push(r.id, r.text);
|
|
641
|
+
stmt.run(...binds);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
bulkInsertRefs(refs) {
|
|
645
|
+
if (refs.length === 0) return;
|
|
646
|
+
const cols = 5;
|
|
647
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
648
|
+
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
649
|
+
const chunk = refs.slice(i, i + chunkSize);
|
|
650
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
651
|
+
const stmt = this.stmt(
|
|
652
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line) VALUES ${placeholders}`
|
|
653
|
+
);
|
|
654
|
+
const binds = [];
|
|
655
|
+
for (const ref of chunk) {
|
|
656
|
+
binds.push(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
657
|
+
}
|
|
658
|
+
stmt.run(...binds);
|
|
659
|
+
}
|
|
528
660
|
}
|
|
529
661
|
// ─── Symbol CRUD ─────────────────────────────────────────────────────────────
|
|
530
662
|
/**
|
|
531
663
|
* Insert symbols, assigning IDs atomically inside `BEGIN IMMEDIATE` /
|
|
532
|
-
* `COMMIT`.
|
|
533
|
-
*
|
|
534
|
-
* processes index concurrently (each would see a different `MAX(id)` and
|
|
535
|
-
* neither can insert with the other's IDs).
|
|
664
|
+
* `COMMIT`. Id ranges come from the `next_symbol_id` metadata counter
|
|
665
|
+
* (O(1)); multi-row INSERT amortizes bind overhead for large files.
|
|
536
666
|
*
|
|
537
667
|
* @returns The symbols array with `id` fields populated so the caller can
|
|
538
668
|
* use them for refs without re-reading from the DB.
|
|
539
669
|
*/
|
|
540
670
|
insertSymbols(symbols) {
|
|
671
|
+
this.invalidateBm25();
|
|
541
672
|
return this.runWithRetry(() => {
|
|
542
673
|
this.db.exec("BEGIN IMMEDIATE");
|
|
543
674
|
try {
|
|
544
|
-
|
|
545
|
-
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
546
|
-
const stmt = this.db.prepare(
|
|
547
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
548
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
549
|
-
);
|
|
550
|
-
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
675
|
+
let nextId = this.allocateSymbolIds(symbols.length);
|
|
551
676
|
const result = [];
|
|
677
|
+
const bulk = [];
|
|
678
|
+
const ftsRows = [];
|
|
552
679
|
for (const s of symbols) {
|
|
553
680
|
const id = nextId++;
|
|
554
|
-
|
|
681
|
+
bulk.push({
|
|
555
682
|
id,
|
|
556
|
-
s.lang,
|
|
557
|
-
s.kind,
|
|
558
|
-
s.name,
|
|
559
|
-
s.file,
|
|
560
|
-
s.line,
|
|
561
|
-
s.col,
|
|
562
|
-
s.signature,
|
|
563
|
-
s.docComment,
|
|
564
|
-
s.scope,
|
|
565
|
-
s.text
|
|
566
|
-
|
|
567
|
-
)
|
|
568
|
-
|
|
683
|
+
lang: s.lang,
|
|
684
|
+
kind: s.kind,
|
|
685
|
+
name: s.name,
|
|
686
|
+
file: s.file,
|
|
687
|
+
line: s.line,
|
|
688
|
+
col: s.col,
|
|
689
|
+
signature: s.signature,
|
|
690
|
+
docComment: s.docComment,
|
|
691
|
+
scope: s.scope,
|
|
692
|
+
text: s.text
|
|
693
|
+
});
|
|
694
|
+
if (this.ftsAvailable) {
|
|
695
|
+
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
696
|
+
}
|
|
569
697
|
result.push({ ...s, id });
|
|
570
698
|
}
|
|
699
|
+
this.bulkInsertSymbols(bulk);
|
|
700
|
+
this.bulkInsertFts(ftsRows);
|
|
571
701
|
this.db.exec("COMMIT");
|
|
572
702
|
return result;
|
|
573
703
|
} catch (err) {
|
|
@@ -577,6 +707,7 @@ var IndexStore = class _IndexStore {
|
|
|
577
707
|
});
|
|
578
708
|
}
|
|
579
709
|
deleteSymbolsForFile(file) {
|
|
710
|
+
this.invalidateBm25();
|
|
580
711
|
this.runWithRetry(() => {
|
|
581
712
|
if (this.ftsAvailable) {
|
|
582
713
|
this.stmt(
|
|
@@ -592,17 +723,20 @@ var IndexStore = class _IndexStore {
|
|
|
592
723
|
* dropped the `files` row, leaving its symbols orphaned but still searchable.
|
|
593
724
|
*/
|
|
594
725
|
deleteFile(file) {
|
|
726
|
+
this.invalidateBm25();
|
|
595
727
|
this.runWithRetry(() => {
|
|
596
728
|
this.db.exec("BEGIN IMMEDIATE");
|
|
597
729
|
try {
|
|
598
730
|
if (this.ftsAvailable) {
|
|
599
|
-
this.
|
|
731
|
+
this.stmt(
|
|
600
732
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
601
733
|
).run(file);
|
|
602
734
|
}
|
|
603
|
-
this.
|
|
604
|
-
|
|
605
|
-
|
|
735
|
+
this.stmt(
|
|
736
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
737
|
+
).run(file);
|
|
738
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
739
|
+
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
606
740
|
this.db.exec("COMMIT");
|
|
607
741
|
} catch (err) {
|
|
608
742
|
this.db.exec("ROLLBACK");
|
|
@@ -639,7 +773,7 @@ var IndexStore = class _IndexStore {
|
|
|
639
773
|
};
|
|
640
774
|
}
|
|
641
775
|
getAllFileMetas() {
|
|
642
|
-
return this.
|
|
776
|
+
return this.stmt("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
643
777
|
file: r.file,
|
|
644
778
|
lang: r.lang,
|
|
645
779
|
mtimeMs: r.mtime_ms,
|
|
@@ -648,7 +782,32 @@ var IndexStore = class _IndexStore {
|
|
|
648
782
|
}));
|
|
649
783
|
}
|
|
650
784
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
651
|
-
search(query, filter) {
|
|
785
|
+
search(query, filter, opts) {
|
|
786
|
+
const built = this.buildSearchWhere(query, filter);
|
|
787
|
+
if (built === null) return [];
|
|
788
|
+
const { where, values } = built;
|
|
789
|
+
const limit = typeof opts?.limit === "number" && Number.isFinite(opts.limit) ? Math.max(0, Math.trunc(opts.limit)) : void 0;
|
|
790
|
+
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
791
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
|
|
792
|
+
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
793
|
+
const rows = this.stmt(sql).all(...binds);
|
|
794
|
+
return rows.map((r) => ({
|
|
795
|
+
id: r.id,
|
|
796
|
+
lang: r.lang,
|
|
797
|
+
kind: r.kind,
|
|
798
|
+
name: r.name,
|
|
799
|
+
file: r.file,
|
|
800
|
+
line: r.line,
|
|
801
|
+
col: r.col,
|
|
802
|
+
signature: r.signature,
|
|
803
|
+
docComment: r.doc_comment,
|
|
804
|
+
score: 0,
|
|
805
|
+
snippet: "",
|
|
806
|
+
lspKind: filter?.lspKind
|
|
807
|
+
}));
|
|
808
|
+
}
|
|
809
|
+
/** Shared WHERE builder for {@link search} / empty-query ranked totals. */
|
|
810
|
+
buildSearchWhere(query, filter) {
|
|
652
811
|
const conditions = [];
|
|
653
812
|
const values = [];
|
|
654
813
|
let effectiveKind = filter?.kind;
|
|
@@ -657,7 +816,7 @@ var IndexStore = class _IndexStore {
|
|
|
657
816
|
if (mapped !== null) {
|
|
658
817
|
effectiveKind = mapped;
|
|
659
818
|
} else {
|
|
660
|
-
return
|
|
819
|
+
return null;
|
|
661
820
|
}
|
|
662
821
|
}
|
|
663
822
|
if (effectiveKind) {
|
|
@@ -679,23 +838,15 @@ var IndexStore = class _IndexStore {
|
|
|
679
838
|
for (const t of tokens) values.push(`%${t}%`);
|
|
680
839
|
}
|
|
681
840
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
line: r.line,
|
|
692
|
-
col: r.col,
|
|
693
|
-
signature: r.signature,
|
|
694
|
-
docComment: r.doc_comment,
|
|
695
|
-
score: 0,
|
|
696
|
-
snippet: "",
|
|
697
|
-
lspKind: filter?.lspKind
|
|
698
|
-
}));
|
|
841
|
+
return { where, values };
|
|
842
|
+
}
|
|
843
|
+
countSearch(query, filter) {
|
|
844
|
+
const built = this.buildSearchWhere(query, filter);
|
|
845
|
+
if (built === null) return 0;
|
|
846
|
+
const row = this.stmt(`SELECT COUNT(*) AS n FROM symbols ${built.where}`).get(
|
|
847
|
+
...built.values
|
|
848
|
+
);
|
|
849
|
+
return Number(row?.n ?? 0);
|
|
699
850
|
}
|
|
700
851
|
/**
|
|
701
852
|
* Ranked search — the one-stop query the codebase-search tool and plug-lsp
|
|
@@ -736,12 +887,12 @@ var IndexStore = class _IndexStore {
|
|
|
736
887
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
737
888
|
}
|
|
738
889
|
const where = conditions.join(" AND ");
|
|
739
|
-
const countRows = this.
|
|
890
|
+
const countRows = this.stmt(
|
|
740
891
|
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
741
892
|
).all(...values);
|
|
742
893
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
743
894
|
if (total === 0) return { results: [], total: 0 };
|
|
744
|
-
const rows = this.
|
|
895
|
+
const rows = this.stmt(
|
|
745
896
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
746
897
|
-bm25(symbols_fts) AS score,
|
|
747
898
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
@@ -774,20 +925,60 @@ var IndexStore = class _IndexStore {
|
|
|
774
925
|
total
|
|
775
926
|
};
|
|
776
927
|
}
|
|
928
|
+
/**
|
|
929
|
+
* Invalidate the cached BM25 index.
|
|
930
|
+
*
|
|
931
|
+
* **Contract: every method that mutates `symbols` MUST call this before
|
|
932
|
+
* returning.** (`refs` mutations do not affect the BM25 fallback because
|
|
933
|
+
* the corpus is built from `symbols.text` via `getAllIndexable()` and the
|
|
934
|
+
* BM25 score is filtered by the LIKE-selected candidate set in
|
|
935
|
+
* `searchRankedFallback`.) Today the call sites are `repairDrift`,
|
|
936
|
+
* `insertSymbols`, `deleteSymbolsForFile`, `deleteFile`, `clearAll`, and
|
|
937
|
+
* `commitBatch`. A future mutation that adds a new write path (e.g.
|
|
938
|
+
* `renameFile`, `updateSignature`) MUST also call this — otherwise the
|
|
939
|
+
* FTS5-unavailable fallback will serve stale search results. The
|
|
940
|
+
* `close()` reset at L1820-1821 tears the cache down on store shutdown,
|
|
941
|
+
* which is the only legitimate place that flips the flag outside this
|
|
942
|
+
* helper.
|
|
943
|
+
*
|
|
944
|
+
* Called *before* `runWithRetry` on purpose: if the write fails all
|
|
945
|
+
* retries the flag stays set, forcing a rebuild on the next search rather
|
|
946
|
+
* than trusting a cache that may not reflect the intended mutation.
|
|
947
|
+
* Do not move this inside the retry closure.
|
|
948
|
+
*/
|
|
949
|
+
invalidateBm25() {
|
|
950
|
+
this.bm25Dirty = true;
|
|
951
|
+
this.bm25Cache = null;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Return the cached full-corpus BM25 index, rebuilding it only when the
|
|
955
|
+
* symbols table has been mutated since the last build. The full-corpus IDF
|
|
956
|
+
* is more correct than the old per-query candidate-subset IDF, and the
|
|
957
|
+
* amortized build cost drops from O(symbols × tokens) per search to once
|
|
958
|
+
* per write batch.
|
|
959
|
+
*
|
|
960
|
+
* Note: the first call after a long idle (or on a freshly opened store)
|
|
961
|
+
* pays the full corpus rebuild synchronously on the search path. For a
|
|
962
|
+
* 5 500+ symbol corpus this is a visible one-time latency spike.
|
|
963
|
+
*/
|
|
964
|
+
getOrBuildBm25() {
|
|
965
|
+
if (this.bm25Cache && !this.bm25Dirty) return this.bm25Cache;
|
|
966
|
+
const docs = this.getAllIndexable();
|
|
967
|
+
this.bm25Cache = buildBm25Index(docs);
|
|
968
|
+
this.bm25Dirty = false;
|
|
969
|
+
return this.bm25Cache;
|
|
970
|
+
}
|
|
777
971
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
778
972
|
searchRankedFallback(query, filter, limit) {
|
|
779
|
-
const candidates = this.search(query, filter);
|
|
780
|
-
if (candidates.length === 0) return { results: [], total: 0 };
|
|
781
973
|
if (!query.trim()) {
|
|
782
|
-
|
|
974
|
+
const total = this.countSearch(query, filter);
|
|
975
|
+
if (total === 0) return { results: [], total: 0 };
|
|
976
|
+
return { results: this.search(query, filter, { limit }), total };
|
|
783
977
|
}
|
|
978
|
+
const candidates = this.search(query, filter);
|
|
979
|
+
if (candidates.length === 0) return { results: [], total: 0 };
|
|
784
980
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
785
|
-
const bm25 =
|
|
786
|
-
candidates.map((c) => ({
|
|
787
|
-
id: c.id,
|
|
788
|
-
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
789
|
-
}))
|
|
790
|
-
);
|
|
981
|
+
const bm25 = this.getOrBuildBm25();
|
|
791
982
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
792
983
|
const q = query.trim().toLowerCase();
|
|
793
984
|
const rank = (id) => {
|
|
@@ -829,7 +1020,7 @@ var IndexStore = class _IndexStore {
|
|
|
829
1020
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
830
1021
|
getStats() {
|
|
831
1022
|
const sizeBytes = this.sizeBytes();
|
|
832
|
-
const lastRows = this.
|
|
1023
|
+
const lastRows = this.stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
833
1024
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
834
1025
|
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
835
1026
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
@@ -854,19 +1045,20 @@ var IndexStore = class _IndexStore {
|
|
|
854
1045
|
}
|
|
855
1046
|
setLastIndexed(ts2) {
|
|
856
1047
|
this.runWithRetry(() => {
|
|
857
|
-
this.
|
|
1048
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
858
1049
|
});
|
|
859
1050
|
}
|
|
860
1051
|
getMetadata(key) {
|
|
861
|
-
const rows = this.
|
|
1052
|
+
const rows = this.stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
862
1053
|
return rows[0]?.value;
|
|
863
1054
|
}
|
|
864
1055
|
setMetadata(key, value) {
|
|
865
1056
|
this.runWithRetry(() => {
|
|
866
|
-
this.
|
|
1057
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
867
1058
|
});
|
|
868
1059
|
}
|
|
869
1060
|
clearAll() {
|
|
1061
|
+
this.invalidateBm25();
|
|
870
1062
|
this.runWithRetry(() => {
|
|
871
1063
|
this.db.exec("BEGIN IMMEDIATE");
|
|
872
1064
|
try {
|
|
@@ -893,13 +1085,7 @@ var IndexStore = class _IndexStore {
|
|
|
893
1085
|
this.runWithRetry(() => {
|
|
894
1086
|
this.stmt("DELETE FROM refs WHERE from_id = ?").run(fromId);
|
|
895
1087
|
if (refs.length === 0) return;
|
|
896
|
-
|
|
897
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
898
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
899
|
-
);
|
|
900
|
-
for (const ref of refs) {
|
|
901
|
-
stmt.run(fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
902
|
-
}
|
|
1088
|
+
this.bulkInsertRefs(refs.map((ref) => ({ ...ref, fromId })));
|
|
903
1089
|
});
|
|
904
1090
|
}
|
|
905
1091
|
/**
|
|
@@ -916,13 +1102,7 @@ var IndexStore = class _IndexStore {
|
|
|
916
1102
|
insertRefsBatch(refs) {
|
|
917
1103
|
if (refs.length === 0) return;
|
|
918
1104
|
this.runWithRetry(() => {
|
|
919
|
-
|
|
920
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
921
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
922
|
-
);
|
|
923
|
-
for (const ref of refs) {
|
|
924
|
-
stmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
925
|
-
}
|
|
1105
|
+
this.bulkInsertRefs(refs);
|
|
926
1106
|
});
|
|
927
1107
|
}
|
|
928
1108
|
/**
|
|
@@ -947,65 +1127,61 @@ var IndexStore = class _IndexStore {
|
|
|
947
1127
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
948
1128
|
return [];
|
|
949
1129
|
}
|
|
1130
|
+
this.invalidateBm25();
|
|
950
1131
|
return this.runWithRetry(() => {
|
|
951
1132
|
this.db.exec("BEGIN IMMEDIATE");
|
|
952
1133
|
try {
|
|
953
1134
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
954
1135
|
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
955
1136
|
if (this.ftsAvailable) {
|
|
956
|
-
this.
|
|
1137
|
+
this.stmt(
|
|
957
1138
|
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
958
1139
|
).run(...options.deleteForFiles);
|
|
959
1140
|
}
|
|
960
|
-
this.
|
|
1141
|
+
this.stmt(
|
|
961
1142
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
962
1143
|
).run(...options.deleteForFiles);
|
|
963
|
-
this.
|
|
1144
|
+
this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
964
1145
|
}
|
|
965
|
-
const
|
|
966
|
-
let nextId = (
|
|
967
|
-
const symStmt = this.db.prepare(
|
|
968
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
969
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
970
|
-
);
|
|
971
|
-
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
1146
|
+
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
1147
|
+
let nextId = this.allocateSymbolIds(totalSymbols);
|
|
972
1148
|
const allInserted = [];
|
|
973
1149
|
const refsToInsert = [];
|
|
1150
|
+
const bulkSyms = [];
|
|
1151
|
+
const ftsRows = [];
|
|
974
1152
|
for (const entry of entries) {
|
|
975
1153
|
const insertedForEntry = [];
|
|
976
1154
|
for (const s of entry.symbols) {
|
|
977
1155
|
const id = nextId++;
|
|
978
|
-
|
|
1156
|
+
bulkSyms.push({
|
|
979
1157
|
id,
|
|
980
|
-
s.lang,
|
|
981
|
-
s.kind,
|
|
982
|
-
s.name,
|
|
983
|
-
s.file,
|
|
984
|
-
s.line,
|
|
985
|
-
s.col,
|
|
986
|
-
s.signature,
|
|
987
|
-
s.docComment,
|
|
988
|
-
s.scope,
|
|
989
|
-
s.text
|
|
990
|
-
|
|
991
|
-
)
|
|
992
|
-
|
|
1158
|
+
lang: s.lang,
|
|
1159
|
+
kind: s.kind,
|
|
1160
|
+
name: s.name,
|
|
1161
|
+
file: s.file,
|
|
1162
|
+
line: s.line,
|
|
1163
|
+
col: s.col,
|
|
1164
|
+
signature: s.signature,
|
|
1165
|
+
docComment: s.docComment,
|
|
1166
|
+
scope: s.scope,
|
|
1167
|
+
text: s.text
|
|
1168
|
+
});
|
|
1169
|
+
if (this.ftsAvailable) {
|
|
1170
|
+
ftsRows.push({
|
|
1171
|
+
id,
|
|
1172
|
+
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
993
1175
|
const inserted = { ...s, id };
|
|
994
1176
|
allInserted.push(inserted);
|
|
995
1177
|
insertedForEntry.push(inserted);
|
|
996
1178
|
}
|
|
997
1179
|
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
998
1180
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
);
|
|
1004
|
-
for (const ref of refsToInsert) {
|
|
1005
|
-
refStmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
const upsertStmt = this.db.prepare(
|
|
1181
|
+
this.bulkInsertSymbols(bulkSyms);
|
|
1182
|
+
this.bulkInsertFts(ftsRows);
|
|
1183
|
+
this.bulkInsertRefs(refsToInsert);
|
|
1184
|
+
const upsertStmt = this.stmt(
|
|
1009
1185
|
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
1010
1186
|
VALUES (?, ?, ?, ?, ?)
|
|
1011
1187
|
ON CONFLICT(file) DO UPDATE SET
|
|
@@ -1032,7 +1208,7 @@ var IndexStore = class _IndexStore {
|
|
|
1032
1208
|
*/
|
|
1033
1209
|
deleteRefsForFile(file) {
|
|
1034
1210
|
this.runWithRetry(() => {
|
|
1035
|
-
this.
|
|
1211
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
1036
1212
|
});
|
|
1037
1213
|
}
|
|
1038
1214
|
/**
|
|
@@ -1045,20 +1221,75 @@ var IndexStore = class _IndexStore {
|
|
|
1045
1221
|
*/
|
|
1046
1222
|
resolveRefs() {
|
|
1047
1223
|
return this.runWithRetry(() => {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1224
|
+
try {
|
|
1225
|
+
const result = this.stmt(
|
|
1226
|
+
`UPDATE refs
|
|
1227
|
+
SET to_id = s.id
|
|
1228
|
+
FROM (
|
|
1229
|
+
SELECT name, MIN(id) AS id FROM symbols GROUP BY name
|
|
1230
|
+
) AS s
|
|
1231
|
+
WHERE refs.to_id IS NULL
|
|
1232
|
+
AND refs.to_name IS NOT NULL
|
|
1233
|
+
AND refs.to_name = s.name`
|
|
1234
|
+
).run();
|
|
1235
|
+
return result.changes ?? 0;
|
|
1236
|
+
} catch {
|
|
1237
|
+
const result = this.stmt(
|
|
1238
|
+
`UPDATE refs SET to_id = (
|
|
1239
|
+
SELECT id FROM symbols WHERE name = refs.to_name LIMIT 1
|
|
1240
|
+
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
1241
|
+
AND to_name IN (SELECT name FROM symbols)`
|
|
1242
|
+
).run();
|
|
1243
|
+
return result.changes ?? 0;
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Clear symbols/refs for a file and mark it as indexed with zero symbols.
|
|
1249
|
+
* Used by the indexer for empty-parse results so three writes share one txn.
|
|
1250
|
+
*/
|
|
1251
|
+
replaceEmptyFile(meta) {
|
|
1252
|
+
this.invalidateBm25();
|
|
1253
|
+
this.runWithRetry(() => {
|
|
1254
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
1255
|
+
try {
|
|
1256
|
+
if (this.ftsAvailable) {
|
|
1257
|
+
this.stmt(
|
|
1258
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
1259
|
+
).run(meta.file);
|
|
1260
|
+
}
|
|
1261
|
+
this.stmt(
|
|
1262
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
1263
|
+
).run(meta.file);
|
|
1264
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
|
|
1265
|
+
this.stmt(
|
|
1266
|
+
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
1267
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1268
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
1269
|
+
lang = excluded.lang,
|
|
1270
|
+
mtime_ms = excluded.mtime_ms,
|
|
1271
|
+
symbol_count = excluded.symbol_count,
|
|
1272
|
+
last_indexed = excluded.last_indexed`
|
|
1273
|
+
).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
|
|
1274
|
+
this.db.exec("COMMIT");
|
|
1275
|
+
} catch (err) {
|
|
1276
|
+
this.db.exec("ROLLBACK");
|
|
1277
|
+
throw err;
|
|
1278
|
+
}
|
|
1055
1279
|
});
|
|
1056
1280
|
}
|
|
1281
|
+
/** Best-effort query planner refresh after a large reindex. */
|
|
1282
|
+
optimize() {
|
|
1283
|
+
try {
|
|
1284
|
+
this.db.exec("PRAGMA optimize");
|
|
1285
|
+
} catch {
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1057
1288
|
/**
|
|
1058
1289
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
1059
1290
|
*/
|
|
1060
1291
|
findRefsTo(symbolId) {
|
|
1061
|
-
return this.
|
|
1292
|
+
return this.stmt(
|
|
1062
1293
|
"SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE to_id = ? OR to_name = (SELECT name FROM symbols WHERE id = ?)"
|
|
1063
1294
|
).all(symbolId, symbolId).map((r) => ({
|
|
1064
1295
|
id: r.id,
|
|
@@ -1073,7 +1304,7 @@ var IndexStore = class _IndexStore {
|
|
|
1073
1304
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
1074
1305
|
*/
|
|
1075
1306
|
findRefsFrom(symbolId) {
|
|
1076
|
-
return this.
|
|
1307
|
+
return this.stmt("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
1077
1308
|
id: r.id,
|
|
1078
1309
|
fromId: r.from_id,
|
|
1079
1310
|
toName: r.to_name,
|
|
@@ -1146,61 +1377,77 @@ var IndexStore = class _IndexStore {
|
|
|
1146
1377
|
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
1147
1378
|
*/
|
|
1148
1379
|
getPackageGraph() {
|
|
1149
|
-
const
|
|
1380
|
+
const fileCounts = this.stmt(
|
|
1381
|
+
"SELECT file, COUNT(*) AS n FROM symbols GROUP BY file"
|
|
1382
|
+
).all();
|
|
1150
1383
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
1151
1384
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
fileToPkg.set(s.file, pkg);
|
|
1156
|
-
symbolToPkg.set(s.id, pkg);
|
|
1385
|
+
for (const { file, n } of fileCounts) {
|
|
1386
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
1387
|
+
fileToPkg.set(file, pkg);
|
|
1157
1388
|
const node = pkgNodes.get(pkg);
|
|
1158
1389
|
if (node) {
|
|
1159
|
-
node.symbolCount = (node.symbolCount ?? 0) +
|
|
1390
|
+
node.symbolCount = (node.symbolCount ?? 0) + n;
|
|
1160
1391
|
} else {
|
|
1161
1392
|
pkgNodes.set(pkg, {
|
|
1162
1393
|
id: `pkg:${pkg}`,
|
|
1163
1394
|
label: pkg,
|
|
1164
1395
|
kind: "package",
|
|
1165
1396
|
package: pkg,
|
|
1166
|
-
symbolCount:
|
|
1397
|
+
symbolCount: n,
|
|
1167
1398
|
fileCount: 0
|
|
1168
1399
|
});
|
|
1169
1400
|
}
|
|
1170
1401
|
}
|
|
1171
|
-
const files = this.
|
|
1402
|
+
const files = this.stmt("SELECT DISTINCT file FROM files").all();
|
|
1172
1403
|
for (const { file } of files) {
|
|
1173
1404
|
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
1405
|
+
fileToPkg.set(file, pkg);
|
|
1174
1406
|
const node = pkgNodes.get(pkg);
|
|
1175
1407
|
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
1408
|
+
else {
|
|
1409
|
+
pkgNodes.set(pkg, {
|
|
1410
|
+
id: `pkg:${pkg}`,
|
|
1411
|
+
label: pkg,
|
|
1412
|
+
kind: "package",
|
|
1413
|
+
package: pkg,
|
|
1414
|
+
symbolCount: 0,
|
|
1415
|
+
fileCount: 1
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1176
1418
|
}
|
|
1177
|
-
const refRows = this.
|
|
1178
|
-
`SELECT r.
|
|
1419
|
+
const refRows = this.stmt(
|
|
1420
|
+
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
|
|
1179
1421
|
FROM refs r
|
|
1180
|
-
|
|
1422
|
+
JOIN symbols sf ON sf.id = r.from_id
|
|
1423
|
+
JOIN symbols st ON st.id = r.to_id
|
|
1424
|
+
WHERE r.to_id IS NOT NULL AND r.call_type != 'import'
|
|
1425
|
+
GROUP BY r.call_type, sf.file, st.file`
|
|
1181
1426
|
).all();
|
|
1182
1427
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1183
1428
|
for (const r of refRows) {
|
|
1184
|
-
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
1429
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
1430
|
+
const toPkg = fileToPkg.get(r.to_file) ?? _IndexStore.derivePackage(r.to_file) ?? "(root)";
|
|
1431
|
+
if (fromPkg === toPkg) continue;
|
|
1188
1432
|
const key = `${fromPkg}\0${toPkg}`;
|
|
1189
1433
|
let e = edgeMap.get(key);
|
|
1190
1434
|
if (!e) {
|
|
1191
1435
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1192
1436
|
edgeMap.set(key, e);
|
|
1193
1437
|
}
|
|
1194
|
-
|
|
1195
|
-
e.
|
|
1438
|
+
const n = Number(r.n) || 0;
|
|
1439
|
+
e.weight += n;
|
|
1440
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
1196
1441
|
}
|
|
1197
|
-
const importRows = this.
|
|
1198
|
-
`SELECT r.
|
|
1442
|
+
const importRows = this.stmt(
|
|
1443
|
+
`SELECT r.to_name, s.file AS from_file, COUNT(*) AS n
|
|
1199
1444
|
FROM refs r
|
|
1200
|
-
|
|
1445
|
+
JOIN symbols s ON s.id = r.from_id
|
|
1446
|
+
WHERE r.call_type = 'import'
|
|
1447
|
+
GROUP BY r.to_name, s.file`
|
|
1201
1448
|
).all();
|
|
1202
1449
|
for (const r of importRows) {
|
|
1203
|
-
const fromPkg =
|
|
1450
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
1204
1451
|
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
1205
1452
|
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
1206
1453
|
const key = `${fromPkg}\0${toPkg}`;
|
|
@@ -1209,8 +1456,9 @@ var IndexStore = class _IndexStore {
|
|
|
1209
1456
|
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1210
1457
|
edgeMap.set(key, edge);
|
|
1211
1458
|
}
|
|
1212
|
-
|
|
1213
|
-
edge.
|
|
1459
|
+
const n = Number(r.n) || 0;
|
|
1460
|
+
edge.weight += n;
|
|
1461
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + n);
|
|
1214
1462
|
}
|
|
1215
1463
|
const edges = [];
|
|
1216
1464
|
for (const [key, e] of edgeMap) {
|
|
@@ -1237,12 +1485,12 @@ var IndexStore = class _IndexStore {
|
|
|
1237
1485
|
* derived from cross-file symbol references within the package.
|
|
1238
1486
|
*/
|
|
1239
1487
|
getFileGraph(packageFilter) {
|
|
1240
|
-
const allFiles = this.
|
|
1488
|
+
const allFiles = this.stmt("SELECT DISTINCT file FROM symbols").all();
|
|
1241
1489
|
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
1242
1490
|
const localFiles = new Set(pkgFilePaths);
|
|
1243
1491
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
1244
1492
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
1245
|
-
const pkgSyms = this.
|
|
1493
|
+
const pkgSyms = this.stmt(
|
|
1246
1494
|
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
1247
1495
|
).all(...pkgFilePaths);
|
|
1248
1496
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
@@ -1274,12 +1522,13 @@ var IndexStore = class _IndexStore {
|
|
|
1274
1522
|
ensureFileNode(file);
|
|
1275
1523
|
}
|
|
1276
1524
|
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
1277
|
-
const refRows = this.
|
|
1278
|
-
`SELECT r.from_id, r.to_id, r.call_type
|
|
1525
|
+
const refRows = this.stmt(
|
|
1526
|
+
`SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
|
|
1279
1527
|
FROM refs r
|
|
1280
1528
|
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
1281
1529
|
OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
|
|
1282
|
-
AND r.to_id IS NOT NULL
|
|
1530
|
+
AND r.to_id IS NOT NULL
|
|
1531
|
+
GROUP BY r.from_id, r.to_id, r.call_type`
|
|
1283
1532
|
).all(...pkgFilePaths, ...pkgFilePaths);
|
|
1284
1533
|
const knownSymIds = new Set(pkgSyms.map((s) => s.id));
|
|
1285
1534
|
const crossRefIds = /* @__PURE__ */ new Set();
|
|
@@ -1289,7 +1538,7 @@ var IndexStore = class _IndexStore {
|
|
|
1289
1538
|
}
|
|
1290
1539
|
if (crossRefIds.size > 0) {
|
|
1291
1540
|
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
1292
|
-
const extras = this.
|
|
1541
|
+
const extras = this.stmt(
|
|
1293
1542
|
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
1294
1543
|
).all(...crossRefIds);
|
|
1295
1544
|
for (const x of extras) {
|
|
@@ -1314,14 +1563,16 @@ var IndexStore = class _IndexStore {
|
|
|
1314
1563
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1315
1564
|
edgeMap.set(key, e);
|
|
1316
1565
|
}
|
|
1317
|
-
|
|
1318
|
-
e.
|
|
1566
|
+
const n = Number(r.n) || 0;
|
|
1567
|
+
e.weight += n;
|
|
1568
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
1319
1569
|
}
|
|
1320
|
-
const importRows = this.
|
|
1321
|
-
`SELECT r.from_id, r.to_name
|
|
1570
|
+
const importRows = this.stmt(
|
|
1571
|
+
`SELECT r.from_id, r.to_name, COUNT(*) AS n
|
|
1322
1572
|
FROM refs r
|
|
1323
1573
|
WHERE r.call_type = 'import'
|
|
1324
|
-
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
1574
|
+
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
1575
|
+
GROUP BY r.from_id, r.to_name`
|
|
1325
1576
|
).all(...pkgFilePaths);
|
|
1326
1577
|
for (const r of importRows) {
|
|
1327
1578
|
const fromFile = symToFile.get(r.from_id);
|
|
@@ -1336,8 +1587,9 @@ var IndexStore = class _IndexStore {
|
|
|
1336
1587
|
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1337
1588
|
edgeMap.set(key, edge);
|
|
1338
1589
|
}
|
|
1339
|
-
|
|
1340
|
-
edge.
|
|
1590
|
+
const n = Number(r.n) || 0;
|
|
1591
|
+
edge.weight += n;
|
|
1592
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + n);
|
|
1341
1593
|
}
|
|
1342
1594
|
const edges = [];
|
|
1343
1595
|
for (const [key, e] of edgeMap) {
|
|
@@ -1364,7 +1616,7 @@ var IndexStore = class _IndexStore {
|
|
|
1364
1616
|
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
1365
1617
|
*/
|
|
1366
1618
|
getSymbolGraph(fileFilter) {
|
|
1367
|
-
const syms = this.
|
|
1619
|
+
const syms = this.stmt(
|
|
1368
1620
|
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
1369
1621
|
).all(fileFilter);
|
|
1370
1622
|
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
@@ -1384,17 +1636,25 @@ var IndexStore = class _IndexStore {
|
|
|
1384
1636
|
scope: s.scope,
|
|
1385
1637
|
external: s.file !== fileFilter
|
|
1386
1638
|
});
|
|
1387
|
-
const
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1639
|
+
const refRows = this.stmt(
|
|
1640
|
+
`SELECT from_id, to_id, call_type, COUNT(*) AS n
|
|
1641
|
+
FROM (
|
|
1642
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
1643
|
+
FROM refs r
|
|
1644
|
+
JOIN symbols s ON s.id = r.from_id
|
|
1645
|
+
WHERE s.file = ?
|
|
1646
|
+
UNION
|
|
1647
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
1648
|
+
FROM refs r
|
|
1649
|
+
JOIN symbols s ON s.id = r.to_id
|
|
1650
|
+
WHERE s.file = ?
|
|
1651
|
+
)
|
|
1652
|
+
WHERE to_id IS NOT NULL
|
|
1653
|
+
GROUP BY from_id, to_id, call_type`
|
|
1393
1654
|
).all(fileFilter, fileFilter);
|
|
1394
1655
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1395
1656
|
for (const r of refRows) {
|
|
1396
|
-
if (r.to_id
|
|
1397
|
-
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
1657
|
+
if (r.to_id == null) continue;
|
|
1398
1658
|
relatedIds.add(r.from_id);
|
|
1399
1659
|
relatedIds.add(r.to_id);
|
|
1400
1660
|
const key = `${r.from_id}\0${r.to_id}`;
|
|
@@ -1403,8 +1663,9 @@ var IndexStore = class _IndexStore {
|
|
|
1403
1663
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1404
1664
|
edgeMap.set(key, e);
|
|
1405
1665
|
}
|
|
1406
|
-
|
|
1407
|
-
e.
|
|
1666
|
+
const n = Number(r.n) || 0;
|
|
1667
|
+
e.weight += n;
|
|
1668
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
1408
1669
|
}
|
|
1409
1670
|
const edges = [];
|
|
1410
1671
|
for (const [key, e] of edgeMap) {
|
|
@@ -1428,7 +1689,7 @@ var IndexStore = class _IndexStore {
|
|
|
1428
1689
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
1429
1690
|
if (missingIds.length > 0) {
|
|
1430
1691
|
const placeholders = missingIds.map(() => "?").join(",");
|
|
1431
|
-
const extras = this.
|
|
1692
|
+
const extras = this.stmt(
|
|
1432
1693
|
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
1433
1694
|
).all(...missingIds);
|
|
1434
1695
|
for (const s of extras) symById.set(s.id, s);
|
|
@@ -1442,6 +1703,8 @@ var IndexStore = class _IndexStore {
|
|
|
1442
1703
|
}
|
|
1443
1704
|
close() {
|
|
1444
1705
|
this.stmtCache.clear();
|
|
1706
|
+
this.bm25Dirty = true;
|
|
1707
|
+
this.bm25Cache = null;
|
|
1445
1708
|
try {
|
|
1446
1709
|
this.db.close();
|
|
1447
1710
|
} catch {
|
|
@@ -1455,14 +1718,121 @@ import { fileURLToPath } from "node:url";
|
|
|
1455
1718
|
import { Worker } from "node:worker_threads";
|
|
1456
1719
|
|
|
1457
1720
|
// src/codebase-index/indexer.ts
|
|
1458
|
-
import { expectDefined as expectDefined5 } from "@wrongstack/core";
|
|
1721
|
+
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
1459
1722
|
import * as fs7 from "node:fs/promises";
|
|
1460
|
-
import * as
|
|
1723
|
+
import * as path9 from "node:path";
|
|
1461
1724
|
import { availableParallelism } from "node:os";
|
|
1462
|
-
import {
|
|
1725
|
+
import { DEFAULT_WALK_IGNORE_DIRS, indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
|
|
1463
1726
|
|
|
1464
1727
|
// src/codebase-index/ts-parser.ts
|
|
1465
1728
|
import * as ts from "@typescript/typescript6";
|
|
1729
|
+
|
|
1730
|
+
// src/codebase-index/languages.ts
|
|
1731
|
+
import * as path2 from "node:path";
|
|
1732
|
+
var EXT_TO_LANG = {
|
|
1733
|
+
// TypeScript / JavaScript (first-class TS compiler API)
|
|
1734
|
+
".ts": "ts",
|
|
1735
|
+
".mts": "ts",
|
|
1736
|
+
".cts": "ts",
|
|
1737
|
+
".tsx": "tsx",
|
|
1738
|
+
".js": "js",
|
|
1739
|
+
".mjs": "js",
|
|
1740
|
+
".cjs": "js",
|
|
1741
|
+
".jsx": "jsx",
|
|
1742
|
+
// First-class native/spawn parsers (+ regex fallback)
|
|
1743
|
+
".go": "go",
|
|
1744
|
+
".py": "py",
|
|
1745
|
+
".pyi": "py",
|
|
1746
|
+
".pyw": "py",
|
|
1747
|
+
".rs": "rs",
|
|
1748
|
+
".json": "json",
|
|
1749
|
+
".jsonc": "json",
|
|
1750
|
+
".yaml": "yaml",
|
|
1751
|
+
".yml": "yaml",
|
|
1752
|
+
// C family
|
|
1753
|
+
".c": "c",
|
|
1754
|
+
".h": "c",
|
|
1755
|
+
".cc": "cpp",
|
|
1756
|
+
".cpp": "cpp",
|
|
1757
|
+
".cxx": "cpp",
|
|
1758
|
+
".hh": "cpp",
|
|
1759
|
+
".hpp": "cpp",
|
|
1760
|
+
".hxx": "cpp",
|
|
1761
|
+
// JVM / .NET
|
|
1762
|
+
".java": "java",
|
|
1763
|
+
".cs": "csharp",
|
|
1764
|
+
".kt": "kotlin",
|
|
1765
|
+
".kts": "kotlin",
|
|
1766
|
+
".scala": "scala",
|
|
1767
|
+
".sc": "scala",
|
|
1768
|
+
// Scripting
|
|
1769
|
+
".php": "php",
|
|
1770
|
+
".rb": "ruby",
|
|
1771
|
+
".swift": "swift",
|
|
1772
|
+
".dart": "dart",
|
|
1773
|
+
".lua": "lua",
|
|
1774
|
+
".r": "r",
|
|
1775
|
+
".R": "r",
|
|
1776
|
+
".pl": "other",
|
|
1777
|
+
".pm": "other",
|
|
1778
|
+
// Systems / functional
|
|
1779
|
+
".zig": "zig",
|
|
1780
|
+
".ex": "elixir",
|
|
1781
|
+
".exs": "elixir",
|
|
1782
|
+
".hs": "haskell",
|
|
1783
|
+
".lhs": "haskell",
|
|
1784
|
+
// Shell / data / docs / web
|
|
1785
|
+
".sh": "shell",
|
|
1786
|
+
".bash": "shell",
|
|
1787
|
+
".zsh": "shell",
|
|
1788
|
+
".ps1": "shell",
|
|
1789
|
+
".sql": "sql",
|
|
1790
|
+
".md": "md",
|
|
1791
|
+
".mdx": "md",
|
|
1792
|
+
".toml": "toml",
|
|
1793
|
+
".html": "html",
|
|
1794
|
+
".htm": "html",
|
|
1795
|
+
".css": "css",
|
|
1796
|
+
".scss": "css",
|
|
1797
|
+
".less": "css",
|
|
1798
|
+
".vue": "vue",
|
|
1799
|
+
".svelte": "svelte",
|
|
1800
|
+
".proto": "proto",
|
|
1801
|
+
".graphql": "graphql",
|
|
1802
|
+
".gql": "graphql"
|
|
1803
|
+
};
|
|
1804
|
+
var INDEXABLE_EXTENSIONS = Object.freeze(
|
|
1805
|
+
[...new Set(Object.keys(EXT_TO_LANG).map((e) => e.toLowerCase()))].sort()
|
|
1806
|
+
);
|
|
1807
|
+
var SPECIAL_FILENAMES = {
|
|
1808
|
+
makefile: "other",
|
|
1809
|
+
gnumakefile: "other",
|
|
1810
|
+
dockerfile: "other",
|
|
1811
|
+
"docker-compose.yml": "yaml",
|
|
1812
|
+
"docker-compose.yaml": "yaml",
|
|
1813
|
+
"cmakelists.txt": "other",
|
|
1814
|
+
gemfile: "ruby",
|
|
1815
|
+
rakefile: "ruby",
|
|
1816
|
+
procfile: "other",
|
|
1817
|
+
justfile: "other"
|
|
1818
|
+
};
|
|
1819
|
+
function detectLang(file) {
|
|
1820
|
+
const base = path2.basename(file);
|
|
1821
|
+
const lowerBase = base.toLowerCase();
|
|
1822
|
+
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
1823
|
+
return "ts";
|
|
1824
|
+
}
|
|
1825
|
+
const special = SPECIAL_FILENAMES[lowerBase];
|
|
1826
|
+
if (special) return special;
|
|
1827
|
+
const ext = path2.extname(base).toLowerCase();
|
|
1828
|
+
if (!ext) return null;
|
|
1829
|
+
return EXT_TO_LANG[ext] ?? null;
|
|
1830
|
+
}
|
|
1831
|
+
function isIndexablePath(file) {
|
|
1832
|
+
return detectLang(file) !== null;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// src/codebase-index/ts-parser.ts
|
|
1466
1836
|
var KIND_MAP = {
|
|
1467
1837
|
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
1468
1838
|
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
@@ -1489,32 +1859,6 @@ function kindOf(node) {
|
|
|
1489
1859
|
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
1490
1860
|
return KIND_MAP[node.kind] ?? null;
|
|
1491
1861
|
}
|
|
1492
|
-
function extToLang(ext) {
|
|
1493
|
-
switch (ext) {
|
|
1494
|
-
case ".ts":
|
|
1495
|
-
return "ts";
|
|
1496
|
-
case ".tsx":
|
|
1497
|
-
return "tsx";
|
|
1498
|
-
case ".js":
|
|
1499
|
-
return "js";
|
|
1500
|
-
case ".jsx":
|
|
1501
|
-
return "jsx";
|
|
1502
|
-
case ".go":
|
|
1503
|
-
return "go";
|
|
1504
|
-
case ".py":
|
|
1505
|
-
return "py";
|
|
1506
|
-
case ".rs":
|
|
1507
|
-
return "rs";
|
|
1508
|
-
case ".json":
|
|
1509
|
-
return "json";
|
|
1510
|
-
case ".yaml":
|
|
1511
|
-
return "yaml";
|
|
1512
|
-
case ".yml":
|
|
1513
|
-
return "yaml";
|
|
1514
|
-
default:
|
|
1515
|
-
return null;
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
1862
|
function getSignature(printer, node, sourceFile) {
|
|
1519
1863
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
1520
1864
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
@@ -1637,30 +1981,25 @@ function deduplicateRefs(refs) {
|
|
|
1637
1981
|
return true;
|
|
1638
1982
|
});
|
|
1639
1983
|
}
|
|
1640
|
-
function detectLang(file) {
|
|
1641
|
-
const idx = file.lastIndexOf(".");
|
|
1642
|
-
if (idx < 0) return null;
|
|
1643
|
-
return extToLang(file.slice(idx));
|
|
1644
|
-
}
|
|
1645
1984
|
|
|
1646
1985
|
// src/codebase-index/go-parser.ts
|
|
1647
1986
|
import { spawn } from "node:child_process";
|
|
1648
1987
|
import * as os from "node:os";
|
|
1649
|
-
import * as
|
|
1988
|
+
import * as path4 from "node:path";
|
|
1650
1989
|
import * as fs3 from "node:fs/promises";
|
|
1651
1990
|
|
|
1652
1991
|
// src/_win32-resolve.ts
|
|
1653
1992
|
import * as fs2 from "node:fs";
|
|
1654
|
-
import * as
|
|
1993
|
+
import * as path3 from "node:path";
|
|
1655
1994
|
function resolveWin32Command(cmd) {
|
|
1656
1995
|
if (process.platform !== "win32") return cmd;
|
|
1657
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
1996
|
+
if (cmd.includes("/") || cmd.includes("\\") || path3.extname(cmd.replace(/\//g, "\\"))) {
|
|
1658
1997
|
return cmd;
|
|
1659
1998
|
}
|
|
1660
1999
|
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
1661
|
-
const pathDirs = (process.env["PATH"] ?? "").split(
|
|
2000
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path3.delimiter);
|
|
1662
2001
|
for (const dir of pathDirs) {
|
|
1663
|
-
const base =
|
|
2002
|
+
const base = path3.join(dir, cmd);
|
|
1664
2003
|
for (const ext of pathext) {
|
|
1665
2004
|
const full = `${base}${ext}`;
|
|
1666
2005
|
try {
|
|
@@ -1673,11 +2012,22 @@ function resolveWin32Command(cmd) {
|
|
|
1673
2012
|
return cmd;
|
|
1674
2013
|
}
|
|
1675
2014
|
|
|
2015
|
+
// src/codebase-index/spawn-gate.ts
|
|
2016
|
+
var chain = Promise.resolve();
|
|
2017
|
+
function withSpawnGate(fn) {
|
|
2018
|
+
const run = chain.then(fn, fn);
|
|
2019
|
+
chain = run.then(
|
|
2020
|
+
() => void 0,
|
|
2021
|
+
() => void 0
|
|
2022
|
+
);
|
|
2023
|
+
return run;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
1676
2026
|
// src/codebase-index/go-parser.ts
|
|
1677
2027
|
async function parseSymbols2(opts) {
|
|
1678
2028
|
const { file, content, lang } = opts;
|
|
1679
2029
|
try {
|
|
1680
|
-
const parsed = await syncGoParse(file, content, lang);
|
|
2030
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
1681
2031
|
if (parsed.symbols.length > 0) {
|
|
1682
2032
|
return parsed;
|
|
1683
2033
|
}
|
|
@@ -1973,11 +2323,16 @@ func formatType(t ast.Expr) string {
|
|
|
1973
2323
|
}
|
|
1974
2324
|
}
|
|
1975
2325
|
`;
|
|
2326
|
+
var _cachedGoScriptPath = null;
|
|
1976
2327
|
async function syncGoParse(filePath, content, lang) {
|
|
1977
|
-
const tmpDir = await fs3.mkdtemp(path3.join(os.tmpdir(), "ws-go-parse-"));
|
|
1978
2328
|
try {
|
|
1979
|
-
|
|
1980
|
-
|
|
2329
|
+
let scriptPath = _cachedGoScriptPath;
|
|
2330
|
+
if (!scriptPath) {
|
|
2331
|
+
const tmpDir = await fs3.mkdtemp(path4.join(os.tmpdir(), "ws-go-parse-"));
|
|
2332
|
+
scriptPath = path4.join(tmpDir, "parse.go");
|
|
2333
|
+
await fs3.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
2334
|
+
_cachedGoScriptPath = scriptPath;
|
|
2335
|
+
}
|
|
1981
2336
|
const goBinary = resolveWin32Command("go");
|
|
1982
2337
|
const goResult = await new Promise(
|
|
1983
2338
|
(resolve2, reject) => {
|
|
@@ -2034,23 +2389,311 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
2034
2389
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
2035
2390
|
} catch {
|
|
2036
2391
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2037
|
-
} finally {
|
|
2038
|
-
await fs3.rm(tmpDir, { recursive: true, force: true });
|
|
2039
2392
|
}
|
|
2040
2393
|
}
|
|
2041
2394
|
|
|
2042
2395
|
// src/codebase-index/py-parser.ts
|
|
2043
|
-
import { spawn as spawn2
|
|
2396
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2044
2397
|
import * as fs4 from "node:fs/promises";
|
|
2045
2398
|
import * as os2 from "node:os";
|
|
2046
|
-
import * as
|
|
2399
|
+
import * as path5 from "node:path";
|
|
2400
|
+
|
|
2401
|
+
// src/codebase-index/generic-parser.ts
|
|
2402
|
+
var C_LIKE = [
|
|
2403
|
+
{ re: /\b(?:class|struct|enum|interface|union)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2404
|
+
{
|
|
2405
|
+
re: /\b(?:public|private|protected|static|final|async|override|virtual|inline|export)?\s*(?:[\w:<>[\]\s*&]+)\s+([A-Za-z_]\w*)\s*\([^;{]*\)\s*(?:const)?\s*[{;]/g,
|
|
2406
|
+
kind: "function"
|
|
2407
|
+
},
|
|
2408
|
+
{ re: /\b(?:namespace)\s+([A-Za-z_]\w*)/g, kind: "namespace" }
|
|
2409
|
+
];
|
|
2410
|
+
var LANG_PATTERNS = {
|
|
2411
|
+
py: [
|
|
2412
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
2413
|
+
{ re: /^class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
2414
|
+
{ re: /^([A-Za-z_]\w*)\s*=/gm, kind: "var" }
|
|
2415
|
+
],
|
|
2416
|
+
go: [
|
|
2417
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/gm, kind: "function" },
|
|
2418
|
+
{ re: /^type\s+([A-Za-z_]\w*)\b/gm, kind: "type" },
|
|
2419
|
+
{ re: /^(?:const|var)\s+([A-Za-z_]\w*)\b/gm, kind: "const" },
|
|
2420
|
+
{ re: /^package\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
2421
|
+
],
|
|
2422
|
+
rs: [
|
|
2423
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2424
|
+
{ re: /\bstruct\s+([A-Za-z_]\w*)/g, kind: "struct" },
|
|
2425
|
+
{ re: /\benum\s+([A-Za-z_]\w*)/g, kind: "enum" },
|
|
2426
|
+
{ re: /\btrait\s+([A-Za-z_]\w*)/g, kind: "trait" },
|
|
2427
|
+
{ re: /\bimpl(?:\s*<[^>]+>)?\s+([A-Za-z_]\w*)/g, kind: "impl" },
|
|
2428
|
+
{ re: /\b(?:const|static)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
2429
|
+
{ re: /\bmod\s+([A-Za-z_]\w*)/g, kind: "mod" }
|
|
2430
|
+
],
|
|
2431
|
+
c: C_LIKE,
|
|
2432
|
+
cpp: C_LIKE,
|
|
2433
|
+
java: [
|
|
2434
|
+
{ re: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2435
|
+
{
|
|
2436
|
+
re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
2437
|
+
kind: "method"
|
|
2438
|
+
}
|
|
2439
|
+
],
|
|
2440
|
+
csharp: [
|
|
2441
|
+
{ re: /\b(?:class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2442
|
+
{ re: /\bnamespace\s+([A-Za-z_.\w]+)/g, kind: "namespace" },
|
|
2443
|
+
{
|
|
2444
|
+
re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
2445
|
+
kind: "method"
|
|
2446
|
+
}
|
|
2447
|
+
],
|
|
2448
|
+
php: [
|
|
2449
|
+
{ re: /\bfunction\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2450
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2451
|
+
{ re: /\binterface\s+([A-Za-z_]\w*)/g, kind: "interface" },
|
|
2452
|
+
{ re: /\bnamespace\s+([A-Za-z_\\]+)/g, kind: "namespace" }
|
|
2453
|
+
],
|
|
2454
|
+
ruby: [
|
|
2455
|
+
{ re: /^\s*def\s+(?:self\.)?([A-Za-z_]\w*[!?]?)/gm, kind: "function" },
|
|
2456
|
+
{ re: /^\s*class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
2457
|
+
{ re: /^\s*module\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
2458
|
+
],
|
|
2459
|
+
swift: [
|
|
2460
|
+
{ re: /\b(?:func)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2461
|
+
{ re: /\b(?:class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
2462
|
+
],
|
|
2463
|
+
kotlin: [
|
|
2464
|
+
{ re: /\b(?:fun)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2465
|
+
{ re: /\b(?:class|interface|object|enum\s+class|data\s+class)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
2466
|
+
],
|
|
2467
|
+
scala: [
|
|
2468
|
+
{ re: /\b(?:def)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2469
|
+
{ re: /\b(?:class|object|trait|enum)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
2470
|
+
],
|
|
2471
|
+
shell: [
|
|
2472
|
+
{ re: /^(?:function\s+)?([A-Za-z_]\w*)\s*\(\)\s*\{/gm, kind: "function" },
|
|
2473
|
+
{ re: /^([A-Za-z_][\w]*)\s*\(\)\s*\{/gm, kind: "function" }
|
|
2474
|
+
],
|
|
2475
|
+
sql: [
|
|
2476
|
+
{ re: /\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:TABLE|VIEW|INDEX|FUNCTION|PROCEDURE|TRIGGER)\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z_"][\w."]*)/gi, kind: "type" }
|
|
2477
|
+
],
|
|
2478
|
+
md: [
|
|
2479
|
+
{ re: /^(#{1,6})\s+(.+)$/gm, kind: "namespace" }
|
|
2480
|
+
],
|
|
2481
|
+
toml: [
|
|
2482
|
+
{ re: /^\[([^\]]+)\]/gm, kind: "namespace" }
|
|
2483
|
+
],
|
|
2484
|
+
html: [
|
|
2485
|
+
{ re: /\bid\s*=\s*["']([^"']+)["']/gi, kind: "property" },
|
|
2486
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
2487
|
+
],
|
|
2488
|
+
css: [
|
|
2489
|
+
{ re: /^\s*([.#]?[A-Za-z_][\w-]*)\s*\{/gm, kind: "type" },
|
|
2490
|
+
{ re: /@(?:keyframes|media|supports)\s+([^{\s]+)/g, kind: "namespace" }
|
|
2491
|
+
],
|
|
2492
|
+
vue: [
|
|
2493
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2494
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
2495
|
+
],
|
|
2496
|
+
svelte: [
|
|
2497
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
2498
|
+
],
|
|
2499
|
+
dart: [
|
|
2500
|
+
{ re: /\b(?:class|enum|mixin|extension)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2501
|
+
{ re: /\b([A-Za-z_]\w*)\s*\([^;]*\)\s*(?:async\s*)?\{/g, kind: "function" }
|
|
2502
|
+
],
|
|
2503
|
+
lua: [
|
|
2504
|
+
{ re: /\bfunction\s+([A-Za-z_.:]\w*)/g, kind: "function" },
|
|
2505
|
+
{ re: /\blocal\s+function\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
2506
|
+
],
|
|
2507
|
+
r: [
|
|
2508
|
+
{ re: /([A-Za-z.]\w*)\s*<-\s*function\s*\(/g, kind: "function" },
|
|
2509
|
+
{ re: /([A-Za-z.]\w*)\s*=\s*function\s*\(/g, kind: "function" }
|
|
2510
|
+
],
|
|
2511
|
+
proto: [
|
|
2512
|
+
{ re: /\b(?:message|service|enum)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
2513
|
+
{ re: /\brpc\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
2514
|
+
],
|
|
2515
|
+
graphql: [
|
|
2516
|
+
{ re: /\b(?:type|interface|enum|input|union|scalar)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
2517
|
+
{ re: /\b(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
2518
|
+
],
|
|
2519
|
+
zig: [
|
|
2520
|
+
{ re: /\b(?:fn|pub\s+fn)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2521
|
+
{ re: /\b(?:const|var)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
2522
|
+
{ re: /\b(?:struct|enum|union)\s*\{/g, kind: "type" }
|
|
2523
|
+
],
|
|
2524
|
+
elixir: [
|
|
2525
|
+
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
2526
|
+
{ re: /\bdefmodule\s+([A-Za-z_.]\w*)/g, kind: "namespace" }
|
|
2527
|
+
],
|
|
2528
|
+
haskell: [
|
|
2529
|
+
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
2530
|
+
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
2531
|
+
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
2532
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
2533
|
+
],
|
|
2534
|
+
other: [
|
|
2535
|
+
{ re: /^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
2536
|
+
{ re: /^(?:export\s+)?class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
2537
|
+
{ re: /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)/gm, kind: "const" },
|
|
2538
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
2539
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/gm, kind: "function" },
|
|
2540
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2541
|
+
{ re: /^(?:target|PHONY)\s*:/gm, kind: "namespace" }
|
|
2542
|
+
]
|
|
2543
|
+
};
|
|
2544
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
2545
|
+
"if",
|
|
2546
|
+
"else",
|
|
2547
|
+
"for",
|
|
2548
|
+
"while",
|
|
2549
|
+
"switch",
|
|
2550
|
+
"case",
|
|
2551
|
+
"return",
|
|
2552
|
+
"break",
|
|
2553
|
+
"continue",
|
|
2554
|
+
"new",
|
|
2555
|
+
"delete",
|
|
2556
|
+
"typeof",
|
|
2557
|
+
"instanceof",
|
|
2558
|
+
"void",
|
|
2559
|
+
"null",
|
|
2560
|
+
"true",
|
|
2561
|
+
"false",
|
|
2562
|
+
"this",
|
|
2563
|
+
"super",
|
|
2564
|
+
"import",
|
|
2565
|
+
"export",
|
|
2566
|
+
"from",
|
|
2567
|
+
"as",
|
|
2568
|
+
"default",
|
|
2569
|
+
"public",
|
|
2570
|
+
"private",
|
|
2571
|
+
"protected",
|
|
2572
|
+
"static",
|
|
2573
|
+
"final",
|
|
2574
|
+
"class",
|
|
2575
|
+
"struct",
|
|
2576
|
+
"enum",
|
|
2577
|
+
"interface",
|
|
2578
|
+
"function",
|
|
2579
|
+
"def",
|
|
2580
|
+
"fn",
|
|
2581
|
+
"func",
|
|
2582
|
+
"var",
|
|
2583
|
+
"let",
|
|
2584
|
+
"const",
|
|
2585
|
+
"package",
|
|
2586
|
+
"namespace",
|
|
2587
|
+
"module",
|
|
2588
|
+
"using",
|
|
2589
|
+
"include",
|
|
2590
|
+
"require",
|
|
2591
|
+
"select",
|
|
2592
|
+
"from",
|
|
2593
|
+
"where",
|
|
2594
|
+
"and",
|
|
2595
|
+
"or",
|
|
2596
|
+
"not",
|
|
2597
|
+
"in",
|
|
2598
|
+
"is",
|
|
2599
|
+
"try",
|
|
2600
|
+
"catch",
|
|
2601
|
+
"finally",
|
|
2602
|
+
"throw",
|
|
2603
|
+
"async",
|
|
2604
|
+
"await",
|
|
2605
|
+
"yield"
|
|
2606
|
+
]);
|
|
2607
|
+
function patternsFor(lang) {
|
|
2608
|
+
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
2609
|
+
}
|
|
2610
|
+
function looksBinary(content) {
|
|
2611
|
+
if (content.includes("\0")) return true;
|
|
2612
|
+
const sample = content.slice(0, 2048);
|
|
2613
|
+
if (sample.length === 0) return false;
|
|
2614
|
+
let bad = 0;
|
|
2615
|
+
for (let i = 0; i < sample.length; i++) {
|
|
2616
|
+
const c = sample.charCodeAt(i);
|
|
2617
|
+
if (c === 9 || c === 10 || c === 13) continue;
|
|
2618
|
+
if (c < 32 || c === 127) bad++;
|
|
2619
|
+
}
|
|
2620
|
+
return bad / sample.length > 0.1;
|
|
2621
|
+
}
|
|
2622
|
+
function lineColAt(content, index) {
|
|
2623
|
+
let line = 1;
|
|
2624
|
+
let lastNl = -1;
|
|
2625
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
2626
|
+
if (content.charCodeAt(i) === 10) {
|
|
2627
|
+
line++;
|
|
2628
|
+
lastNl = i;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
return { line, col: index - lastNl };
|
|
2632
|
+
}
|
|
2633
|
+
var GENERIC_MAX_SYMBOLS_DEFAULT = 500;
|
|
2634
|
+
var GENERIC_MAX_FILE_CHARS = 512 * 1024;
|
|
2635
|
+
function parseGeneric(opts) {
|
|
2636
|
+
const { file, lang } = opts;
|
|
2637
|
+
const maxSymbols = opts.maxSymbols ?? GENERIC_MAX_SYMBOLS_DEFAULT;
|
|
2638
|
+
const mtimeMs = Date.now();
|
|
2639
|
+
if (!opts.content || looksBinary(opts.content)) {
|
|
2640
|
+
return { file, lang, symbols: [], mtimeMs };
|
|
2641
|
+
}
|
|
2642
|
+
const content = opts.content.length > GENERIC_MAX_FILE_CHARS ? opts.content.slice(0, GENERIC_MAX_FILE_CHARS) : opts.content;
|
|
2643
|
+
const patterns = patternsFor(lang);
|
|
2644
|
+
const symbols = [];
|
|
2645
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2646
|
+
for (const pattern of patterns) {
|
|
2647
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
2648
|
+
re.lastIndex = 0;
|
|
2649
|
+
for (const match of content.matchAll(re)) {
|
|
2650
|
+
if (symbols.length >= maxSymbols) break;
|
|
2651
|
+
let name = (match[1] ?? match[2] ?? "").trim();
|
|
2652
|
+
if (lang === "md" && match[2]) name = match[2].trim();
|
|
2653
|
+
if (!name || name.length > 200) continue;
|
|
2654
|
+
name = name.replace(/^#+\s*/, "").replace(/["'`]/g, "");
|
|
2655
|
+
if (!name || KEYWORDS.has(name.toLowerCase())) continue;
|
|
2656
|
+
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
const { line, col } = lineColAt(content, match.index);
|
|
2660
|
+
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
2661
|
+
if (seen.has(key)) continue;
|
|
2662
|
+
seen.add(key);
|
|
2663
|
+
const nl = content.indexOf("\n", match.index);
|
|
2664
|
+
const lineText = content.slice(match.index, nl === -1 ? content.length : nl);
|
|
2665
|
+
const signature = (lineText || name).trim().slice(0, 500);
|
|
2666
|
+
symbols.push({
|
|
2667
|
+
id: 0,
|
|
2668
|
+
lang,
|
|
2669
|
+
kind: lang === "md" ? "namespace" : pattern.kind,
|
|
2670
|
+
name: name.slice(0, 200),
|
|
2671
|
+
file,
|
|
2672
|
+
line,
|
|
2673
|
+
col,
|
|
2674
|
+
signature,
|
|
2675
|
+
docComment: "",
|
|
2676
|
+
scope: "",
|
|
2677
|
+
text: `${name} ${signature}`.trim().slice(0, 1e3)
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
2680
|
+
if (symbols.length >= maxSymbols) break;
|
|
2681
|
+
}
|
|
2682
|
+
return { file, lang, symbols, mtimeMs };
|
|
2683
|
+
}
|
|
2047
2684
|
async function parseSymbols3(opts) {
|
|
2685
|
+
return parseGeneric(opts);
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
// src/codebase-index/py-parser.ts
|
|
2689
|
+
async function parseSymbols4(opts) {
|
|
2048
2690
|
const { file, content, lang } = opts;
|
|
2049
2691
|
try {
|
|
2050
|
-
|
|
2692
|
+
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
2693
|
+
if (native !== null) return native;
|
|
2051
2694
|
} catch {
|
|
2052
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2053
2695
|
}
|
|
2696
|
+
return parseGeneric({ file, content, lang: lang === "py" ? "py" : lang });
|
|
2054
2697
|
}
|
|
2055
2698
|
var PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
2056
2699
|
|
|
@@ -2255,20 +2898,37 @@ visitor.visit(tree)
|
|
|
2255
2898
|
|
|
2256
2899
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
2257
2900
|
`;
|
|
2258
|
-
function resolvePython() {
|
|
2901
|
+
async function resolvePython() {
|
|
2259
2902
|
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
2260
2903
|
for (const name of candidates) {
|
|
2261
2904
|
const resolved = resolveWin32Command(name);
|
|
2262
|
-
|
|
2263
|
-
stdio: "pipe",
|
|
2264
|
-
timeout: 5e3
|
|
2265
|
-
});
|
|
2266
|
-
if (result.error) continue;
|
|
2267
|
-
if (result.status !== 0) continue;
|
|
2905
|
+
if (!await commandIsAvailable(resolved)) continue;
|
|
2268
2906
|
return resolved;
|
|
2269
2907
|
}
|
|
2270
2908
|
return null;
|
|
2271
2909
|
}
|
|
2910
|
+
function commandIsAvailable(command) {
|
|
2911
|
+
return new Promise((resolve2) => {
|
|
2912
|
+
let settled = false;
|
|
2913
|
+
const proc = spawn2(command, ["--version"], {
|
|
2914
|
+
stdio: "ignore",
|
|
2915
|
+
windowsHide: true
|
|
2916
|
+
});
|
|
2917
|
+
const finish = (available) => {
|
|
2918
|
+
if (settled) return;
|
|
2919
|
+
settled = true;
|
|
2920
|
+
clearTimeout(timer);
|
|
2921
|
+
resolve2(available);
|
|
2922
|
+
};
|
|
2923
|
+
const timer = setTimeout(() => {
|
|
2924
|
+
proc.kill("SIGKILL");
|
|
2925
|
+
finish(false);
|
|
2926
|
+
}, 5e3);
|
|
2927
|
+
timer.unref?.();
|
|
2928
|
+
proc.once("error", () => finish(false));
|
|
2929
|
+
proc.once("close", (code) => finish(code === 0));
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2272
2932
|
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
2273
2933
|
return new Promise((resolve2, reject) => {
|
|
2274
2934
|
let settled = false;
|
|
@@ -2304,21 +2964,20 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
2304
2964
|
});
|
|
2305
2965
|
}
|
|
2306
2966
|
var _cachedScriptPath = null;
|
|
2307
|
-
var
|
|
2967
|
+
var cachedPyBinary;
|
|
2308
2968
|
async function syncPyParse(filePath, content, lang) {
|
|
2309
2969
|
try {
|
|
2310
2970
|
if (!_cachedScriptPath) {
|
|
2311
|
-
const tmpDir =
|
|
2971
|
+
const tmpDir = path5.join(os2.tmpdir(), "ws-py-parse");
|
|
2312
2972
|
await fs4.mkdir(tmpDir, { recursive: true });
|
|
2313
|
-
_cachedScriptPath =
|
|
2973
|
+
_cachedScriptPath = path5.join(tmpDir, "parse.py");
|
|
2314
2974
|
await fs4.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
2315
2975
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
}
|
|
2976
|
+
cachedPyBinary ??= resolvePython();
|
|
2977
|
+
const pyBinary = await cachedPyBinary;
|
|
2978
|
+
if (!pyBinary) return null;
|
|
2320
2979
|
const { code, stdout } = await spawnPyParser(
|
|
2321
|
-
|
|
2980
|
+
pyBinary,
|
|
2322
2981
|
_cachedScriptPath,
|
|
2323
2982
|
filePath,
|
|
2324
2983
|
content
|
|
@@ -2342,30 +3001,39 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
2342
3001
|
}));
|
|
2343
3002
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
2344
3003
|
} catch {
|
|
2345
|
-
return
|
|
3004
|
+
return null;
|
|
2346
3005
|
}
|
|
2347
3006
|
}
|
|
2348
3007
|
|
|
2349
3008
|
// src/codebase-index/rs-parser.ts
|
|
2350
|
-
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
2351
|
-
import {
|
|
3009
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
3010
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
2352
3011
|
import * as fs5 from "node:fs/promises";
|
|
2353
|
-
import * as
|
|
2354
|
-
async function
|
|
3012
|
+
import * as path6 from "node:path";
|
|
3013
|
+
async function parseSymbols5(opts) {
|
|
2355
3014
|
const { file, content, lang } = opts;
|
|
2356
|
-
const nativeAvailable = checkNativeParser();
|
|
3015
|
+
const nativeAvailable = await checkNativeParser();
|
|
2357
3016
|
if (nativeAvailable) {
|
|
2358
|
-
const result = await tryNativeParse(file, content);
|
|
3017
|
+
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
2359
3018
|
if (result) return result;
|
|
2360
3019
|
}
|
|
2361
3020
|
return regexParse({ file, content, lang });
|
|
2362
3021
|
}
|
|
3022
|
+
var nativeParserAvailability;
|
|
3023
|
+
function probe(command, args) {
|
|
3024
|
+
return new Promise((resolve2, reject) => {
|
|
3025
|
+
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
3026
|
+
if (error) reject(error);
|
|
3027
|
+
else resolve2();
|
|
3028
|
+
});
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
2363
3031
|
function checkNativeParser() {
|
|
2364
|
-
|
|
2365
|
-
execFileSync("rustc", ["--version"], { stdio: "pipe", windowsHide: true });
|
|
2366
|
-
const toolsDir = path5.join(process.cwd(), "tools");
|
|
3032
|
+
nativeParserAvailability ??= (async () => {
|
|
2367
3033
|
try {
|
|
2368
|
-
|
|
3034
|
+
await probe("rustc", ["--version"]);
|
|
3035
|
+
const toolsDir = path6.join(process.cwd(), "tools");
|
|
3036
|
+
await probe(
|
|
2369
3037
|
"cargo",
|
|
2370
3038
|
[
|
|
2371
3039
|
"metadata",
|
|
@@ -2373,23 +3041,21 @@ function checkNativeParser() {
|
|
|
2373
3041
|
"--format-version",
|
|
2374
3042
|
"1",
|
|
2375
3043
|
"--manifest-path",
|
|
2376
|
-
|
|
2377
|
-
]
|
|
2378
|
-
{ stdio: "pipe", windowsHide: true }
|
|
3044
|
+
path6.join(toolsDir, "Cargo.toml")
|
|
3045
|
+
]
|
|
2379
3046
|
);
|
|
2380
3047
|
return true;
|
|
2381
3048
|
} catch {
|
|
2382
3049
|
return false;
|
|
2383
3050
|
}
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
}
|
|
3051
|
+
})();
|
|
3052
|
+
return nativeParserAvailability;
|
|
2387
3053
|
}
|
|
2388
3054
|
async function tryNativeParse(file, content) {
|
|
2389
3055
|
try {
|
|
2390
|
-
const toolsDir =
|
|
2391
|
-
const crateDir =
|
|
2392
|
-
const tmpFile =
|
|
3056
|
+
const toolsDir = path6.join(process.cwd(), "tools");
|
|
3057
|
+
const crateDir = path6.join(toolsDir, "syn-parser");
|
|
3058
|
+
const tmpFile = path6.join(crateDir, "src", "input.rs");
|
|
2393
3059
|
await fs5.writeFile(tmpFile, content, "utf8");
|
|
2394
3060
|
const cargoBinary = resolveWin32Command("cargo");
|
|
2395
3061
|
const result = await new Promise(
|
|
@@ -2397,7 +3063,7 @@ async function tryNativeParse(file, content) {
|
|
|
2397
3063
|
let settled = false;
|
|
2398
3064
|
const proc = spawn3(
|
|
2399
3065
|
cargoBinary,
|
|
2400
|
-
["run", "--manifest-path",
|
|
3066
|
+
["run", "--manifest-path", path6.join(toolsDir, "Cargo.toml")],
|
|
2401
3067
|
{
|
|
2402
3068
|
cwd: process.cwd(),
|
|
2403
3069
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2511,9 +3177,9 @@ function regexParse(opts) {
|
|
|
2511
3177
|
}
|
|
2512
3178
|
|
|
2513
3179
|
// src/codebase-index/json-parser.ts
|
|
2514
|
-
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
2515
|
-
import * as
|
|
2516
|
-
function
|
|
3180
|
+
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
3181
|
+
import * as path7 from "node:path";
|
|
3182
|
+
function parseSymbols6(opts) {
|
|
2517
3183
|
const { file, content, lang } = opts;
|
|
2518
3184
|
try {
|
|
2519
3185
|
return regexParse2({ file, content, lang });
|
|
@@ -2524,9 +3190,9 @@ function parseSymbols5(opts) {
|
|
|
2524
3190
|
function regexParse2(opts) {
|
|
2525
3191
|
const { file, content, lang } = opts;
|
|
2526
3192
|
const symbols = [];
|
|
2527
|
-
const
|
|
2528
|
-
const isPackageJson =
|
|
2529
|
-
const isTsconfig =
|
|
3193
|
+
const basename4 = path7.basename(file).toLowerCase();
|
|
3194
|
+
const isPackageJson = basename4 === "package.json";
|
|
3195
|
+
const isTsconfig = basename4 === "tsconfig.json" || basename4 === "tsconfig.build.json";
|
|
2530
3196
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
2531
3197
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
2532
3198
|
const lines = content.split("\n");
|
|
@@ -2550,11 +3216,11 @@ function regexParse2(opts) {
|
|
|
2550
3216
|
const line = lineFromOffset(offset);
|
|
2551
3217
|
symbols.push(
|
|
2552
3218
|
makeSymbol({
|
|
2553
|
-
name:
|
|
3219
|
+
name: path7.basename(file),
|
|
2554
3220
|
kind: "object",
|
|
2555
3221
|
line,
|
|
2556
3222
|
col: 0,
|
|
2557
|
-
signature: `"${
|
|
3223
|
+
signature: `"${path7.basename(file)}" = { ... }`,
|
|
2558
3224
|
file,
|
|
2559
3225
|
lang
|
|
2560
3226
|
})
|
|
@@ -2716,8 +3382,8 @@ function makeSymbol(opts) {
|
|
|
2716
3382
|
}
|
|
2717
3383
|
|
|
2718
3384
|
// src/codebase-index/yaml-parser.ts
|
|
2719
|
-
import { expectDefined as expectDefined4, truncate } from "@wrongstack/core";
|
|
2720
|
-
function
|
|
3385
|
+
import { expectDefined as expectDefined4, truncate } from "@wrongstack/core/utils";
|
|
3386
|
+
function parseSymbols7(opts) {
|
|
2721
3387
|
const { file, content, lang } = opts;
|
|
2722
3388
|
try {
|
|
2723
3389
|
return regexParse3({ file, content, lang });
|
|
@@ -2866,8 +3532,8 @@ function makeSymbol2(opts) {
|
|
|
2866
3532
|
|
|
2867
3533
|
// src/codebase-index/gitignore.ts
|
|
2868
3534
|
import * as fs6 from "node:fs/promises";
|
|
2869
|
-
import * as
|
|
2870
|
-
import { compileGlob } from "@wrongstack/core";
|
|
3535
|
+
import * as path8 from "node:path";
|
|
3536
|
+
import { compileGlob } from "@wrongstack/core/utils";
|
|
2871
3537
|
function globBody(glob) {
|
|
2872
3538
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
2873
3539
|
}
|
|
@@ -2912,7 +3578,7 @@ function compileGitignore(lines) {
|
|
|
2912
3578
|
async function loadGitignoreMatcher(projectRoot) {
|
|
2913
3579
|
let lines = [];
|
|
2914
3580
|
try {
|
|
2915
|
-
const raw = await fs6.readFile(
|
|
3581
|
+
const raw = await fs6.readFile(path8.join(projectRoot, ".gitignore"), "utf8");
|
|
2916
3582
|
lines = raw.split("\n");
|
|
2917
3583
|
} catch {
|
|
2918
3584
|
}
|
|
@@ -2921,7 +3587,9 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
2921
3587
|
|
|
2922
3588
|
// src/codebase-index/indexer.ts
|
|
2923
3589
|
var YIELD_EVERY_N = 50;
|
|
2924
|
-
|
|
3590
|
+
function resolveParallelBatch() {
|
|
3591
|
+
return indexParallelBatchSize(availableParallelism());
|
|
3592
|
+
}
|
|
2925
3593
|
function yieldEventLoop() {
|
|
2926
3594
|
return new Promise((resolve2) => setImmediate(resolve2));
|
|
2927
3595
|
}
|
|
@@ -2933,22 +3601,12 @@ function throwIfAborted(signal) {
|
|
|
2933
3601
|
function isAbortError(err) {
|
|
2934
3602
|
return err instanceof DOMException && err.name === "AbortError";
|
|
2935
3603
|
}
|
|
2936
|
-
var DEFAULT_IGNORE =
|
|
2937
|
-
"node_modules",
|
|
2938
|
-
".git",
|
|
2939
|
-
"dist",
|
|
2940
|
-
"build",
|
|
2941
|
-
".next",
|
|
2942
|
-
"coverage",
|
|
2943
|
-
".turbo",
|
|
2944
|
-
"__snapshots__",
|
|
2945
|
-
".nyc_output"
|
|
2946
|
-
];
|
|
3604
|
+
var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
|
|
2947
3605
|
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
2948
3606
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
2949
3607
|
function isWithinProject(projectRoot, file) {
|
|
2950
|
-
const rel =
|
|
2951
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
3608
|
+
const rel = path9.relative(projectRoot, file);
|
|
3609
|
+
return rel !== "" && !rel.startsWith(`..${path9.sep}`) && rel !== ".." && !path9.isAbsolute(rel);
|
|
2952
3610
|
}
|
|
2953
3611
|
function isMissingPathError(err) {
|
|
2954
3612
|
const code = err?.code;
|
|
@@ -2959,18 +3617,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2959
3617
|
const errors = [];
|
|
2960
3618
|
let complete = true;
|
|
2961
3619
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
2962
|
-
const
|
|
2963
|
-
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
2964
|
-
{ ext: ".tsx", pat: compileGlob2("**/*.tsx") },
|
|
2965
|
-
{ ext: ".js", pat: compileGlob2("**/*.js") },
|
|
2966
|
-
{ ext: ".jsx", pat: compileGlob2("**/*.jsx") },
|
|
2967
|
-
{ ext: ".go", pat: compileGlob2("**/*.go") },
|
|
2968
|
-
{ ext: ".py", pat: compileGlob2("**/*.py") },
|
|
2969
|
-
{ ext: ".rs", pat: compileGlob2("**/*.rs") },
|
|
2970
|
-
{ ext: ".json", pat: compileGlob2("**/*.json") },
|
|
2971
|
-
{ ext: ".yaml", pat: compileGlob2("**/*.yaml") },
|
|
2972
|
-
{ ext: ".yml", pat: compileGlob2("**/*.yml") }
|
|
2973
|
-
];
|
|
3620
|
+
const indexableExts = new Set(INDEXABLE_EXTENSIONS);
|
|
2974
3621
|
let dirCount = 0;
|
|
2975
3622
|
const walk = async (dir) => {
|
|
2976
3623
|
throwIfAborted(signal);
|
|
@@ -2989,19 +3636,16 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2989
3636
|
dirCount++;
|
|
2990
3637
|
for (const e of entries) {
|
|
2991
3638
|
if (ignoreSet.has(e.name)) continue;
|
|
2992
|
-
const full =
|
|
2993
|
-
const rel =
|
|
3639
|
+
const full = path9.join(dir, e.name);
|
|
3640
|
+
const rel = path9.relative(projectRoot, full).replace(/\\/g, "/");
|
|
2994
3641
|
if (e.isDirectory()) {
|
|
2995
3642
|
if (isGitIgnored(rel, true)) continue;
|
|
2996
3643
|
await walk(full);
|
|
2997
3644
|
} else if (e.isFile()) {
|
|
2998
3645
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
2999
|
-
const ext =
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
results.push(full);
|
|
3003
|
-
break;
|
|
3004
|
-
}
|
|
3646
|
+
const ext = path9.extname(e.name).toLowerCase();
|
|
3647
|
+
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
3648
|
+
results.push(full);
|
|
3005
3649
|
}
|
|
3006
3650
|
}
|
|
3007
3651
|
}
|
|
@@ -3010,24 +3654,25 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
3010
3654
|
return { files: results, complete, errors };
|
|
3011
3655
|
}
|
|
3012
3656
|
async function parseFile(file, content, lang) {
|
|
3013
|
-
|
|
3657
|
+
const symbolLang = lang;
|
|
3658
|
+
switch (symbolLang) {
|
|
3014
3659
|
case "ts":
|
|
3015
3660
|
case "tsx":
|
|
3016
3661
|
case "js":
|
|
3017
3662
|
case "jsx":
|
|
3018
|
-
return parseSymbols({ file, content, lang });
|
|
3663
|
+
return parseSymbols({ file, content, lang: symbolLang });
|
|
3019
3664
|
case "go":
|
|
3020
3665
|
return parseSymbols2({ file, content, lang: "go" });
|
|
3021
3666
|
case "py":
|
|
3022
|
-
return
|
|
3667
|
+
return parseSymbols4({ file, content, lang: "py" });
|
|
3023
3668
|
case "rs":
|
|
3024
|
-
return
|
|
3669
|
+
return parseSymbols5({ file, content, lang: "rs" });
|
|
3025
3670
|
case "json":
|
|
3026
|
-
return
|
|
3671
|
+
return parseSymbols6({ file, content, lang: "json" });
|
|
3027
3672
|
case "yaml":
|
|
3028
|
-
return
|
|
3673
|
+
return parseSymbols7({ file, content, lang: "yaml" });
|
|
3029
3674
|
default:
|
|
3030
|
-
return { file,
|
|
3675
|
+
return parseSymbols3({ file, content, lang: symbolLang });
|
|
3031
3676
|
}
|
|
3032
3677
|
}
|
|
3033
3678
|
function assignRefsToSymbols2(refs, symbols) {
|
|
@@ -3075,10 +3720,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
3075
3720
|
let discoveredFiles = null;
|
|
3076
3721
|
let discoveryComplete = true;
|
|
3077
3722
|
if (opts.files && opts.files.length > 0) {
|
|
3078
|
-
files = opts.files.map((f) =>
|
|
3723
|
+
files = opts.files.map((f) => path9.resolve(projectRoot, f)).filter((f) => {
|
|
3079
3724
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
3080
|
-
const rel =
|
|
3081
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
3725
|
+
const rel = path9.relative(projectRoot, f).replace(/\\/g, "/");
|
|
3726
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path9.basename(f)) && !isGitIgnored(rel, false);
|
|
3082
3727
|
});
|
|
3083
3728
|
} else {
|
|
3084
3729
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -3099,15 +3744,19 @@ async function runIndexerWithStore(store, opts) {
|
|
|
3099
3744
|
if (!force) {
|
|
3100
3745
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
3101
3746
|
}
|
|
3747
|
+
const parallelBatch = resolveParallelBatch();
|
|
3102
3748
|
let filesSinceLastYield = 0;
|
|
3103
|
-
for (let batchStart = 0; batchStart < files.length; batchStart +=
|
|
3104
|
-
const batchEnd = Math.min(batchStart +
|
|
3749
|
+
for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
|
|
3750
|
+
const batchEnd = Math.min(batchStart + parallelBatch, files.length);
|
|
3105
3751
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
3106
3752
|
opts.onProgress?.(batchEnd, files.length);
|
|
3107
3753
|
filesSinceLastYield += batchFiles.length;
|
|
3108
3754
|
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
3109
3755
|
filesSinceLastYield = 0;
|
|
3110
3756
|
await yieldEventLoop();
|
|
3757
|
+
if (isFrugalPerf()) {
|
|
3758
|
+
await new Promise((r) => setTimeout(r, 8));
|
|
3759
|
+
}
|
|
3111
3760
|
throwIfAborted(signal);
|
|
3112
3761
|
}
|
|
3113
3762
|
const statOpts = signal ? { signal } : {};
|
|
@@ -3211,9 +3860,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
3211
3860
|
continue;
|
|
3212
3861
|
}
|
|
3213
3862
|
if (parsed.symbols.length === 0) {
|
|
3214
|
-
store.
|
|
3215
|
-
store.deleteSymbolsForFile(file);
|
|
3216
|
-
store.upsertFile({
|
|
3863
|
+
store.replaceEmptyFile({
|
|
3217
3864
|
file,
|
|
3218
3865
|
lang,
|
|
3219
3866
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
@@ -3282,6 +3929,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
3282
3929
|
}
|
|
3283
3930
|
store.resolveRefs();
|
|
3284
3931
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
3932
|
+
store.optimize();
|
|
3285
3933
|
const durationMs = Date.now() - startMs;
|
|
3286
3934
|
store.setLastIndexed(Date.now());
|
|
3287
3935
|
return {
|
|
@@ -3572,10 +4220,10 @@ async function callInline(op, args, opts) {
|
|
|
3572
4220
|
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
3573
4221
|
}
|
|
3574
4222
|
}
|
|
3575
|
-
var
|
|
4223
|
+
var chain2 = Promise.resolve();
|
|
3576
4224
|
function withMutex(job) {
|
|
3577
|
-
const run =
|
|
3578
|
-
|
|
4225
|
+
const run = chain2.then(job, job);
|
|
4226
|
+
chain2 = run.then(
|
|
3579
4227
|
() => void 0,
|
|
3580
4228
|
() => void 0
|
|
3581
4229
|
);
|
|
@@ -3589,11 +4237,62 @@ function circuitOpenError() {
|
|
|
3589
4237
|
}
|
|
3590
4238
|
var DEFAULT_DEBOUNCE_MS = 400;
|
|
3591
4239
|
var debounceTimers = /* @__PURE__ */ new Map();
|
|
3592
|
-
|
|
3593
|
-
|
|
4240
|
+
var readyReindexBatches = /* @__PURE__ */ new Map();
|
|
4241
|
+
function debounceKey(projectRoot, indexDir, file) {
|
|
4242
|
+
return JSON.stringify([projectRoot, indexDir ?? "", file]);
|
|
4243
|
+
}
|
|
4244
|
+
function reindexBatchKey(projectRoot, indexDir) {
|
|
4245
|
+
return JSON.stringify([projectRoot, indexDir ?? ""]);
|
|
4246
|
+
}
|
|
4247
|
+
function flushReadyReindexBatch(key) {
|
|
4248
|
+
const batch = readyReindexBatches.get(key);
|
|
4249
|
+
if (!batch) return;
|
|
4250
|
+
readyReindexBatches.delete(key);
|
|
4251
|
+
if (!indexCircuitBreaker.allowRequest()) {
|
|
4252
|
+
const error = circuitOpenError();
|
|
4253
|
+
for (const onError of batch.onErrors) onError(error);
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
void withMutex(
|
|
4257
|
+
() => callIndexOp(
|
|
4258
|
+
"index",
|
|
4259
|
+
{
|
|
4260
|
+
projectRoot: batch.projectRoot,
|
|
4261
|
+
files: [...batch.files].sort(),
|
|
4262
|
+
indexDir: batch.indexDir
|
|
4263
|
+
},
|
|
4264
|
+
{ timeoutMs: batch.timeoutMs }
|
|
4265
|
+
)
|
|
4266
|
+
).then(
|
|
4267
|
+
() => indexCircuitBreaker.recordSuccess(),
|
|
4268
|
+
(err) => {
|
|
4269
|
+
indexCircuitBreaker.recordFailure(err);
|
|
4270
|
+
for (const onError of batch.onErrors) onError(err);
|
|
4271
|
+
}
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4274
|
+
function addReadyReindex(opts) {
|
|
4275
|
+
const key = reindexBatchKey(opts.projectRoot, opts.indexDir);
|
|
4276
|
+
const existing = readyReindexBatches.get(key);
|
|
4277
|
+
if (existing) {
|
|
4278
|
+
existing.files.add(opts.file);
|
|
4279
|
+
existing.timeoutMs = Math.max(existing.timeoutMs, opts.timeoutMs);
|
|
4280
|
+
if (opts.onError) existing.onErrors.add(opts.onError);
|
|
4281
|
+
return;
|
|
4282
|
+
}
|
|
4283
|
+
const batch = {
|
|
4284
|
+
projectRoot: opts.projectRoot,
|
|
4285
|
+
indexDir: opts.indexDir,
|
|
4286
|
+
files: /* @__PURE__ */ new Set([opts.file]),
|
|
4287
|
+
timeoutMs: opts.timeoutMs,
|
|
4288
|
+
onErrors: new Set(opts.onError ? [opts.onError] : [])
|
|
4289
|
+
};
|
|
4290
|
+
batch.flush = setImmediate(() => flushReadyReindexBatch(key));
|
|
4291
|
+
batch.flush.unref?.();
|
|
4292
|
+
readyReindexBatches.set(key, batch);
|
|
3594
4293
|
}
|
|
3595
4294
|
function isIndexableFile(filePath) {
|
|
3596
|
-
return
|
|
4295
|
+
return isIndexablePath(filePath);
|
|
3597
4296
|
}
|
|
3598
4297
|
function isRecoverableConstraintError(err) {
|
|
3599
4298
|
if (err instanceof Error) {
|
|
@@ -3653,28 +4352,18 @@ function enqueueReindex(opts) {
|
|
|
3653
4352
|
if (files.length === 0) return;
|
|
3654
4353
|
const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
3655
4354
|
for (const file of files) {
|
|
3656
|
-
const key = debounceKey(opts.indexDir, file);
|
|
4355
|
+
const key = debounceKey(opts.projectRoot, opts.indexDir, file);
|
|
3657
4356
|
const existing = debounceTimers.get(key);
|
|
3658
4357
|
if (existing) clearTimeout(existing);
|
|
3659
4358
|
const timer = setTimeout(() => {
|
|
3660
4359
|
debounceTimers.delete(key);
|
|
3661
|
-
|
|
3662
|
-
opts.
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
{ projectRoot: opts.projectRoot, files: [file], indexDir: opts.indexDir },
|
|
3669
|
-
{ timeoutMs: opts.timeoutMs ?? DEFAULT_INCREMENTAL_TIMEOUT_MS }
|
|
3670
|
-
)
|
|
3671
|
-
).then(
|
|
3672
|
-
() => indexCircuitBreaker.recordSuccess(),
|
|
3673
|
-
(err) => {
|
|
3674
|
-
indexCircuitBreaker.recordFailure(err);
|
|
3675
|
-
opts.onError?.(err);
|
|
3676
|
-
}
|
|
3677
|
-
);
|
|
4360
|
+
addReadyReindex({
|
|
4361
|
+
projectRoot: opts.projectRoot,
|
|
4362
|
+
file,
|
|
4363
|
+
indexDir: opts.indexDir,
|
|
4364
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_INCREMENTAL_TIMEOUT_MS,
|
|
4365
|
+
onError: opts.onError
|
|
4366
|
+
});
|
|
3678
4367
|
}, ms);
|
|
3679
4368
|
timer.unref?.();
|
|
3680
4369
|
debounceTimers.set(key, timer);
|
|
@@ -3683,6 +4372,10 @@ function enqueueReindex(opts) {
|
|
|
3683
4372
|
function cancelPendingReindexes() {
|
|
3684
4373
|
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
3685
4374
|
debounceTimers.clear();
|
|
4375
|
+
for (const batch of readyReindexBatches.values()) {
|
|
4376
|
+
if (batch.flush) clearImmediate(batch.flush);
|
|
4377
|
+
}
|
|
4378
|
+
readyReindexBatches.clear();
|
|
3686
4379
|
}
|
|
3687
4380
|
async function searchCodebaseIndex(args, opts = {}) {
|
|
3688
4381
|
return callIndexOp("search", args, {
|
|
@@ -3967,6 +4660,7 @@ var codebaseStatsTool = {
|
|
|
3967
4660
|
};
|
|
3968
4661
|
export {
|
|
3969
4662
|
CircuitOpenError,
|
|
4663
|
+
INDEXABLE_EXTENSIONS,
|
|
3970
4664
|
IndexCircuitBreaker,
|
|
3971
4665
|
IndexStore,
|
|
3972
4666
|
IndexTimeoutError,
|
|
@@ -3979,6 +4673,7 @@ export {
|
|
|
3979
4673
|
codebaseIndexTool,
|
|
3980
4674
|
codebaseSearchTool,
|
|
3981
4675
|
codebaseStatsTool,
|
|
4676
|
+
detectLang,
|
|
3982
4677
|
enqueueReindex,
|
|
3983
4678
|
fileGraphService,
|
|
3984
4679
|
getIndexState,
|
|
@@ -3986,6 +4681,7 @@ export {
|
|
|
3986
4681
|
internalKindToLspKind,
|
|
3987
4682
|
isIndexReady,
|
|
3988
4683
|
isIndexableFile,
|
|
4684
|
+
isIndexablePath,
|
|
3989
4685
|
isIndexing,
|
|
3990
4686
|
lspKindToInternalKind,
|
|
3991
4687
|
onIndexStateChange,
|