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