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