@wrongstack/tools 0.292.1 → 0.295.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +66 -52
- 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 +1394 -553
- 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 +1036 -341
- 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 +961 -317
- 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 +60 -21
- 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 +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +4 -3
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1657 -705
- 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.d.ts +4 -3
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +94 -27
- package/dist/kanban.js.map +2 -2
- 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 +1394 -553
- 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 +54 -15
- 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 +22233 -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 +2 -2
- package/dist/tree.js.map +1 -1
- 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/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 +7 -3
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { parentPort } from "node:worker_threads";
|
|
3
3
|
|
|
4
4
|
// src/codebase-index/indexer.ts
|
|
5
|
-
import { expectDefined as expectDefined5 } from "@wrongstack/core";
|
|
5
|
+
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
6
6
|
import * as fs7 from "node:fs/promises";
|
|
7
|
-
import * as
|
|
7
|
+
import * as path9 from "node:path";
|
|
8
8
|
import { availableParallelism } from "node:os";
|
|
9
|
-
import {
|
|
9
|
+
import { indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
|
|
10
10
|
|
|
11
11
|
// src/codebase-index/writer.ts
|
|
12
|
-
import { expectDefined } from "@wrongstack/core";
|
|
12
|
+
import { expectDefined } from "@wrongstack/core/utils";
|
|
13
13
|
|
|
14
14
|
// src/codebase-index/circuit-breaker.ts
|
|
15
15
|
var LockError = class extends Error {
|
|
@@ -90,7 +90,7 @@ import { toErrorMessage } from "@wrongstack/core/utils";
|
|
|
90
90
|
import { createRequire } from "node:module";
|
|
91
91
|
import * as fs from "node:fs";
|
|
92
92
|
import * as path from "node:path";
|
|
93
|
-
import { resolveWstackPaths } from "@wrongstack/core";
|
|
93
|
+
import { resolveWstackPaths, sqliteCachePragmas } from "@wrongstack/core/utils";
|
|
94
94
|
|
|
95
95
|
// src/codebase-index/schema.ts
|
|
96
96
|
var SCHEMA_VERSION = 3;
|
|
@@ -146,23 +146,13 @@ function buildBm25Index(docs) {
|
|
|
146
146
|
const tokens = tokenise(d.text);
|
|
147
147
|
return { id: d.id, tokens, raw: d.text, len: tokens.length };
|
|
148
148
|
});
|
|
149
|
-
const df = {};
|
|
150
|
-
for (const doc of documents) {
|
|
151
|
-
const seen = /* @__PURE__ */ new Set();
|
|
152
|
-
for (const t of doc.tokens) {
|
|
153
|
-
if (!seen.has(t)) {
|
|
154
|
-
df[t] = (df[t] ?? 0) + 1;
|
|
155
|
-
seen.add(t);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
149
|
const N = documents.length;
|
|
160
150
|
const totalLen = documents.reduce((sum, d) => sum + d.len, 0);
|
|
161
151
|
const avgLen = N === 0 ? 0 : totalLen / N;
|
|
162
|
-
return new Bm25Index(documents,
|
|
152
|
+
return new Bm25Index(documents, N, avgLen);
|
|
163
153
|
}
|
|
164
154
|
var Bm25Index = class {
|
|
165
|
-
constructor(documents,
|
|
155
|
+
constructor(documents, N, avgLen) {
|
|
166
156
|
this.documents = documents;
|
|
167
157
|
this.N = N;
|
|
168
158
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
@@ -170,9 +160,19 @@ var Bm25Index = class {
|
|
|
170
160
|
documents;
|
|
171
161
|
N;
|
|
172
162
|
safeAvgLen;
|
|
163
|
+
/** Lazily-built id→doc index so getDoc is O(1) instead of an O(D) linear find. */
|
|
164
|
+
_byId;
|
|
173
165
|
score(query, filter) {
|
|
174
166
|
const qTokens = tokenise(query);
|
|
175
167
|
if (qTokens.length === 0) return [];
|
|
168
|
+
const dfByTerm = /* @__PURE__ */ new Map();
|
|
169
|
+
for (const qTerm of qTokens) {
|
|
170
|
+
let dfVal = 0;
|
|
171
|
+
for (const candidate of this.documents) {
|
|
172
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
173
|
+
}
|
|
174
|
+
dfByTerm.set(qTerm, dfVal);
|
|
175
|
+
}
|
|
176
176
|
const results = [];
|
|
177
177
|
for (const doc of this.documents) {
|
|
178
178
|
if (filter && !filter(doc.id)) continue;
|
|
@@ -183,10 +183,7 @@ var Bm25Index = class {
|
|
|
183
183
|
if (t.startsWith(qTerm)) tf++;
|
|
184
184
|
}
|
|
185
185
|
if (tf === 0) continue;
|
|
186
|
-
|
|
187
|
-
for (const candidate of this.documents) {
|
|
188
|
-
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
189
|
-
}
|
|
186
|
+
const dfVal = dfByTerm.get(qTerm) ?? 0;
|
|
190
187
|
if (dfVal === 0) continue;
|
|
191
188
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
192
189
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -198,7 +195,10 @@ var Bm25Index = class {
|
|
|
198
195
|
return results;
|
|
199
196
|
}
|
|
200
197
|
getDoc(id) {
|
|
201
|
-
|
|
198
|
+
if (!this._byId) {
|
|
199
|
+
this._byId = new Map(this.documents.map((d) => [d.id, d]));
|
|
200
|
+
}
|
|
201
|
+
return this._byId.get(id);
|
|
202
202
|
}
|
|
203
203
|
extractSnippet(docId, queryTokens, radius = 40) {
|
|
204
204
|
const doc = this.getDoc(docId);
|
|
@@ -356,6 +356,25 @@ var IndexStore = class _IndexStore {
|
|
|
356
356
|
* and reuse it. Cleared in {@link close} when the connection is torn down.
|
|
357
357
|
*/
|
|
358
358
|
stmtCache = /* @__PURE__ */ new Map();
|
|
359
|
+
/**
|
|
360
|
+
* Cached full-corpus BM25 index for the FTS5-unavailable fallback path.
|
|
361
|
+
* Built lazily on the first `searchRankedFallback` call and invalidated
|
|
362
|
+
* (via `bm25Dirty`) whenever the `symbols` table is mutated. Computing
|
|
363
|
+
* IDF over the full corpus is also more correct than the old per-query
|
|
364
|
+
* candidate-subset IDF.
|
|
365
|
+
*
|
|
366
|
+
* Cache-lifecycle invariants (single source of truth lives at the
|
|
367
|
+
* `invalidateBm25()` helper — see its docblock for the "every mutation
|
|
368
|
+
* MUST call this" contract):
|
|
369
|
+
* - declaration: this field + `bm25Dirty` (here)
|
|
370
|
+
* - invalidation: `invalidateBm25()` flips the flag and nulls the cache
|
|
371
|
+
* - build: `getOrBuildBm25()` rebuilds against current `symbols` rows
|
|
372
|
+
* - teardown: `close()` resets the flag and nulls the cache
|
|
373
|
+
*/
|
|
374
|
+
bm25Cache = null;
|
|
375
|
+
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
376
|
+
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
377
|
+
bm25Dirty = true;
|
|
359
378
|
/** Prepare-once helper: compile `sql` on first use, reuse thereafter. */
|
|
360
379
|
stmt(sql) {
|
|
361
380
|
let s = this.stmtCache.get(sql);
|
|
@@ -403,7 +422,15 @@ var IndexStore = class _IndexStore {
|
|
|
403
422
|
this.db = new Database(path.join(this.indexDir, DB_FILE));
|
|
404
423
|
try {
|
|
405
424
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
406
|
-
this.db.exec("PRAGMA
|
|
425
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
426
|
+
this.db.exec("PRAGMA busy_timeout = 15000");
|
|
427
|
+
this.db.exec("PRAGMA temp_store = MEMORY");
|
|
428
|
+
const cache = sqliteCachePragmas();
|
|
429
|
+
this.db.exec(`PRAGMA cache_size = -${cache.cacheSizeKiB}`);
|
|
430
|
+
this.db.exec(`PRAGMA mmap_size = ${cache.mmapBytes}`);
|
|
431
|
+
this.db.exec("PRAGMA foreign_keys = ON");
|
|
432
|
+
this.db.exec("PRAGMA journal_size_limit = 67108864");
|
|
433
|
+
this.db.exec("PRAGMA wal_autocheckpoint = 1000");
|
|
407
434
|
} catch {
|
|
408
435
|
}
|
|
409
436
|
this.initSchema();
|
|
@@ -415,7 +442,7 @@ var IndexStore = class _IndexStore {
|
|
|
415
442
|
value TEXT NOT NULL
|
|
416
443
|
);
|
|
417
444
|
`);
|
|
418
|
-
const storedRows = this.
|
|
445
|
+
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
419
446
|
const storedVersion = storedRows.length ? Number(storedRows[0]?.value) : null;
|
|
420
447
|
if (storedVersion !== null && storedVersion !== SCHEMA_VERSION) {
|
|
421
448
|
this.db.exec(`
|
|
@@ -424,9 +451,9 @@ var IndexStore = class _IndexStore {
|
|
|
424
451
|
DROP TABLE IF EXISTS refs;
|
|
425
452
|
`);
|
|
426
453
|
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
427
|
-
this.
|
|
454
|
+
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
|
|
428
455
|
} else if (storedVersion === null) {
|
|
429
|
-
this.
|
|
456
|
+
this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
|
|
430
457
|
}
|
|
431
458
|
this.db.exec(`
|
|
432
459
|
CREATE TABLE IF NOT EXISTS files (
|
|
@@ -457,6 +484,7 @@ var IndexStore = class _IndexStore {
|
|
|
457
484
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
|
|
458
485
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
|
|
459
486
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
|
|
487
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)");
|
|
460
488
|
this.db.exec(`
|
|
461
489
|
CREATE TABLE IF NOT EXISTS refs (
|
|
462
490
|
id INTEGER PRIMARY KEY,
|
|
@@ -477,65 +505,167 @@ var IndexStore = class _IndexStore {
|
|
|
477
505
|
);
|
|
478
506
|
this.ftsAvailable = true;
|
|
479
507
|
const symbolCount = Number(
|
|
480
|
-
this.
|
|
508
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
481
509
|
);
|
|
482
510
|
const ftsCount = Number(
|
|
483
|
-
this.
|
|
511
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
484
512
|
);
|
|
485
513
|
if (symbolCount !== ftsCount) {
|
|
486
514
|
this.db.exec("DELETE FROM symbols_fts");
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
515
|
+
const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
516
|
+
this.bulkInsertFts(
|
|
517
|
+
rows.map((row) => ({
|
|
518
|
+
id: row.id,
|
|
519
|
+
text: buildIndexableText(row.name, row.signature, row.doc_comment)
|
|
520
|
+
}))
|
|
521
|
+
);
|
|
522
|
+
this.invalidateBm25();
|
|
492
523
|
}
|
|
493
524
|
} catch {
|
|
494
525
|
this.ftsAvailable = false;
|
|
495
526
|
}
|
|
527
|
+
this.ensureNextSymbolIdSeeded();
|
|
528
|
+
}
|
|
529
|
+
// ─── ID allocation & bulk helpers ────────────────────────────────────────────
|
|
530
|
+
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
531
|
+
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
532
|
+
static MAX_SQL_VARS = 900;
|
|
533
|
+
/**
|
|
534
|
+
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
535
|
+
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
536
|
+
* re-reads and advances the counter atomically.
|
|
537
|
+
*/
|
|
538
|
+
ensureNextSymbolIdSeeded() {
|
|
539
|
+
const existing = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
540
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
541
|
+
);
|
|
542
|
+
if (existing?.value !== void 0) return;
|
|
543
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
544
|
+
const next = (maxRows[0]?.m ?? 0) + 1;
|
|
545
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
546
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
547
|
+
String(next)
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Reserve `count` consecutive symbol ids. MUST run inside BEGIN IMMEDIATE
|
|
552
|
+
* so concurrent indexers cannot hand out overlapping ranges.
|
|
553
|
+
*/
|
|
554
|
+
allocateSymbolIds(count) {
|
|
555
|
+
if (count <= 0) return this.getMaxSymbolId() + 1;
|
|
556
|
+
this.ensureNextSymbolIdSeeded();
|
|
557
|
+
const row = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
558
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
559
|
+
);
|
|
560
|
+
const start = Math.max(1, Number(row?.value ?? 1) || 1);
|
|
561
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
562
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
563
|
+
String(start + count)
|
|
564
|
+
);
|
|
565
|
+
return start;
|
|
566
|
+
}
|
|
567
|
+
/** Multi-row INSERT for symbols — amortizes prepare/bind overhead in large batches. */
|
|
568
|
+
bulkInsertSymbols(rows) {
|
|
569
|
+
if (rows.length === 0) return;
|
|
570
|
+
const cols = 12;
|
|
571
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
572
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
573
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
574
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
575
|
+
const stmt = this.stmt(
|
|
576
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
577
|
+
VALUES ${placeholders}`
|
|
578
|
+
);
|
|
579
|
+
const binds = [];
|
|
580
|
+
for (const r of chunk) {
|
|
581
|
+
binds.push(
|
|
582
|
+
r.id,
|
|
583
|
+
r.lang,
|
|
584
|
+
r.kind,
|
|
585
|
+
r.name,
|
|
586
|
+
r.file,
|
|
587
|
+
r.line,
|
|
588
|
+
r.col,
|
|
589
|
+
r.signature,
|
|
590
|
+
r.docComment,
|
|
591
|
+
r.scope,
|
|
592
|
+
r.text,
|
|
593
|
+
r.file
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
stmt.run(...binds);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
bulkInsertFts(rows) {
|
|
600
|
+
if (!this.ftsAvailable || rows.length === 0) return;
|
|
601
|
+
const cols = 2;
|
|
602
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
603
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
604
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
605
|
+
const placeholders = chunk.map(() => "(?, ?)").join(", ");
|
|
606
|
+
const stmt = this.stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders}`);
|
|
607
|
+
const binds = [];
|
|
608
|
+
for (const r of chunk) binds.push(r.id, r.text);
|
|
609
|
+
stmt.run(...binds);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
bulkInsertRefs(refs) {
|
|
613
|
+
if (refs.length === 0) return;
|
|
614
|
+
const cols = 5;
|
|
615
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
616
|
+
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
617
|
+
const chunk = refs.slice(i, i + chunkSize);
|
|
618
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
619
|
+
const stmt = this.stmt(
|
|
620
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line) VALUES ${placeholders}`
|
|
621
|
+
);
|
|
622
|
+
const binds = [];
|
|
623
|
+
for (const ref of chunk) {
|
|
624
|
+
binds.push(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
625
|
+
}
|
|
626
|
+
stmt.run(...binds);
|
|
627
|
+
}
|
|
496
628
|
}
|
|
497
629
|
// ─── Symbol CRUD ─────────────────────────────────────────────────────────────
|
|
498
630
|
/**
|
|
499
631
|
* Insert symbols, assigning IDs atomically inside `BEGIN IMMEDIATE` /
|
|
500
|
-
* `COMMIT`.
|
|
501
|
-
*
|
|
502
|
-
* processes index concurrently (each would see a different `MAX(id)` and
|
|
503
|
-
* neither can insert with the other's IDs).
|
|
632
|
+
* `COMMIT`. Id ranges come from the `next_symbol_id` metadata counter
|
|
633
|
+
* (O(1)); multi-row INSERT amortizes bind overhead for large files.
|
|
504
634
|
*
|
|
505
635
|
* @returns The symbols array with `id` fields populated so the caller can
|
|
506
636
|
* use them for refs without re-reading from the DB.
|
|
507
637
|
*/
|
|
508
638
|
insertSymbols(symbols) {
|
|
639
|
+
this.invalidateBm25();
|
|
509
640
|
return this.runWithRetry(() => {
|
|
510
641
|
this.db.exec("BEGIN IMMEDIATE");
|
|
511
642
|
try {
|
|
512
|
-
|
|
513
|
-
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
514
|
-
const stmt = this.db.prepare(
|
|
515
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
516
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
517
|
-
);
|
|
518
|
-
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
643
|
+
let nextId = this.allocateSymbolIds(symbols.length);
|
|
519
644
|
const result = [];
|
|
645
|
+
const bulk = [];
|
|
646
|
+
const ftsRows = [];
|
|
520
647
|
for (const s of symbols) {
|
|
521
648
|
const id = nextId++;
|
|
522
|
-
|
|
649
|
+
bulk.push({
|
|
523
650
|
id,
|
|
524
|
-
s.lang,
|
|
525
|
-
s.kind,
|
|
526
|
-
s.name,
|
|
527
|
-
s.file,
|
|
528
|
-
s.line,
|
|
529
|
-
s.col,
|
|
530
|
-
s.signature,
|
|
531
|
-
s.docComment,
|
|
532
|
-
s.scope,
|
|
533
|
-
s.text
|
|
534
|
-
|
|
535
|
-
)
|
|
536
|
-
|
|
651
|
+
lang: s.lang,
|
|
652
|
+
kind: s.kind,
|
|
653
|
+
name: s.name,
|
|
654
|
+
file: s.file,
|
|
655
|
+
line: s.line,
|
|
656
|
+
col: s.col,
|
|
657
|
+
signature: s.signature,
|
|
658
|
+
docComment: s.docComment,
|
|
659
|
+
scope: s.scope,
|
|
660
|
+
text: s.text
|
|
661
|
+
});
|
|
662
|
+
if (this.ftsAvailable) {
|
|
663
|
+
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
664
|
+
}
|
|
537
665
|
result.push({ ...s, id });
|
|
538
666
|
}
|
|
667
|
+
this.bulkInsertSymbols(bulk);
|
|
668
|
+
this.bulkInsertFts(ftsRows);
|
|
539
669
|
this.db.exec("COMMIT");
|
|
540
670
|
return result;
|
|
541
671
|
} catch (err) {
|
|
@@ -545,6 +675,7 @@ var IndexStore = class _IndexStore {
|
|
|
545
675
|
});
|
|
546
676
|
}
|
|
547
677
|
deleteSymbolsForFile(file) {
|
|
678
|
+
this.invalidateBm25();
|
|
548
679
|
this.runWithRetry(() => {
|
|
549
680
|
if (this.ftsAvailable) {
|
|
550
681
|
this.stmt(
|
|
@@ -560,17 +691,20 @@ var IndexStore = class _IndexStore {
|
|
|
560
691
|
* dropped the `files` row, leaving its symbols orphaned but still searchable.
|
|
561
692
|
*/
|
|
562
693
|
deleteFile(file) {
|
|
694
|
+
this.invalidateBm25();
|
|
563
695
|
this.runWithRetry(() => {
|
|
564
696
|
this.db.exec("BEGIN IMMEDIATE");
|
|
565
697
|
try {
|
|
566
698
|
if (this.ftsAvailable) {
|
|
567
|
-
this.
|
|
699
|
+
this.stmt(
|
|
568
700
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
569
701
|
).run(file);
|
|
570
702
|
}
|
|
571
|
-
this.
|
|
572
|
-
|
|
573
|
-
|
|
703
|
+
this.stmt(
|
|
704
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
705
|
+
).run(file);
|
|
706
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
707
|
+
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
574
708
|
this.db.exec("COMMIT");
|
|
575
709
|
} catch (err) {
|
|
576
710
|
this.db.exec("ROLLBACK");
|
|
@@ -607,7 +741,7 @@ var IndexStore = class _IndexStore {
|
|
|
607
741
|
};
|
|
608
742
|
}
|
|
609
743
|
getAllFileMetas() {
|
|
610
|
-
return this.
|
|
744
|
+
return this.stmt("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
611
745
|
file: r.file,
|
|
612
746
|
lang: r.lang,
|
|
613
747
|
mtimeMs: r.mtime_ms,
|
|
@@ -616,7 +750,32 @@ var IndexStore = class _IndexStore {
|
|
|
616
750
|
}));
|
|
617
751
|
}
|
|
618
752
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
619
|
-
search(query, filter) {
|
|
753
|
+
search(query, filter, opts) {
|
|
754
|
+
const built = this.buildSearchWhere(query, filter);
|
|
755
|
+
if (built === null) return [];
|
|
756
|
+
const { where, values } = built;
|
|
757
|
+
const limit = typeof opts?.limit === "number" && Number.isFinite(opts.limit) ? Math.max(0, Math.trunc(opts.limit)) : void 0;
|
|
758
|
+
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
759
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
|
|
760
|
+
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
761
|
+
const rows = this.stmt(sql).all(...binds);
|
|
762
|
+
return rows.map((r) => ({
|
|
763
|
+
id: r.id,
|
|
764
|
+
lang: r.lang,
|
|
765
|
+
kind: r.kind,
|
|
766
|
+
name: r.name,
|
|
767
|
+
file: r.file,
|
|
768
|
+
line: r.line,
|
|
769
|
+
col: r.col,
|
|
770
|
+
signature: r.signature,
|
|
771
|
+
docComment: r.doc_comment,
|
|
772
|
+
score: 0,
|
|
773
|
+
snippet: "",
|
|
774
|
+
lspKind: filter?.lspKind
|
|
775
|
+
}));
|
|
776
|
+
}
|
|
777
|
+
/** Shared WHERE builder for {@link search} / empty-query ranked totals. */
|
|
778
|
+
buildSearchWhere(query, filter) {
|
|
620
779
|
const conditions = [];
|
|
621
780
|
const values = [];
|
|
622
781
|
let effectiveKind = filter?.kind;
|
|
@@ -625,7 +784,7 @@ var IndexStore = class _IndexStore {
|
|
|
625
784
|
if (mapped !== null) {
|
|
626
785
|
effectiveKind = mapped;
|
|
627
786
|
} else {
|
|
628
|
-
return
|
|
787
|
+
return null;
|
|
629
788
|
}
|
|
630
789
|
}
|
|
631
790
|
if (effectiveKind) {
|
|
@@ -647,23 +806,15 @@ var IndexStore = class _IndexStore {
|
|
|
647
806
|
for (const t of tokens) values.push(`%${t}%`);
|
|
648
807
|
}
|
|
649
808
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
line: r.line,
|
|
660
|
-
col: r.col,
|
|
661
|
-
signature: r.signature,
|
|
662
|
-
docComment: r.doc_comment,
|
|
663
|
-
score: 0,
|
|
664
|
-
snippet: "",
|
|
665
|
-
lspKind: filter?.lspKind
|
|
666
|
-
}));
|
|
809
|
+
return { where, values };
|
|
810
|
+
}
|
|
811
|
+
countSearch(query, filter) {
|
|
812
|
+
const built = this.buildSearchWhere(query, filter);
|
|
813
|
+
if (built === null) return 0;
|
|
814
|
+
const row = this.stmt(`SELECT COUNT(*) AS n FROM symbols ${built.where}`).get(
|
|
815
|
+
...built.values
|
|
816
|
+
);
|
|
817
|
+
return Number(row?.n ?? 0);
|
|
667
818
|
}
|
|
668
819
|
/**
|
|
669
820
|
* Ranked search — the one-stop query the codebase-search tool and plug-lsp
|
|
@@ -704,12 +855,12 @@ var IndexStore = class _IndexStore {
|
|
|
704
855
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
705
856
|
}
|
|
706
857
|
const where = conditions.join(" AND ");
|
|
707
|
-
const countRows = this.
|
|
858
|
+
const countRows = this.stmt(
|
|
708
859
|
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
709
860
|
).all(...values);
|
|
710
861
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
711
862
|
if (total === 0) return { results: [], total: 0 };
|
|
712
|
-
const rows = this.
|
|
863
|
+
const rows = this.stmt(
|
|
713
864
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
714
865
|
-bm25(symbols_fts) AS score,
|
|
715
866
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
@@ -742,20 +893,60 @@ var IndexStore = class _IndexStore {
|
|
|
742
893
|
total
|
|
743
894
|
};
|
|
744
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* Invalidate the cached BM25 index.
|
|
898
|
+
*
|
|
899
|
+
* **Contract: every method that mutates `symbols` MUST call this before
|
|
900
|
+
* returning.** (`refs` mutations do not affect the BM25 fallback because
|
|
901
|
+
* the corpus is built from `symbols.text` via `getAllIndexable()` and the
|
|
902
|
+
* BM25 score is filtered by the LIKE-selected candidate set in
|
|
903
|
+
* `searchRankedFallback`.) Today the call sites are `repairDrift`,
|
|
904
|
+
* `insertSymbols`, `deleteSymbolsForFile`, `deleteFile`, `clearAll`, and
|
|
905
|
+
* `commitBatch`. A future mutation that adds a new write path (e.g.
|
|
906
|
+
* `renameFile`, `updateSignature`) MUST also call this — otherwise the
|
|
907
|
+
* FTS5-unavailable fallback will serve stale search results. The
|
|
908
|
+
* `close()` reset at L1820-1821 tears the cache down on store shutdown,
|
|
909
|
+
* which is the only legitimate place that flips the flag outside this
|
|
910
|
+
* helper.
|
|
911
|
+
*
|
|
912
|
+
* Called *before* `runWithRetry` on purpose: if the write fails all
|
|
913
|
+
* retries the flag stays set, forcing a rebuild on the next search rather
|
|
914
|
+
* than trusting a cache that may not reflect the intended mutation.
|
|
915
|
+
* Do not move this inside the retry closure.
|
|
916
|
+
*/
|
|
917
|
+
invalidateBm25() {
|
|
918
|
+
this.bm25Dirty = true;
|
|
919
|
+
this.bm25Cache = null;
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Return the cached full-corpus BM25 index, rebuilding it only when the
|
|
923
|
+
* symbols table has been mutated since the last build. The full-corpus IDF
|
|
924
|
+
* is more correct than the old per-query candidate-subset IDF, and the
|
|
925
|
+
* amortized build cost drops from O(symbols × tokens) per search to once
|
|
926
|
+
* per write batch.
|
|
927
|
+
*
|
|
928
|
+
* Note: the first call after a long idle (or on a freshly opened store)
|
|
929
|
+
* pays the full corpus rebuild synchronously on the search path. For a
|
|
930
|
+
* 5 500+ symbol corpus this is a visible one-time latency spike.
|
|
931
|
+
*/
|
|
932
|
+
getOrBuildBm25() {
|
|
933
|
+
if (this.bm25Cache && !this.bm25Dirty) return this.bm25Cache;
|
|
934
|
+
const docs = this.getAllIndexable();
|
|
935
|
+
this.bm25Cache = buildBm25Index(docs);
|
|
936
|
+
this.bm25Dirty = false;
|
|
937
|
+
return this.bm25Cache;
|
|
938
|
+
}
|
|
745
939
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
746
940
|
searchRankedFallback(query, filter, limit) {
|
|
747
|
-
const candidates = this.search(query, filter);
|
|
748
|
-
if (candidates.length === 0) return { results: [], total: 0 };
|
|
749
941
|
if (!query.trim()) {
|
|
750
|
-
|
|
942
|
+
const total = this.countSearch(query, filter);
|
|
943
|
+
if (total === 0) return { results: [], total: 0 };
|
|
944
|
+
return { results: this.search(query, filter, { limit }), total };
|
|
751
945
|
}
|
|
946
|
+
const candidates = this.search(query, filter);
|
|
947
|
+
if (candidates.length === 0) return { results: [], total: 0 };
|
|
752
948
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
753
|
-
const bm25 =
|
|
754
|
-
candidates.map((c) => ({
|
|
755
|
-
id: c.id,
|
|
756
|
-
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
757
|
-
}))
|
|
758
|
-
);
|
|
949
|
+
const bm25 = this.getOrBuildBm25();
|
|
759
950
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
760
951
|
const q = query.trim().toLowerCase();
|
|
761
952
|
const rank = (id) => {
|
|
@@ -797,7 +988,7 @@ var IndexStore = class _IndexStore {
|
|
|
797
988
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
798
989
|
getStats() {
|
|
799
990
|
const sizeBytes = this.sizeBytes();
|
|
800
|
-
const lastRows = this.
|
|
991
|
+
const lastRows = this.stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
801
992
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
802
993
|
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
803
994
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
@@ -822,19 +1013,20 @@ var IndexStore = class _IndexStore {
|
|
|
822
1013
|
}
|
|
823
1014
|
setLastIndexed(ts2) {
|
|
824
1015
|
this.runWithRetry(() => {
|
|
825
|
-
this.
|
|
1016
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
826
1017
|
});
|
|
827
1018
|
}
|
|
828
1019
|
getMetadata(key) {
|
|
829
|
-
const rows = this.
|
|
1020
|
+
const rows = this.stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
830
1021
|
return rows[0]?.value;
|
|
831
1022
|
}
|
|
832
1023
|
setMetadata(key, value) {
|
|
833
1024
|
this.runWithRetry(() => {
|
|
834
|
-
this.
|
|
1025
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
835
1026
|
});
|
|
836
1027
|
}
|
|
837
1028
|
clearAll() {
|
|
1029
|
+
this.invalidateBm25();
|
|
838
1030
|
this.runWithRetry(() => {
|
|
839
1031
|
this.db.exec("BEGIN IMMEDIATE");
|
|
840
1032
|
try {
|
|
@@ -861,13 +1053,7 @@ var IndexStore = class _IndexStore {
|
|
|
861
1053
|
this.runWithRetry(() => {
|
|
862
1054
|
this.stmt("DELETE FROM refs WHERE from_id = ?").run(fromId);
|
|
863
1055
|
if (refs.length === 0) return;
|
|
864
|
-
|
|
865
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
866
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
867
|
-
);
|
|
868
|
-
for (const ref of refs) {
|
|
869
|
-
stmt.run(fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
870
|
-
}
|
|
1056
|
+
this.bulkInsertRefs(refs.map((ref) => ({ ...ref, fromId })));
|
|
871
1057
|
});
|
|
872
1058
|
}
|
|
873
1059
|
/**
|
|
@@ -884,13 +1070,7 @@ var IndexStore = class _IndexStore {
|
|
|
884
1070
|
insertRefsBatch(refs) {
|
|
885
1071
|
if (refs.length === 0) return;
|
|
886
1072
|
this.runWithRetry(() => {
|
|
887
|
-
|
|
888
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
889
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
890
|
-
);
|
|
891
|
-
for (const ref of refs) {
|
|
892
|
-
stmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
893
|
-
}
|
|
1073
|
+
this.bulkInsertRefs(refs);
|
|
894
1074
|
});
|
|
895
1075
|
}
|
|
896
1076
|
/**
|
|
@@ -915,65 +1095,61 @@ var IndexStore = class _IndexStore {
|
|
|
915
1095
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
916
1096
|
return [];
|
|
917
1097
|
}
|
|
1098
|
+
this.invalidateBm25();
|
|
918
1099
|
return this.runWithRetry(() => {
|
|
919
1100
|
this.db.exec("BEGIN IMMEDIATE");
|
|
920
1101
|
try {
|
|
921
1102
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
922
1103
|
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
923
1104
|
if (this.ftsAvailable) {
|
|
924
|
-
this.
|
|
1105
|
+
this.stmt(
|
|
925
1106
|
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
926
1107
|
).run(...options.deleteForFiles);
|
|
927
1108
|
}
|
|
928
|
-
this.
|
|
1109
|
+
this.stmt(
|
|
929
1110
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
930
1111
|
).run(...options.deleteForFiles);
|
|
931
|
-
this.
|
|
1112
|
+
this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
932
1113
|
}
|
|
933
|
-
const
|
|
934
|
-
let nextId = (
|
|
935
|
-
const symStmt = this.db.prepare(
|
|
936
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
937
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
938
|
-
);
|
|
939
|
-
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
1114
|
+
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
1115
|
+
let nextId = this.allocateSymbolIds(totalSymbols);
|
|
940
1116
|
const allInserted = [];
|
|
941
1117
|
const refsToInsert = [];
|
|
1118
|
+
const bulkSyms = [];
|
|
1119
|
+
const ftsRows = [];
|
|
942
1120
|
for (const entry of entries) {
|
|
943
1121
|
const insertedForEntry = [];
|
|
944
1122
|
for (const s of entry.symbols) {
|
|
945
1123
|
const id = nextId++;
|
|
946
|
-
|
|
1124
|
+
bulkSyms.push({
|
|
947
1125
|
id,
|
|
948
|
-
s.lang,
|
|
949
|
-
s.kind,
|
|
950
|
-
s.name,
|
|
951
|
-
s.file,
|
|
952
|
-
s.line,
|
|
953
|
-
s.col,
|
|
954
|
-
s.signature,
|
|
955
|
-
s.docComment,
|
|
956
|
-
s.scope,
|
|
957
|
-
s.text
|
|
958
|
-
|
|
959
|
-
)
|
|
960
|
-
|
|
1126
|
+
lang: s.lang,
|
|
1127
|
+
kind: s.kind,
|
|
1128
|
+
name: s.name,
|
|
1129
|
+
file: s.file,
|
|
1130
|
+
line: s.line,
|
|
1131
|
+
col: s.col,
|
|
1132
|
+
signature: s.signature,
|
|
1133
|
+
docComment: s.docComment,
|
|
1134
|
+
scope: s.scope,
|
|
1135
|
+
text: s.text
|
|
1136
|
+
});
|
|
1137
|
+
if (this.ftsAvailable) {
|
|
1138
|
+
ftsRows.push({
|
|
1139
|
+
id,
|
|
1140
|
+
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
961
1143
|
const inserted = { ...s, id };
|
|
962
1144
|
allInserted.push(inserted);
|
|
963
1145
|
insertedForEntry.push(inserted);
|
|
964
1146
|
}
|
|
965
1147
|
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
966
1148
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
);
|
|
972
|
-
for (const ref of refsToInsert) {
|
|
973
|
-
refStmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
const upsertStmt = this.db.prepare(
|
|
1149
|
+
this.bulkInsertSymbols(bulkSyms);
|
|
1150
|
+
this.bulkInsertFts(ftsRows);
|
|
1151
|
+
this.bulkInsertRefs(refsToInsert);
|
|
1152
|
+
const upsertStmt = this.stmt(
|
|
977
1153
|
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
978
1154
|
VALUES (?, ?, ?, ?, ?)
|
|
979
1155
|
ON CONFLICT(file) DO UPDATE SET
|
|
@@ -1000,7 +1176,7 @@ var IndexStore = class _IndexStore {
|
|
|
1000
1176
|
*/
|
|
1001
1177
|
deleteRefsForFile(file) {
|
|
1002
1178
|
this.runWithRetry(() => {
|
|
1003
|
-
this.
|
|
1179
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
1004
1180
|
});
|
|
1005
1181
|
}
|
|
1006
1182
|
/**
|
|
@@ -1013,20 +1189,75 @@ var IndexStore = class _IndexStore {
|
|
|
1013
1189
|
*/
|
|
1014
1190
|
resolveRefs() {
|
|
1015
1191
|
return this.runWithRetry(() => {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1192
|
+
try {
|
|
1193
|
+
const result = this.stmt(
|
|
1194
|
+
`UPDATE refs
|
|
1195
|
+
SET to_id = s.id
|
|
1196
|
+
FROM (
|
|
1197
|
+
SELECT name, MIN(id) AS id FROM symbols GROUP BY name
|
|
1198
|
+
) AS s
|
|
1199
|
+
WHERE refs.to_id IS NULL
|
|
1200
|
+
AND refs.to_name IS NOT NULL
|
|
1201
|
+
AND refs.to_name = s.name`
|
|
1202
|
+
).run();
|
|
1203
|
+
return result.changes ?? 0;
|
|
1204
|
+
} catch {
|
|
1205
|
+
const result = this.stmt(
|
|
1206
|
+
`UPDATE refs SET to_id = (
|
|
1207
|
+
SELECT id FROM symbols WHERE name = refs.to_name LIMIT 1
|
|
1208
|
+
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
1209
|
+
AND to_name IN (SELECT name FROM symbols)`
|
|
1210
|
+
).run();
|
|
1211
|
+
return result.changes ?? 0;
|
|
1212
|
+
}
|
|
1023
1213
|
});
|
|
1024
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Clear symbols/refs for a file and mark it as indexed with zero symbols.
|
|
1217
|
+
* Used by the indexer for empty-parse results so three writes share one txn.
|
|
1218
|
+
*/
|
|
1219
|
+
replaceEmptyFile(meta) {
|
|
1220
|
+
this.invalidateBm25();
|
|
1221
|
+
this.runWithRetry(() => {
|
|
1222
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
1223
|
+
try {
|
|
1224
|
+
if (this.ftsAvailable) {
|
|
1225
|
+
this.stmt(
|
|
1226
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
1227
|
+
).run(meta.file);
|
|
1228
|
+
}
|
|
1229
|
+
this.stmt(
|
|
1230
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
1231
|
+
).run(meta.file);
|
|
1232
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
|
|
1233
|
+
this.stmt(
|
|
1234
|
+
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
1235
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1236
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
1237
|
+
lang = excluded.lang,
|
|
1238
|
+
mtime_ms = excluded.mtime_ms,
|
|
1239
|
+
symbol_count = excluded.symbol_count,
|
|
1240
|
+
last_indexed = excluded.last_indexed`
|
|
1241
|
+
).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
|
|
1242
|
+
this.db.exec("COMMIT");
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
this.db.exec("ROLLBACK");
|
|
1245
|
+
throw err;
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
/** Best-effort query planner refresh after a large reindex. */
|
|
1250
|
+
optimize() {
|
|
1251
|
+
try {
|
|
1252
|
+
this.db.exec("PRAGMA optimize");
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1025
1256
|
/**
|
|
1026
1257
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
1027
1258
|
*/
|
|
1028
1259
|
findRefsTo(symbolId) {
|
|
1029
|
-
return this.
|
|
1260
|
+
return this.stmt(
|
|
1030
1261
|
"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 = ?)"
|
|
1031
1262
|
).all(symbolId, symbolId).map((r) => ({
|
|
1032
1263
|
id: r.id,
|
|
@@ -1041,7 +1272,7 @@ var IndexStore = class _IndexStore {
|
|
|
1041
1272
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
1042
1273
|
*/
|
|
1043
1274
|
findRefsFrom(symbolId) {
|
|
1044
|
-
return this.
|
|
1275
|
+
return this.stmt("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
1045
1276
|
id: r.id,
|
|
1046
1277
|
fromId: r.from_id,
|
|
1047
1278
|
toName: r.to_name,
|
|
@@ -1114,45 +1345,57 @@ var IndexStore = class _IndexStore {
|
|
|
1114
1345
|
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
1115
1346
|
*/
|
|
1116
1347
|
getPackageGraph() {
|
|
1117
|
-
const
|
|
1348
|
+
const fileCounts = this.stmt(
|
|
1349
|
+
"SELECT file, COUNT(*) AS n FROM symbols GROUP BY file"
|
|
1350
|
+
).all();
|
|
1118
1351
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
1119
1352
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
fileToPkg.set(s.file, pkg);
|
|
1124
|
-
symbolToPkg.set(s.id, pkg);
|
|
1353
|
+
for (const { file, n } of fileCounts) {
|
|
1354
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
1355
|
+
fileToPkg.set(file, pkg);
|
|
1125
1356
|
const node = pkgNodes.get(pkg);
|
|
1126
1357
|
if (node) {
|
|
1127
|
-
node.symbolCount = (node.symbolCount ?? 0) +
|
|
1358
|
+
node.symbolCount = (node.symbolCount ?? 0) + n;
|
|
1128
1359
|
} else {
|
|
1129
1360
|
pkgNodes.set(pkg, {
|
|
1130
1361
|
id: `pkg:${pkg}`,
|
|
1131
1362
|
label: pkg,
|
|
1132
1363
|
kind: "package",
|
|
1133
1364
|
package: pkg,
|
|
1134
|
-
symbolCount:
|
|
1365
|
+
symbolCount: n,
|
|
1135
1366
|
fileCount: 0
|
|
1136
1367
|
});
|
|
1137
1368
|
}
|
|
1138
1369
|
}
|
|
1139
|
-
const files = this.
|
|
1370
|
+
const files = this.stmt("SELECT DISTINCT file FROM files").all();
|
|
1140
1371
|
for (const { file } of files) {
|
|
1141
1372
|
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
1373
|
+
fileToPkg.set(file, pkg);
|
|
1142
1374
|
const node = pkgNodes.get(pkg);
|
|
1143
1375
|
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
1376
|
+
else {
|
|
1377
|
+
pkgNodes.set(pkg, {
|
|
1378
|
+
id: `pkg:${pkg}`,
|
|
1379
|
+
label: pkg,
|
|
1380
|
+
kind: "package",
|
|
1381
|
+
package: pkg,
|
|
1382
|
+
symbolCount: 0,
|
|
1383
|
+
fileCount: 1
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1144
1386
|
}
|
|
1145
|
-
const refRows = this.
|
|
1146
|
-
`SELECT r.
|
|
1387
|
+
const refRows = this.stmt(
|
|
1388
|
+
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file
|
|
1147
1389
|
FROM refs r
|
|
1148
|
-
|
|
1390
|
+
JOIN symbols sf ON sf.id = r.from_id
|
|
1391
|
+
JOIN symbols st ON st.id = r.to_id
|
|
1392
|
+
WHERE r.to_id IS NOT NULL AND r.call_type != 'import'`
|
|
1149
1393
|
).all();
|
|
1150
1394
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1151
1395
|
for (const r of refRows) {
|
|
1152
|
-
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
1396
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
1397
|
+
const toPkg = fileToPkg.get(r.to_file) ?? _IndexStore.derivePackage(r.to_file) ?? "(root)";
|
|
1398
|
+
if (fromPkg === toPkg) continue;
|
|
1156
1399
|
const key = `${fromPkg}\0${toPkg}`;
|
|
1157
1400
|
let e = edgeMap.get(key);
|
|
1158
1401
|
if (!e) {
|
|
@@ -1162,13 +1405,14 @@ var IndexStore = class _IndexStore {
|
|
|
1162
1405
|
e.weight++;
|
|
1163
1406
|
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
1164
1407
|
}
|
|
1165
|
-
const importRows = this.
|
|
1166
|
-
`SELECT r.
|
|
1408
|
+
const importRows = this.stmt(
|
|
1409
|
+
`SELECT r.to_name, s.file AS from_file
|
|
1167
1410
|
FROM refs r
|
|
1411
|
+
JOIN symbols s ON s.id = r.from_id
|
|
1168
1412
|
WHERE r.call_type = 'import'`
|
|
1169
1413
|
).all();
|
|
1170
1414
|
for (const r of importRows) {
|
|
1171
|
-
const fromPkg =
|
|
1415
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
1172
1416
|
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
1173
1417
|
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
1174
1418
|
const key = `${fromPkg}\0${toPkg}`;
|
|
@@ -1205,12 +1449,12 @@ var IndexStore = class _IndexStore {
|
|
|
1205
1449
|
* derived from cross-file symbol references within the package.
|
|
1206
1450
|
*/
|
|
1207
1451
|
getFileGraph(packageFilter) {
|
|
1208
|
-
const allFiles = this.
|
|
1452
|
+
const allFiles = this.stmt("SELECT DISTINCT file FROM symbols").all();
|
|
1209
1453
|
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
1210
1454
|
const localFiles = new Set(pkgFilePaths);
|
|
1211
1455
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
1212
1456
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
1213
|
-
const pkgSyms = this.
|
|
1457
|
+
const pkgSyms = this.stmt(
|
|
1214
1458
|
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
1215
1459
|
).all(...pkgFilePaths);
|
|
1216
1460
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
@@ -1242,7 +1486,7 @@ var IndexStore = class _IndexStore {
|
|
|
1242
1486
|
ensureFileNode(file);
|
|
1243
1487
|
}
|
|
1244
1488
|
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
1245
|
-
const refRows = this.
|
|
1489
|
+
const refRows = this.stmt(
|
|
1246
1490
|
`SELECT r.from_id, r.to_id, r.call_type
|
|
1247
1491
|
FROM refs r
|
|
1248
1492
|
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
@@ -1257,7 +1501,7 @@ var IndexStore = class _IndexStore {
|
|
|
1257
1501
|
}
|
|
1258
1502
|
if (crossRefIds.size > 0) {
|
|
1259
1503
|
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
1260
|
-
const extras = this.
|
|
1504
|
+
const extras = this.stmt(
|
|
1261
1505
|
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
1262
1506
|
).all(...crossRefIds);
|
|
1263
1507
|
for (const x of extras) {
|
|
@@ -1285,7 +1529,7 @@ var IndexStore = class _IndexStore {
|
|
|
1285
1529
|
e.weight++;
|
|
1286
1530
|
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
1287
1531
|
}
|
|
1288
|
-
const importRows = this.
|
|
1532
|
+
const importRows = this.stmt(
|
|
1289
1533
|
`SELECT r.from_id, r.to_name
|
|
1290
1534
|
FROM refs r
|
|
1291
1535
|
WHERE r.call_type = 'import'
|
|
@@ -1332,7 +1576,7 @@ var IndexStore = class _IndexStore {
|
|
|
1332
1576
|
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
1333
1577
|
*/
|
|
1334
1578
|
getSymbolGraph(fileFilter) {
|
|
1335
|
-
const syms = this.
|
|
1579
|
+
const syms = this.stmt(
|
|
1336
1580
|
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
1337
1581
|
).all(fileFilter);
|
|
1338
1582
|
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
@@ -1353,11 +1597,16 @@ var IndexStore = class _IndexStore {
|
|
|
1353
1597
|
external: s.file !== fileFilter
|
|
1354
1598
|
});
|
|
1355
1599
|
const symIds = new Set(syms.map((s) => s.id));
|
|
1356
|
-
const refRows = this.
|
|
1600
|
+
const refRows = this.stmt(
|
|
1357
1601
|
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
1358
1602
|
FROM refs r
|
|
1359
|
-
|
|
1360
|
-
|
|
1603
|
+
JOIN symbols s ON s.id = r.from_id
|
|
1604
|
+
WHERE s.file = ?
|
|
1605
|
+
UNION
|
|
1606
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
1607
|
+
FROM refs r
|
|
1608
|
+
JOIN symbols s ON s.id = r.to_id
|
|
1609
|
+
WHERE s.file = ?`
|
|
1361
1610
|
).all(fileFilter, fileFilter);
|
|
1362
1611
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1363
1612
|
for (const r of refRows) {
|
|
@@ -1396,7 +1645,7 @@ var IndexStore = class _IndexStore {
|
|
|
1396
1645
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
1397
1646
|
if (missingIds.length > 0) {
|
|
1398
1647
|
const placeholders = missingIds.map(() => "?").join(",");
|
|
1399
|
-
const extras = this.
|
|
1648
|
+
const extras = this.stmt(
|
|
1400
1649
|
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
1401
1650
|
).all(...missingIds);
|
|
1402
1651
|
for (const s of extras) symById.set(s.id, s);
|
|
@@ -1410,6 +1659,8 @@ var IndexStore = class _IndexStore {
|
|
|
1410
1659
|
}
|
|
1411
1660
|
close() {
|
|
1412
1661
|
this.stmtCache.clear();
|
|
1662
|
+
this.bm25Dirty = true;
|
|
1663
|
+
this.bm25Cache = null;
|
|
1413
1664
|
try {
|
|
1414
1665
|
this.db.close();
|
|
1415
1666
|
} catch {
|
|
@@ -1419,6 +1670,110 @@ var IndexStore = class _IndexStore {
|
|
|
1419
1670
|
|
|
1420
1671
|
// src/codebase-index/ts-parser.ts
|
|
1421
1672
|
import * as ts from "@typescript/typescript6";
|
|
1673
|
+
|
|
1674
|
+
// src/codebase-index/languages.ts
|
|
1675
|
+
import * as path2 from "node:path";
|
|
1676
|
+
var EXT_TO_LANG = {
|
|
1677
|
+
// TypeScript / JavaScript (first-class TS compiler API)
|
|
1678
|
+
".ts": "ts",
|
|
1679
|
+
".mts": "ts",
|
|
1680
|
+
".cts": "ts",
|
|
1681
|
+
".tsx": "tsx",
|
|
1682
|
+
".js": "js",
|
|
1683
|
+
".mjs": "js",
|
|
1684
|
+
".cjs": "js",
|
|
1685
|
+
".jsx": "jsx",
|
|
1686
|
+
// First-class native/spawn parsers (+ regex fallback)
|
|
1687
|
+
".go": "go",
|
|
1688
|
+
".py": "py",
|
|
1689
|
+
".pyi": "py",
|
|
1690
|
+
".pyw": "py",
|
|
1691
|
+
".rs": "rs",
|
|
1692
|
+
".json": "json",
|
|
1693
|
+
".jsonc": "json",
|
|
1694
|
+
".yaml": "yaml",
|
|
1695
|
+
".yml": "yaml",
|
|
1696
|
+
// C family
|
|
1697
|
+
".c": "c",
|
|
1698
|
+
".h": "c",
|
|
1699
|
+
".cc": "cpp",
|
|
1700
|
+
".cpp": "cpp",
|
|
1701
|
+
".cxx": "cpp",
|
|
1702
|
+
".hh": "cpp",
|
|
1703
|
+
".hpp": "cpp",
|
|
1704
|
+
".hxx": "cpp",
|
|
1705
|
+
// JVM / .NET
|
|
1706
|
+
".java": "java",
|
|
1707
|
+
".cs": "csharp",
|
|
1708
|
+
".kt": "kotlin",
|
|
1709
|
+
".kts": "kotlin",
|
|
1710
|
+
".scala": "scala",
|
|
1711
|
+
".sc": "scala",
|
|
1712
|
+
// Scripting
|
|
1713
|
+
".php": "php",
|
|
1714
|
+
".rb": "ruby",
|
|
1715
|
+
".swift": "swift",
|
|
1716
|
+
".dart": "dart",
|
|
1717
|
+
".lua": "lua",
|
|
1718
|
+
".r": "r",
|
|
1719
|
+
".R": "r",
|
|
1720
|
+
".pl": "other",
|
|
1721
|
+
".pm": "other",
|
|
1722
|
+
// Systems / functional
|
|
1723
|
+
".zig": "zig",
|
|
1724
|
+
".ex": "elixir",
|
|
1725
|
+
".exs": "elixir",
|
|
1726
|
+
".hs": "haskell",
|
|
1727
|
+
".lhs": "haskell",
|
|
1728
|
+
// Shell / data / docs / web
|
|
1729
|
+
".sh": "shell",
|
|
1730
|
+
".bash": "shell",
|
|
1731
|
+
".zsh": "shell",
|
|
1732
|
+
".ps1": "shell",
|
|
1733
|
+
".sql": "sql",
|
|
1734
|
+
".md": "md",
|
|
1735
|
+
".mdx": "md",
|
|
1736
|
+
".toml": "toml",
|
|
1737
|
+
".html": "html",
|
|
1738
|
+
".htm": "html",
|
|
1739
|
+
".css": "css",
|
|
1740
|
+
".scss": "css",
|
|
1741
|
+
".less": "css",
|
|
1742
|
+
".vue": "vue",
|
|
1743
|
+
".svelte": "svelte",
|
|
1744
|
+
".proto": "proto",
|
|
1745
|
+
".graphql": "graphql",
|
|
1746
|
+
".gql": "graphql"
|
|
1747
|
+
};
|
|
1748
|
+
var INDEXABLE_EXTENSIONS = Object.freeze(
|
|
1749
|
+
[...new Set(Object.keys(EXT_TO_LANG).map((e) => e.toLowerCase()))].sort()
|
|
1750
|
+
);
|
|
1751
|
+
var SPECIAL_FILENAMES = {
|
|
1752
|
+
makefile: "other",
|
|
1753
|
+
gnumakefile: "other",
|
|
1754
|
+
dockerfile: "other",
|
|
1755
|
+
"docker-compose.yml": "yaml",
|
|
1756
|
+
"docker-compose.yaml": "yaml",
|
|
1757
|
+
"cmakelists.txt": "other",
|
|
1758
|
+
gemfile: "ruby",
|
|
1759
|
+
rakefile: "ruby",
|
|
1760
|
+
procfile: "other",
|
|
1761
|
+
justfile: "other"
|
|
1762
|
+
};
|
|
1763
|
+
function detectLang(file) {
|
|
1764
|
+
const base = path2.basename(file);
|
|
1765
|
+
const lowerBase = base.toLowerCase();
|
|
1766
|
+
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
1767
|
+
return "ts";
|
|
1768
|
+
}
|
|
1769
|
+
const special = SPECIAL_FILENAMES[lowerBase];
|
|
1770
|
+
if (special) return special;
|
|
1771
|
+
const ext = path2.extname(base).toLowerCase();
|
|
1772
|
+
if (!ext) return null;
|
|
1773
|
+
return EXT_TO_LANG[ext] ?? null;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// src/codebase-index/ts-parser.ts
|
|
1422
1777
|
var KIND_MAP = {
|
|
1423
1778
|
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
1424
1779
|
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
@@ -1445,32 +1800,6 @@ function kindOf(node) {
|
|
|
1445
1800
|
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
1446
1801
|
return KIND_MAP[node.kind] ?? null;
|
|
1447
1802
|
}
|
|
1448
|
-
function extToLang(ext) {
|
|
1449
|
-
switch (ext) {
|
|
1450
|
-
case ".ts":
|
|
1451
|
-
return "ts";
|
|
1452
|
-
case ".tsx":
|
|
1453
|
-
return "tsx";
|
|
1454
|
-
case ".js":
|
|
1455
|
-
return "js";
|
|
1456
|
-
case ".jsx":
|
|
1457
|
-
return "jsx";
|
|
1458
|
-
case ".go":
|
|
1459
|
-
return "go";
|
|
1460
|
-
case ".py":
|
|
1461
|
-
return "py";
|
|
1462
|
-
case ".rs":
|
|
1463
|
-
return "rs";
|
|
1464
|
-
case ".json":
|
|
1465
|
-
return "json";
|
|
1466
|
-
case ".yaml":
|
|
1467
|
-
return "yaml";
|
|
1468
|
-
case ".yml":
|
|
1469
|
-
return "yaml";
|
|
1470
|
-
default:
|
|
1471
|
-
return null;
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
1803
|
function getSignature(printer, node, sourceFile) {
|
|
1475
1804
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
1476
1805
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
@@ -1593,30 +1922,25 @@ function deduplicateRefs(refs) {
|
|
|
1593
1922
|
return true;
|
|
1594
1923
|
});
|
|
1595
1924
|
}
|
|
1596
|
-
function detectLang(file) {
|
|
1597
|
-
const idx = file.lastIndexOf(".");
|
|
1598
|
-
if (idx < 0) return null;
|
|
1599
|
-
return extToLang(file.slice(idx));
|
|
1600
|
-
}
|
|
1601
1925
|
|
|
1602
1926
|
// src/codebase-index/go-parser.ts
|
|
1603
1927
|
import { spawn } from "node:child_process";
|
|
1604
1928
|
import * as os from "node:os";
|
|
1605
|
-
import * as
|
|
1929
|
+
import * as path4 from "node:path";
|
|
1606
1930
|
import * as fs3 from "node:fs/promises";
|
|
1607
1931
|
|
|
1608
1932
|
// src/_win32-resolve.ts
|
|
1609
1933
|
import * as fs2 from "node:fs";
|
|
1610
|
-
import * as
|
|
1934
|
+
import * as path3 from "node:path";
|
|
1611
1935
|
function resolveWin32Command(cmd) {
|
|
1612
1936
|
if (process.platform !== "win32") return cmd;
|
|
1613
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
1937
|
+
if (cmd.includes("/") || cmd.includes("\\") || path3.extname(cmd.replace(/\//g, "\\"))) {
|
|
1614
1938
|
return cmd;
|
|
1615
1939
|
}
|
|
1616
1940
|
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
1617
|
-
const pathDirs = (process.env["PATH"] ?? "").split(
|
|
1941
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path3.delimiter);
|
|
1618
1942
|
for (const dir of pathDirs) {
|
|
1619
|
-
const base =
|
|
1943
|
+
const base = path3.join(dir, cmd);
|
|
1620
1944
|
for (const ext of pathext) {
|
|
1621
1945
|
const full = `${base}${ext}`;
|
|
1622
1946
|
try {
|
|
@@ -1629,11 +1953,22 @@ function resolveWin32Command(cmd) {
|
|
|
1629
1953
|
return cmd;
|
|
1630
1954
|
}
|
|
1631
1955
|
|
|
1956
|
+
// src/codebase-index/spawn-gate.ts
|
|
1957
|
+
var chain = Promise.resolve();
|
|
1958
|
+
function withSpawnGate(fn) {
|
|
1959
|
+
const run = chain.then(fn, fn);
|
|
1960
|
+
chain = run.then(
|
|
1961
|
+
() => void 0,
|
|
1962
|
+
() => void 0
|
|
1963
|
+
);
|
|
1964
|
+
return run;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1632
1967
|
// src/codebase-index/go-parser.ts
|
|
1633
1968
|
async function parseSymbols2(opts) {
|
|
1634
1969
|
const { file, content, lang } = opts;
|
|
1635
1970
|
try {
|
|
1636
|
-
const parsed = await syncGoParse(file, content, lang);
|
|
1971
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
1637
1972
|
if (parsed.symbols.length > 0) {
|
|
1638
1973
|
return parsed;
|
|
1639
1974
|
}
|
|
@@ -1929,11 +2264,16 @@ func formatType(t ast.Expr) string {
|
|
|
1929
2264
|
}
|
|
1930
2265
|
}
|
|
1931
2266
|
`;
|
|
2267
|
+
var _cachedGoScriptPath = null;
|
|
1932
2268
|
async function syncGoParse(filePath, content, lang) {
|
|
1933
|
-
const tmpDir = await fs3.mkdtemp(path3.join(os.tmpdir(), "ws-go-parse-"));
|
|
1934
2269
|
try {
|
|
1935
|
-
|
|
1936
|
-
|
|
2270
|
+
let scriptPath = _cachedGoScriptPath;
|
|
2271
|
+
if (!scriptPath) {
|
|
2272
|
+
const tmpDir = await fs3.mkdtemp(path4.join(os.tmpdir(), "ws-go-parse-"));
|
|
2273
|
+
scriptPath = path4.join(tmpDir, "parse.go");
|
|
2274
|
+
await fs3.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
2275
|
+
_cachedGoScriptPath = scriptPath;
|
|
2276
|
+
}
|
|
1937
2277
|
const goBinary = resolveWin32Command("go");
|
|
1938
2278
|
const goResult = await new Promise(
|
|
1939
2279
|
(resolve2, reject) => {
|
|
@@ -1990,23 +2330,312 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
1990
2330
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
1991
2331
|
} catch {
|
|
1992
2332
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1993
|
-
} finally {
|
|
1994
|
-
await fs3.rm(tmpDir, { recursive: true, force: true });
|
|
1995
2333
|
}
|
|
1996
2334
|
}
|
|
1997
2335
|
|
|
1998
2336
|
// src/codebase-index/py-parser.ts
|
|
1999
|
-
import { spawn as spawn2
|
|
2337
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2000
2338
|
import * as fs4 from "node:fs/promises";
|
|
2001
2339
|
import * as os2 from "node:os";
|
|
2002
|
-
import * as
|
|
2340
|
+
import * as path5 from "node:path";
|
|
2341
|
+
|
|
2342
|
+
// src/codebase-index/generic-parser.ts
|
|
2343
|
+
var C_LIKE = [
|
|
2344
|
+
{ re: /\b(?:class|struct|enum|interface|union)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2345
|
+
{
|
|
2346
|
+
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,
|
|
2347
|
+
kind: "function"
|
|
2348
|
+
},
|
|
2349
|
+
{ re: /\b(?:namespace)\s+([A-Za-z_]\w*)/g, kind: "namespace" }
|
|
2350
|
+
];
|
|
2351
|
+
var LANG_PATTERNS = {
|
|
2352
|
+
py: [
|
|
2353
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
2354
|
+
{ re: /^class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
2355
|
+
{ re: /^([A-Za-z_]\w*)\s*=/gm, kind: "var" }
|
|
2356
|
+
],
|
|
2357
|
+
go: [
|
|
2358
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/gm, kind: "function" },
|
|
2359
|
+
{ re: /^type\s+([A-Za-z_]\w*)\b/gm, kind: "type" },
|
|
2360
|
+
{ re: /^(?:const|var)\s+([A-Za-z_]\w*)\b/gm, kind: "const" },
|
|
2361
|
+
{ re: /^package\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
2362
|
+
],
|
|
2363
|
+
rs: [
|
|
2364
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2365
|
+
{ re: /\bstruct\s+([A-Za-z_]\w*)/g, kind: "struct" },
|
|
2366
|
+
{ re: /\benum\s+([A-Za-z_]\w*)/g, kind: "enum" },
|
|
2367
|
+
{ re: /\btrait\s+([A-Za-z_]\w*)/g, kind: "trait" },
|
|
2368
|
+
{ re: /\bimpl(?:\s*<[^>]+>)?\s+([A-Za-z_]\w*)/g, kind: "impl" },
|
|
2369
|
+
{ re: /\b(?:const|static)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
2370
|
+
{ re: /\bmod\s+([A-Za-z_]\w*)/g, kind: "mod" }
|
|
2371
|
+
],
|
|
2372
|
+
c: C_LIKE,
|
|
2373
|
+
cpp: C_LIKE,
|
|
2374
|
+
java: [
|
|
2375
|
+
{ re: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2376
|
+
{
|
|
2377
|
+
re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
2378
|
+
kind: "method"
|
|
2379
|
+
}
|
|
2380
|
+
],
|
|
2381
|
+
csharp: [
|
|
2382
|
+
{ re: /\b(?:class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2383
|
+
{ re: /\bnamespace\s+([A-Za-z_.\w]+)/g, kind: "namespace" },
|
|
2384
|
+
{
|
|
2385
|
+
re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
2386
|
+
kind: "method"
|
|
2387
|
+
}
|
|
2388
|
+
],
|
|
2389
|
+
php: [
|
|
2390
|
+
{ re: /\bfunction\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2391
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2392
|
+
{ re: /\binterface\s+([A-Za-z_]\w*)/g, kind: "interface" },
|
|
2393
|
+
{ re: /\bnamespace\s+([A-Za-z_\\]+)/g, kind: "namespace" }
|
|
2394
|
+
],
|
|
2395
|
+
ruby: [
|
|
2396
|
+
{ re: /^\s*def\s+(?:self\.)?([A-Za-z_]\w*[!?]?)/gm, kind: "function" },
|
|
2397
|
+
{ re: /^\s*class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
2398
|
+
{ re: /^\s*module\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
2399
|
+
],
|
|
2400
|
+
swift: [
|
|
2401
|
+
{ re: /\b(?:func)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2402
|
+
{ re: /\b(?:class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
2403
|
+
],
|
|
2404
|
+
kotlin: [
|
|
2405
|
+
{ re: /\b(?:fun)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2406
|
+
{ re: /\b(?:class|interface|object|enum\s+class|data\s+class)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
2407
|
+
],
|
|
2408
|
+
scala: [
|
|
2409
|
+
{ re: /\b(?:def)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2410
|
+
{ re: /\b(?:class|object|trait|enum)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
2411
|
+
],
|
|
2412
|
+
shell: [
|
|
2413
|
+
{ re: /^(?:function\s+)?([A-Za-z_]\w*)\s*\(\)\s*\{/gm, kind: "function" },
|
|
2414
|
+
{ re: /^([A-Za-z_][\w]*)\s*\(\)\s*\{/gm, kind: "function" }
|
|
2415
|
+
],
|
|
2416
|
+
sql: [
|
|
2417
|
+
{ 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" }
|
|
2418
|
+
],
|
|
2419
|
+
md: [
|
|
2420
|
+
{ re: /^(#{1,6})\s+(.+)$/gm, kind: "namespace" }
|
|
2421
|
+
],
|
|
2422
|
+
toml: [
|
|
2423
|
+
{ re: /^\[([^\]]+)\]/gm, kind: "namespace" }
|
|
2424
|
+
],
|
|
2425
|
+
html: [
|
|
2426
|
+
{ re: /\bid\s*=\s*["']([^"']+)["']/gi, kind: "property" },
|
|
2427
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
2428
|
+
],
|
|
2429
|
+
css: [
|
|
2430
|
+
{ re: /^\s*([.#]?[A-Za-z_][\w-]*)\s*\{/gm, kind: "type" },
|
|
2431
|
+
{ re: /@(?:keyframes|media|supports)\s+([^{\s]+)/g, kind: "namespace" }
|
|
2432
|
+
],
|
|
2433
|
+
vue: [
|
|
2434
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2435
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
2436
|
+
],
|
|
2437
|
+
svelte: [
|
|
2438
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
2439
|
+
],
|
|
2440
|
+
dart: [
|
|
2441
|
+
{ re: /\b(?:class|enum|mixin|extension)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
2442
|
+
{ re: /\b([A-Za-z_]\w*)\s*\([^;]*\)\s*(?:async\s*)?\{/g, kind: "function" }
|
|
2443
|
+
],
|
|
2444
|
+
lua: [
|
|
2445
|
+
{ re: /\bfunction\s+([A-Za-z_.:]\w*)/g, kind: "function" },
|
|
2446
|
+
{ re: /\blocal\s+function\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
2447
|
+
],
|
|
2448
|
+
r: [
|
|
2449
|
+
{ re: /([A-Za-z.]\w*)\s*<-\s*function\s*\(/g, kind: "function" },
|
|
2450
|
+
{ re: /([A-Za-z.]\w*)\s*=\s*function\s*\(/g, kind: "function" }
|
|
2451
|
+
],
|
|
2452
|
+
proto: [
|
|
2453
|
+
{ re: /\b(?:message|service|enum)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
2454
|
+
{ re: /\brpc\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
2455
|
+
],
|
|
2456
|
+
graphql: [
|
|
2457
|
+
{ re: /\b(?:type|interface|enum|input|union|scalar)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
2458
|
+
{ re: /\b(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
2459
|
+
],
|
|
2460
|
+
zig: [
|
|
2461
|
+
{ re: /\b(?:fn|pub\s+fn)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2462
|
+
{ re: /\b(?:const|var)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
2463
|
+
{ re: /\b(?:struct|enum|union)\s*\{/g, kind: "type" }
|
|
2464
|
+
],
|
|
2465
|
+
elixir: [
|
|
2466
|
+
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
2467
|
+
{ re: /\bdefmodule\s+([A-Za-z_.]\w*)/g, kind: "namespace" }
|
|
2468
|
+
],
|
|
2469
|
+
haskell: [
|
|
2470
|
+
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
2471
|
+
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
2472
|
+
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
2473
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
2474
|
+
],
|
|
2475
|
+
other: [
|
|
2476
|
+
{ re: /^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
2477
|
+
{ re: /^(?:export\s+)?class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
2478
|
+
{ re: /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)/gm, kind: "const" },
|
|
2479
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
2480
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/gm, kind: "function" },
|
|
2481
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
2482
|
+
{ re: /^(?:target|PHONY)\s*:/gm, kind: "namespace" }
|
|
2483
|
+
]
|
|
2484
|
+
};
|
|
2485
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
2486
|
+
"if",
|
|
2487
|
+
"else",
|
|
2488
|
+
"for",
|
|
2489
|
+
"while",
|
|
2490
|
+
"switch",
|
|
2491
|
+
"case",
|
|
2492
|
+
"return",
|
|
2493
|
+
"break",
|
|
2494
|
+
"continue",
|
|
2495
|
+
"new",
|
|
2496
|
+
"delete",
|
|
2497
|
+
"typeof",
|
|
2498
|
+
"instanceof",
|
|
2499
|
+
"void",
|
|
2500
|
+
"null",
|
|
2501
|
+
"true",
|
|
2502
|
+
"false",
|
|
2503
|
+
"this",
|
|
2504
|
+
"super",
|
|
2505
|
+
"import",
|
|
2506
|
+
"export",
|
|
2507
|
+
"from",
|
|
2508
|
+
"as",
|
|
2509
|
+
"default",
|
|
2510
|
+
"public",
|
|
2511
|
+
"private",
|
|
2512
|
+
"protected",
|
|
2513
|
+
"static",
|
|
2514
|
+
"final",
|
|
2515
|
+
"class",
|
|
2516
|
+
"struct",
|
|
2517
|
+
"enum",
|
|
2518
|
+
"interface",
|
|
2519
|
+
"function",
|
|
2520
|
+
"def",
|
|
2521
|
+
"fn",
|
|
2522
|
+
"func",
|
|
2523
|
+
"var",
|
|
2524
|
+
"let",
|
|
2525
|
+
"const",
|
|
2526
|
+
"package",
|
|
2527
|
+
"namespace",
|
|
2528
|
+
"module",
|
|
2529
|
+
"using",
|
|
2530
|
+
"include",
|
|
2531
|
+
"require",
|
|
2532
|
+
"select",
|
|
2533
|
+
"from",
|
|
2534
|
+
"where",
|
|
2535
|
+
"and",
|
|
2536
|
+
"or",
|
|
2537
|
+
"not",
|
|
2538
|
+
"in",
|
|
2539
|
+
"is",
|
|
2540
|
+
"try",
|
|
2541
|
+
"catch",
|
|
2542
|
+
"finally",
|
|
2543
|
+
"throw",
|
|
2544
|
+
"async",
|
|
2545
|
+
"await",
|
|
2546
|
+
"yield"
|
|
2547
|
+
]);
|
|
2548
|
+
function patternsFor(lang) {
|
|
2549
|
+
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
2550
|
+
}
|
|
2551
|
+
function looksBinary(content) {
|
|
2552
|
+
if (content.includes("\0")) return true;
|
|
2553
|
+
const sample = content.slice(0, 2048);
|
|
2554
|
+
if (sample.length === 0) return false;
|
|
2555
|
+
let bad = 0;
|
|
2556
|
+
for (let i = 0; i < sample.length; i++) {
|
|
2557
|
+
const c = sample.charCodeAt(i);
|
|
2558
|
+
if (c === 9 || c === 10 || c === 13) continue;
|
|
2559
|
+
if (c < 32 || c === 127) bad++;
|
|
2560
|
+
}
|
|
2561
|
+
return bad / sample.length > 0.1;
|
|
2562
|
+
}
|
|
2563
|
+
function lineColAt(content, index) {
|
|
2564
|
+
let line = 1;
|
|
2565
|
+
let lastNl = -1;
|
|
2566
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
2567
|
+
if (content.charCodeAt(i) === 10) {
|
|
2568
|
+
line++;
|
|
2569
|
+
lastNl = i;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
return { line, col: index - lastNl };
|
|
2573
|
+
}
|
|
2574
|
+
var GENERIC_MAX_SYMBOLS_DEFAULT = 500;
|
|
2575
|
+
var GENERIC_MAX_FILE_CHARS = 512 * 1024;
|
|
2576
|
+
function parseGeneric(opts) {
|
|
2577
|
+
const { file, lang } = opts;
|
|
2578
|
+
const maxSymbols = opts.maxSymbols ?? GENERIC_MAX_SYMBOLS_DEFAULT;
|
|
2579
|
+
const mtimeMs = Date.now();
|
|
2580
|
+
if (!opts.content || looksBinary(opts.content)) {
|
|
2581
|
+
return { file, lang, symbols: [], mtimeMs };
|
|
2582
|
+
}
|
|
2583
|
+
const content = opts.content.length > GENERIC_MAX_FILE_CHARS ? opts.content.slice(0, GENERIC_MAX_FILE_CHARS) : opts.content;
|
|
2584
|
+
const patterns = patternsFor(lang);
|
|
2585
|
+
const symbols = [];
|
|
2586
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2587
|
+
for (const pattern of patterns) {
|
|
2588
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
2589
|
+
re.lastIndex = 0;
|
|
2590
|
+
let match;
|
|
2591
|
+
while ((match = re.exec(content)) !== null) {
|
|
2592
|
+
if (symbols.length >= maxSymbols) break;
|
|
2593
|
+
let name = (match[1] ?? match[2] ?? "").trim();
|
|
2594
|
+
if (lang === "md" && match[2]) name = match[2].trim();
|
|
2595
|
+
if (!name || name.length > 200) continue;
|
|
2596
|
+
name = name.replace(/^#+\s*/, "").replace(/["'`]/g, "");
|
|
2597
|
+
if (!name || KEYWORDS.has(name.toLowerCase())) continue;
|
|
2598
|
+
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
const { line, col } = lineColAt(content, match.index);
|
|
2602
|
+
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
2603
|
+
if (seen.has(key)) continue;
|
|
2604
|
+
seen.add(key);
|
|
2605
|
+
const nl = content.indexOf("\n", match.index);
|
|
2606
|
+
const lineText = content.slice(match.index, nl === -1 ? content.length : nl);
|
|
2607
|
+
const signature = (lineText || name).trim().slice(0, 500);
|
|
2608
|
+
symbols.push({
|
|
2609
|
+
id: 0,
|
|
2610
|
+
lang,
|
|
2611
|
+
kind: lang === "md" ? "namespace" : pattern.kind,
|
|
2612
|
+
name: name.slice(0, 200),
|
|
2613
|
+
file,
|
|
2614
|
+
line,
|
|
2615
|
+
col,
|
|
2616
|
+
signature,
|
|
2617
|
+
docComment: "",
|
|
2618
|
+
scope: "",
|
|
2619
|
+
text: `${name} ${signature}`.trim().slice(0, 1e3)
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
if (symbols.length >= maxSymbols) break;
|
|
2623
|
+
}
|
|
2624
|
+
return { file, lang, symbols, mtimeMs };
|
|
2625
|
+
}
|
|
2003
2626
|
async function parseSymbols3(opts) {
|
|
2627
|
+
return parseGeneric(opts);
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
// src/codebase-index/py-parser.ts
|
|
2631
|
+
async function parseSymbols4(opts) {
|
|
2004
2632
|
const { file, content, lang } = opts;
|
|
2005
2633
|
try {
|
|
2006
|
-
|
|
2634
|
+
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
2635
|
+
if (native !== null) return native;
|
|
2007
2636
|
} catch {
|
|
2008
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2009
2637
|
}
|
|
2638
|
+
return parseGeneric({ file, content, lang: lang === "py" ? "py" : lang });
|
|
2010
2639
|
}
|
|
2011
2640
|
var PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
2012
2641
|
|
|
@@ -2211,20 +2840,37 @@ visitor.visit(tree)
|
|
|
2211
2840
|
|
|
2212
2841
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
2213
2842
|
`;
|
|
2214
|
-
function resolvePython() {
|
|
2843
|
+
async function resolvePython() {
|
|
2215
2844
|
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
2216
2845
|
for (const name of candidates) {
|
|
2217
2846
|
const resolved = resolveWin32Command(name);
|
|
2218
|
-
|
|
2219
|
-
stdio: "pipe",
|
|
2220
|
-
timeout: 5e3
|
|
2221
|
-
});
|
|
2222
|
-
if (result.error) continue;
|
|
2223
|
-
if (result.status !== 0) continue;
|
|
2847
|
+
if (!await commandIsAvailable(resolved)) continue;
|
|
2224
2848
|
return resolved;
|
|
2225
2849
|
}
|
|
2226
2850
|
return null;
|
|
2227
2851
|
}
|
|
2852
|
+
function commandIsAvailable(command) {
|
|
2853
|
+
return new Promise((resolve2) => {
|
|
2854
|
+
let settled = false;
|
|
2855
|
+
const proc = spawn2(command, ["--version"], {
|
|
2856
|
+
stdio: "ignore",
|
|
2857
|
+
windowsHide: true
|
|
2858
|
+
});
|
|
2859
|
+
const finish = (available) => {
|
|
2860
|
+
if (settled) return;
|
|
2861
|
+
settled = true;
|
|
2862
|
+
clearTimeout(timer);
|
|
2863
|
+
resolve2(available);
|
|
2864
|
+
};
|
|
2865
|
+
const timer = setTimeout(() => {
|
|
2866
|
+
proc.kill("SIGKILL");
|
|
2867
|
+
finish(false);
|
|
2868
|
+
}, 5e3);
|
|
2869
|
+
timer.unref?.();
|
|
2870
|
+
proc.once("error", () => finish(false));
|
|
2871
|
+
proc.once("close", (code) => finish(code === 0));
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2228
2874
|
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
2229
2875
|
return new Promise((resolve2, reject) => {
|
|
2230
2876
|
let settled = false;
|
|
@@ -2260,21 +2906,20 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
2260
2906
|
});
|
|
2261
2907
|
}
|
|
2262
2908
|
var _cachedScriptPath = null;
|
|
2263
|
-
var
|
|
2909
|
+
var cachedPyBinary;
|
|
2264
2910
|
async function syncPyParse(filePath, content, lang) {
|
|
2265
2911
|
try {
|
|
2266
2912
|
if (!_cachedScriptPath) {
|
|
2267
|
-
const tmpDir =
|
|
2913
|
+
const tmpDir = path5.join(os2.tmpdir(), "ws-py-parse");
|
|
2268
2914
|
await fs4.mkdir(tmpDir, { recursive: true });
|
|
2269
|
-
_cachedScriptPath =
|
|
2915
|
+
_cachedScriptPath = path5.join(tmpDir, "parse.py");
|
|
2270
2916
|
await fs4.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
2271
2917
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
}
|
|
2918
|
+
cachedPyBinary ??= resolvePython();
|
|
2919
|
+
const pyBinary = await cachedPyBinary;
|
|
2920
|
+
if (!pyBinary) return null;
|
|
2276
2921
|
const { code, stdout } = await spawnPyParser(
|
|
2277
|
-
|
|
2922
|
+
pyBinary,
|
|
2278
2923
|
_cachedScriptPath,
|
|
2279
2924
|
filePath,
|
|
2280
2925
|
content
|
|
@@ -2298,30 +2943,39 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
2298
2943
|
}));
|
|
2299
2944
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
2300
2945
|
} catch {
|
|
2301
|
-
return
|
|
2946
|
+
return null;
|
|
2302
2947
|
}
|
|
2303
2948
|
}
|
|
2304
2949
|
|
|
2305
2950
|
// src/codebase-index/rs-parser.ts
|
|
2306
|
-
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
2307
|
-
import {
|
|
2951
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
2952
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
2308
2953
|
import * as fs5 from "node:fs/promises";
|
|
2309
|
-
import * as
|
|
2310
|
-
async function
|
|
2954
|
+
import * as path6 from "node:path";
|
|
2955
|
+
async function parseSymbols5(opts) {
|
|
2311
2956
|
const { file, content, lang } = opts;
|
|
2312
|
-
const nativeAvailable = checkNativeParser();
|
|
2957
|
+
const nativeAvailable = await checkNativeParser();
|
|
2313
2958
|
if (nativeAvailable) {
|
|
2314
|
-
const result = await tryNativeParse(file, content);
|
|
2959
|
+
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
2315
2960
|
if (result) return result;
|
|
2316
2961
|
}
|
|
2317
2962
|
return regexParse({ file, content, lang });
|
|
2318
2963
|
}
|
|
2964
|
+
var nativeParserAvailability;
|
|
2965
|
+
function probe(command, args) {
|
|
2966
|
+
return new Promise((resolve2, reject) => {
|
|
2967
|
+
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
2968
|
+
if (error) reject(error);
|
|
2969
|
+
else resolve2();
|
|
2970
|
+
});
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2319
2973
|
function checkNativeParser() {
|
|
2320
|
-
|
|
2321
|
-
execFileSync("rustc", ["--version"], { stdio: "pipe", windowsHide: true });
|
|
2322
|
-
const toolsDir = path5.join(process.cwd(), "tools");
|
|
2974
|
+
nativeParserAvailability ??= (async () => {
|
|
2323
2975
|
try {
|
|
2324
|
-
|
|
2976
|
+
await probe("rustc", ["--version"]);
|
|
2977
|
+
const toolsDir = path6.join(process.cwd(), "tools");
|
|
2978
|
+
await probe(
|
|
2325
2979
|
"cargo",
|
|
2326
2980
|
[
|
|
2327
2981
|
"metadata",
|
|
@@ -2329,23 +2983,21 @@ function checkNativeParser() {
|
|
|
2329
2983
|
"--format-version",
|
|
2330
2984
|
"1",
|
|
2331
2985
|
"--manifest-path",
|
|
2332
|
-
|
|
2333
|
-
]
|
|
2334
|
-
{ stdio: "pipe", windowsHide: true }
|
|
2986
|
+
path6.join(toolsDir, "Cargo.toml")
|
|
2987
|
+
]
|
|
2335
2988
|
);
|
|
2336
2989
|
return true;
|
|
2337
2990
|
} catch {
|
|
2338
2991
|
return false;
|
|
2339
2992
|
}
|
|
2340
|
-
}
|
|
2341
|
-
|
|
2342
|
-
}
|
|
2993
|
+
})();
|
|
2994
|
+
return nativeParserAvailability;
|
|
2343
2995
|
}
|
|
2344
2996
|
async function tryNativeParse(file, content) {
|
|
2345
2997
|
try {
|
|
2346
|
-
const toolsDir =
|
|
2347
|
-
const crateDir =
|
|
2348
|
-
const tmpFile =
|
|
2998
|
+
const toolsDir = path6.join(process.cwd(), "tools");
|
|
2999
|
+
const crateDir = path6.join(toolsDir, "syn-parser");
|
|
3000
|
+
const tmpFile = path6.join(crateDir, "src", "input.rs");
|
|
2349
3001
|
await fs5.writeFile(tmpFile, content, "utf8");
|
|
2350
3002
|
const cargoBinary = resolveWin32Command("cargo");
|
|
2351
3003
|
const result = await new Promise(
|
|
@@ -2353,7 +3005,7 @@ async function tryNativeParse(file, content) {
|
|
|
2353
3005
|
let settled = false;
|
|
2354
3006
|
const proc = spawn3(
|
|
2355
3007
|
cargoBinary,
|
|
2356
|
-
["run", "--manifest-path",
|
|
3008
|
+
["run", "--manifest-path", path6.join(toolsDir, "Cargo.toml")],
|
|
2357
3009
|
{
|
|
2358
3010
|
cwd: process.cwd(),
|
|
2359
3011
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2467,9 +3119,9 @@ function regexParse(opts) {
|
|
|
2467
3119
|
}
|
|
2468
3120
|
|
|
2469
3121
|
// src/codebase-index/json-parser.ts
|
|
2470
|
-
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
2471
|
-
import * as
|
|
2472
|
-
function
|
|
3122
|
+
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
3123
|
+
import * as path7 from "node:path";
|
|
3124
|
+
function parseSymbols6(opts) {
|
|
2473
3125
|
const { file, content, lang } = opts;
|
|
2474
3126
|
try {
|
|
2475
3127
|
return regexParse2({ file, content, lang });
|
|
@@ -2480,9 +3132,9 @@ function parseSymbols5(opts) {
|
|
|
2480
3132
|
function regexParse2(opts) {
|
|
2481
3133
|
const { file, content, lang } = opts;
|
|
2482
3134
|
const symbols = [];
|
|
2483
|
-
const
|
|
2484
|
-
const isPackageJson =
|
|
2485
|
-
const isTsconfig =
|
|
3135
|
+
const basename4 = path7.basename(file).toLowerCase();
|
|
3136
|
+
const isPackageJson = basename4 === "package.json";
|
|
3137
|
+
const isTsconfig = basename4 === "tsconfig.json" || basename4 === "tsconfig.build.json";
|
|
2486
3138
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
2487
3139
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
2488
3140
|
const lines = content.split("\n");
|
|
@@ -2506,11 +3158,11 @@ function regexParse2(opts) {
|
|
|
2506
3158
|
const line = lineFromOffset(offset);
|
|
2507
3159
|
symbols.push(
|
|
2508
3160
|
makeSymbol({
|
|
2509
|
-
name:
|
|
3161
|
+
name: path7.basename(file),
|
|
2510
3162
|
kind: "object",
|
|
2511
3163
|
line,
|
|
2512
3164
|
col: 0,
|
|
2513
|
-
signature: `"${
|
|
3165
|
+
signature: `"${path7.basename(file)}" = { ... }`,
|
|
2514
3166
|
file,
|
|
2515
3167
|
lang
|
|
2516
3168
|
})
|
|
@@ -2672,8 +3324,8 @@ function makeSymbol(opts) {
|
|
|
2672
3324
|
}
|
|
2673
3325
|
|
|
2674
3326
|
// src/codebase-index/yaml-parser.ts
|
|
2675
|
-
import { expectDefined as expectDefined4, truncate } from "@wrongstack/core";
|
|
2676
|
-
function
|
|
3327
|
+
import { expectDefined as expectDefined4, truncate } from "@wrongstack/core/utils";
|
|
3328
|
+
function parseSymbols7(opts) {
|
|
2677
3329
|
const { file, content, lang } = opts;
|
|
2678
3330
|
try {
|
|
2679
3331
|
return regexParse3({ file, content, lang });
|
|
@@ -2822,8 +3474,8 @@ function makeSymbol2(opts) {
|
|
|
2822
3474
|
|
|
2823
3475
|
// src/codebase-index/gitignore.ts
|
|
2824
3476
|
import * as fs6 from "node:fs/promises";
|
|
2825
|
-
import * as
|
|
2826
|
-
import { compileGlob } from "@wrongstack/core";
|
|
3477
|
+
import * as path8 from "node:path";
|
|
3478
|
+
import { compileGlob } from "@wrongstack/core/utils";
|
|
2827
3479
|
function globBody(glob) {
|
|
2828
3480
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
2829
3481
|
}
|
|
@@ -2868,7 +3520,7 @@ function compileGitignore(lines) {
|
|
|
2868
3520
|
async function loadGitignoreMatcher(projectRoot) {
|
|
2869
3521
|
let lines = [];
|
|
2870
3522
|
try {
|
|
2871
|
-
const raw = await fs6.readFile(
|
|
3523
|
+
const raw = await fs6.readFile(path8.join(projectRoot, ".gitignore"), "utf8");
|
|
2872
3524
|
lines = raw.split("\n");
|
|
2873
3525
|
} catch {
|
|
2874
3526
|
}
|
|
@@ -2877,7 +3529,9 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
2877
3529
|
|
|
2878
3530
|
// src/codebase-index/indexer.ts
|
|
2879
3531
|
var YIELD_EVERY_N = 50;
|
|
2880
|
-
|
|
3532
|
+
function resolveParallelBatch() {
|
|
3533
|
+
return indexParallelBatchSize(availableParallelism());
|
|
3534
|
+
}
|
|
2881
3535
|
function yieldEventLoop() {
|
|
2882
3536
|
return new Promise((resolve2) => setImmediate(resolve2));
|
|
2883
3537
|
}
|
|
@@ -2903,8 +3557,8 @@ var DEFAULT_IGNORE = [
|
|
|
2903
3557
|
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
2904
3558
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
2905
3559
|
function isWithinProject(projectRoot, file) {
|
|
2906
|
-
const rel =
|
|
2907
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
3560
|
+
const rel = path9.relative(projectRoot, file);
|
|
3561
|
+
return rel !== "" && !rel.startsWith(`..${path9.sep}`) && rel !== ".." && !path9.isAbsolute(rel);
|
|
2908
3562
|
}
|
|
2909
3563
|
function isMissingPathError(err) {
|
|
2910
3564
|
const code = err?.code;
|
|
@@ -2915,18 +3569,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2915
3569
|
const errors = [];
|
|
2916
3570
|
let complete = true;
|
|
2917
3571
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
2918
|
-
const
|
|
2919
|
-
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
2920
|
-
{ ext: ".tsx", pat: compileGlob2("**/*.tsx") },
|
|
2921
|
-
{ ext: ".js", pat: compileGlob2("**/*.js") },
|
|
2922
|
-
{ ext: ".jsx", pat: compileGlob2("**/*.jsx") },
|
|
2923
|
-
{ ext: ".go", pat: compileGlob2("**/*.go") },
|
|
2924
|
-
{ ext: ".py", pat: compileGlob2("**/*.py") },
|
|
2925
|
-
{ ext: ".rs", pat: compileGlob2("**/*.rs") },
|
|
2926
|
-
{ ext: ".json", pat: compileGlob2("**/*.json") },
|
|
2927
|
-
{ ext: ".yaml", pat: compileGlob2("**/*.yaml") },
|
|
2928
|
-
{ ext: ".yml", pat: compileGlob2("**/*.yml") }
|
|
2929
|
-
];
|
|
3572
|
+
const indexableExts = new Set(INDEXABLE_EXTENSIONS);
|
|
2930
3573
|
let dirCount = 0;
|
|
2931
3574
|
const walk = async (dir) => {
|
|
2932
3575
|
throwIfAborted(signal);
|
|
@@ -2945,19 +3588,16 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2945
3588
|
dirCount++;
|
|
2946
3589
|
for (const e of entries) {
|
|
2947
3590
|
if (ignoreSet.has(e.name)) continue;
|
|
2948
|
-
const full =
|
|
2949
|
-
const rel =
|
|
3591
|
+
const full = path9.join(dir, e.name);
|
|
3592
|
+
const rel = path9.relative(projectRoot, full).replace(/\\/g, "/");
|
|
2950
3593
|
if (e.isDirectory()) {
|
|
2951
3594
|
if (isGitIgnored(rel, true)) continue;
|
|
2952
3595
|
await walk(full);
|
|
2953
3596
|
} else if (e.isFile()) {
|
|
2954
3597
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
2955
|
-
const ext =
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
results.push(full);
|
|
2959
|
-
break;
|
|
2960
|
-
}
|
|
3598
|
+
const ext = path9.extname(e.name).toLowerCase();
|
|
3599
|
+
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
3600
|
+
results.push(full);
|
|
2961
3601
|
}
|
|
2962
3602
|
}
|
|
2963
3603
|
}
|
|
@@ -2966,24 +3606,25 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2966
3606
|
return { files: results, complete, errors };
|
|
2967
3607
|
}
|
|
2968
3608
|
async function parseFile(file, content, lang) {
|
|
2969
|
-
|
|
3609
|
+
const symbolLang = lang;
|
|
3610
|
+
switch (symbolLang) {
|
|
2970
3611
|
case "ts":
|
|
2971
3612
|
case "tsx":
|
|
2972
3613
|
case "js":
|
|
2973
3614
|
case "jsx":
|
|
2974
|
-
return parseSymbols({ file, content, lang });
|
|
3615
|
+
return parseSymbols({ file, content, lang: symbolLang });
|
|
2975
3616
|
case "go":
|
|
2976
3617
|
return parseSymbols2({ file, content, lang: "go" });
|
|
2977
3618
|
case "py":
|
|
2978
|
-
return
|
|
3619
|
+
return parseSymbols4({ file, content, lang: "py" });
|
|
2979
3620
|
case "rs":
|
|
2980
|
-
return
|
|
3621
|
+
return parseSymbols5({ file, content, lang: "rs" });
|
|
2981
3622
|
case "json":
|
|
2982
|
-
return
|
|
3623
|
+
return parseSymbols6({ file, content, lang: "json" });
|
|
2983
3624
|
case "yaml":
|
|
2984
|
-
return
|
|
3625
|
+
return parseSymbols7({ file, content, lang: "yaml" });
|
|
2985
3626
|
default:
|
|
2986
|
-
return { file,
|
|
3627
|
+
return parseSymbols3({ file, content, lang: symbolLang });
|
|
2987
3628
|
}
|
|
2988
3629
|
}
|
|
2989
3630
|
function assignRefsToSymbols2(refs, symbols) {
|
|
@@ -3031,10 +3672,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
3031
3672
|
let discoveredFiles = null;
|
|
3032
3673
|
let discoveryComplete = true;
|
|
3033
3674
|
if (opts.files && opts.files.length > 0) {
|
|
3034
|
-
files = opts.files.map((f) =>
|
|
3675
|
+
files = opts.files.map((f) => path9.resolve(projectRoot, f)).filter((f) => {
|
|
3035
3676
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
3036
|
-
const rel =
|
|
3037
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
3677
|
+
const rel = path9.relative(projectRoot, f).replace(/\\/g, "/");
|
|
3678
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path9.basename(f)) && !isGitIgnored(rel, false);
|
|
3038
3679
|
});
|
|
3039
3680
|
} else {
|
|
3040
3681
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -3055,15 +3696,19 @@ async function runIndexerWithStore(store, opts) {
|
|
|
3055
3696
|
if (!force) {
|
|
3056
3697
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
3057
3698
|
}
|
|
3699
|
+
const parallelBatch = resolveParallelBatch();
|
|
3058
3700
|
let filesSinceLastYield = 0;
|
|
3059
|
-
for (let batchStart = 0; batchStart < files.length; batchStart +=
|
|
3060
|
-
const batchEnd = Math.min(batchStart +
|
|
3701
|
+
for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
|
|
3702
|
+
const batchEnd = Math.min(batchStart + parallelBatch, files.length);
|
|
3061
3703
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
3062
3704
|
opts.onProgress?.(batchEnd, files.length);
|
|
3063
3705
|
filesSinceLastYield += batchFiles.length;
|
|
3064
3706
|
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
3065
3707
|
filesSinceLastYield = 0;
|
|
3066
3708
|
await yieldEventLoop();
|
|
3709
|
+
if (isFrugalPerf()) {
|
|
3710
|
+
await new Promise((r) => setTimeout(r, 8));
|
|
3711
|
+
}
|
|
3067
3712
|
throwIfAborted(signal);
|
|
3068
3713
|
}
|
|
3069
3714
|
const statOpts = signal ? { signal } : {};
|
|
@@ -3167,9 +3812,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
3167
3812
|
continue;
|
|
3168
3813
|
}
|
|
3169
3814
|
if (parsed.symbols.length === 0) {
|
|
3170
|
-
store.
|
|
3171
|
-
store.deleteSymbolsForFile(file);
|
|
3172
|
-
store.upsertFile({
|
|
3815
|
+
store.replaceEmptyFile({
|
|
3173
3816
|
file,
|
|
3174
3817
|
lang,
|
|
3175
3818
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
@@ -3238,6 +3881,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
3238
3881
|
}
|
|
3239
3882
|
store.resolveRefs();
|
|
3240
3883
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
3884
|
+
store.optimize();
|
|
3241
3885
|
const durationMs = Date.now() - startMs;
|
|
3242
3886
|
store.setLastIndexed(Date.now());
|
|
3243
3887
|
return {
|