@wrongstack/tools 0.287.0 → 0.291.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/auto-proceed-loop-guard.d.ts +128 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -0
- package/dist/auto-proceed-loop-guard.js +46 -0
- package/dist/auto-proceed-loop-guard.js.map +7 -0
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1766 -864
- package/dist/builtin.js.map +3 -3
- package/dist/codebase-index/background-indexer.d.ts +1 -1
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +1 -2
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/index-service.d.ts +11 -1
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +2 -1
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +888 -254
- package/dist/codebase-index/index.js.map +3 -3
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/refs-extractor.d.ts +2 -17
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +43 -1
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +1 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +738 -212
- package/dist/codebase-index/worker.js.map +3 -3
- package/dist/codebase-index/writer.d.ts +60 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -5
- package/dist/exec.js.map +3 -3
- package/dist/fetch.js +2 -2
- package/dist/fetch.js.map +2 -2
- package/dist/format.js +61 -14
- package/dist/format.js.map +3 -3
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +2 -2
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2001 -873
- package/dist/index.js.map +3 -3
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +21 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +713 -572
- package/dist/kanban.js.map +3 -3
- package/dist/languages/detect.d.ts.map +1 -1
- package/dist/languages/index.js +61 -14
- package/dist/languages/index.js.map +3 -3
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/lint.js +61 -14
- package/dist/lint.js.map +3 -3
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +1763 -861
- package/dist/pack.js.map +3 -3
- package/dist/plan.js +4 -0
- package/dist/plan.js.map +2 -2
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +9 -0
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +197 -3
- package/dist/session-kanban.js.map +2 -2
- package/dist/task.js +4 -0
- package/dist/task.js.map +2 -2
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.js +4 -0
- package/dist/todo.js.map +2 -2
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +61 -14
- package/dist/typecheck.js.map +3 -3
- package/package.json +13 -5
|
@@ -92,7 +92,7 @@ import * as path from "node:path";
|
|
|
92
92
|
import { resolveWstackPaths } from "@wrongstack/core";
|
|
93
93
|
|
|
94
94
|
// src/codebase-index/schema.ts
|
|
95
|
-
var SCHEMA_VERSION =
|
|
95
|
+
var SCHEMA_VERSION = 3;
|
|
96
96
|
|
|
97
97
|
// src/codebase-index/lsp-kind.ts
|
|
98
98
|
function lspKindToInternalKind(k) {
|
|
@@ -164,7 +164,7 @@ function tokenise(text) {
|
|
|
164
164
|
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
165
165
|
}
|
|
166
166
|
function splitName(name) {
|
|
167
|
-
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
167
|
+
return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([\p{L}])(\d)/gu, "$1 $2").replace(/(\d)([\p{L}])/gu, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
168
168
|
}
|
|
169
169
|
function buildIndexableText(name, signature, docComment) {
|
|
170
170
|
return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
|
|
@@ -190,14 +190,12 @@ function buildBm25Index(docs) {
|
|
|
190
190
|
return new Bm25Index(documents, df, N, avgLen);
|
|
191
191
|
}
|
|
192
192
|
var Bm25Index = class {
|
|
193
|
-
constructor(documents,
|
|
193
|
+
constructor(documents, _df, N, avgLen) {
|
|
194
194
|
this.documents = documents;
|
|
195
|
-
this.df = df;
|
|
196
195
|
this.N = N;
|
|
197
196
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
198
197
|
}
|
|
199
198
|
documents;
|
|
200
|
-
df;
|
|
201
199
|
N;
|
|
202
200
|
safeAvgLen;
|
|
203
201
|
score(query, filter) {
|
|
@@ -210,10 +208,13 @@ var Bm25Index = class {
|
|
|
210
208
|
for (const qTerm of qTokens) {
|
|
211
209
|
let tf = 0;
|
|
212
210
|
for (const t of doc.tokens) {
|
|
213
|
-
if (t
|
|
211
|
+
if (t.startsWith(qTerm)) tf++;
|
|
214
212
|
}
|
|
215
213
|
if (tf === 0) continue;
|
|
216
|
-
|
|
214
|
+
let dfVal = 0;
|
|
215
|
+
for (const candidate of this.documents) {
|
|
216
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
217
|
+
}
|
|
217
218
|
if (dfVal === 0) continue;
|
|
218
219
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
219
220
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -246,6 +247,29 @@ var Bm25Index = class {
|
|
|
246
247
|
|
|
247
248
|
// src/codebase-index/writer.ts
|
|
248
249
|
var DB_FILE = "index.db";
|
|
250
|
+
function escapeLike(value) {
|
|
251
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
252
|
+
}
|
|
253
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
254
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
255
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
256
|
+
const seen = /* @__PURE__ */ new Set();
|
|
257
|
+
const assigned = [];
|
|
258
|
+
for (const ref of refs) {
|
|
259
|
+
let owner;
|
|
260
|
+
for (const symbol of ordered) {
|
|
261
|
+
if (symbol.line > ref.line) break;
|
|
262
|
+
owner = symbol;
|
|
263
|
+
}
|
|
264
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
265
|
+
if (!owner || owner.id <= 0) continue;
|
|
266
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}`;
|
|
267
|
+
if (seen.has(key)) continue;
|
|
268
|
+
seen.add(key);
|
|
269
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
270
|
+
}
|
|
271
|
+
return assigned;
|
|
272
|
+
}
|
|
249
273
|
function resolveIndexDir(projectRoot, override) {
|
|
250
274
|
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
251
275
|
}
|
|
@@ -253,6 +277,53 @@ function codebaseIndexDirOverride(ctx) {
|
|
|
253
277
|
const v = ctx.meta?.["codebaseIndexDir"];
|
|
254
278
|
return typeof v === "string" ? v : void 0;
|
|
255
279
|
}
|
|
280
|
+
var StorePool = class {
|
|
281
|
+
stores = /* @__PURE__ */ new Map();
|
|
282
|
+
key(projectRoot, indexDir) {
|
|
283
|
+
return `${projectRoot}\0${indexDir ?? ""}`;
|
|
284
|
+
}
|
|
285
|
+
/** Borrow a store. Creates it on first access for this key. */
|
|
286
|
+
acquire(projectRoot, opts) {
|
|
287
|
+
const k = this.key(projectRoot, opts?.indexDir);
|
|
288
|
+
let store = this.stores.get(k);
|
|
289
|
+
if (!store) {
|
|
290
|
+
store = new IndexStore(projectRoot, { indexDir: opts?.indexDir });
|
|
291
|
+
this.stores.set(k, store);
|
|
292
|
+
}
|
|
293
|
+
return store;
|
|
294
|
+
}
|
|
295
|
+
/** Return the store to the pool. The connection stays warm for subsequent
|
|
296
|
+
* operations on the same (projectRoot, indexDir). */
|
|
297
|
+
release(_store) {
|
|
298
|
+
}
|
|
299
|
+
/** Close every pooled connection and drain the pool. Call on shutdown. */
|
|
300
|
+
closeAll() {
|
|
301
|
+
for (const store of this.stores.values()) {
|
|
302
|
+
try {
|
|
303
|
+
store.close();
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
this.stores.clear();
|
|
308
|
+
}
|
|
309
|
+
/** Remove one store from the pool. Used by tests that need isolation. */
|
|
310
|
+
evict(projectRoot, indexDir) {
|
|
311
|
+
const k = this.key(projectRoot, indexDir);
|
|
312
|
+
const store = this.stores.get(k);
|
|
313
|
+
if (store) {
|
|
314
|
+
try {
|
|
315
|
+
store.close();
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
this.stores.delete(k);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** True when the pool holds a connection for the given key. */
|
|
322
|
+
has(projectRoot, indexDir) {
|
|
323
|
+
return this.stores.has(this.key(projectRoot, indexDir));
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
var indexStorePool = new StorePool();
|
|
256
327
|
var warningSilenced = false;
|
|
257
328
|
function silenceSqliteExperimentalWarning() {
|
|
258
329
|
if (warningSilenced) return;
|
|
@@ -299,7 +370,7 @@ function sleepSync(ms) {
|
|
|
299
370
|
} catch {
|
|
300
371
|
}
|
|
301
372
|
}
|
|
302
|
-
var IndexStore = class {
|
|
373
|
+
var IndexStore = class _IndexStore {
|
|
303
374
|
db;
|
|
304
375
|
/** Absolute path to this project's index directory. */
|
|
305
376
|
indexDir;
|
|
@@ -351,10 +422,7 @@ var IndexStore = class {
|
|
|
351
422
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
352
423
|
throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
|
|
353
424
|
}
|
|
354
|
-
const delay = Math.min(
|
|
355
|
-
LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
|
|
356
|
-
LOCK_RETRY_MAX_DELAY_MS
|
|
357
|
-
);
|
|
425
|
+
const delay = Math.min(LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt, LOCK_RETRY_MAX_DELAY_MS);
|
|
358
426
|
sleepSync(delay);
|
|
359
427
|
}
|
|
360
428
|
}
|
|
@@ -436,8 +504,24 @@ var IndexStore = class {
|
|
|
436
504
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
|
|
437
505
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
|
|
438
506
|
try {
|
|
439
|
-
this.db.exec(
|
|
507
|
+
this.db.exec(
|
|
508
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')"
|
|
509
|
+
);
|
|
440
510
|
this.ftsAvailable = true;
|
|
511
|
+
const symbolCount = Number(
|
|
512
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
513
|
+
);
|
|
514
|
+
const ftsCount = Number(
|
|
515
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
516
|
+
);
|
|
517
|
+
if (symbolCount !== ftsCount) {
|
|
518
|
+
this.db.exec("DELETE FROM symbols_fts");
|
|
519
|
+
const insert = this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)");
|
|
520
|
+
const rows = this.db.prepare("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
521
|
+
for (const row of rows) {
|
|
522
|
+
insert.run(row.id, buildIndexableText(row.name, row.signature, row.doc_comment));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
441
525
|
} catch {
|
|
442
526
|
this.ftsAvailable = false;
|
|
443
527
|
}
|
|
@@ -457,7 +541,7 @@ var IndexStore = class {
|
|
|
457
541
|
return this.runWithRetry(() => {
|
|
458
542
|
this.db.exec("BEGIN IMMEDIATE");
|
|
459
543
|
try {
|
|
460
|
-
const maxRows = this.
|
|
544
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
461
545
|
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
462
546
|
const stmt = this.db.prepare(
|
|
463
547
|
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
@@ -495,7 +579,9 @@ var IndexStore = class {
|
|
|
495
579
|
deleteSymbolsForFile(file) {
|
|
496
580
|
this.runWithRetry(() => {
|
|
497
581
|
if (this.ftsAvailable) {
|
|
498
|
-
this.stmt(
|
|
582
|
+
this.stmt(
|
|
583
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
584
|
+
).run(file);
|
|
499
585
|
}
|
|
500
586
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
501
587
|
});
|
|
@@ -507,9 +593,21 @@ var IndexStore = class {
|
|
|
507
593
|
*/
|
|
508
594
|
deleteFile(file) {
|
|
509
595
|
this.runWithRetry(() => {
|
|
510
|
-
this.
|
|
511
|
-
|
|
512
|
-
|
|
596
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
597
|
+
try {
|
|
598
|
+
if (this.ftsAvailable) {
|
|
599
|
+
this.db.prepare(
|
|
600
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
601
|
+
).run(file);
|
|
602
|
+
}
|
|
603
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
|
|
604
|
+
this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
605
|
+
this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
|
|
606
|
+
this.db.exec("COMMIT");
|
|
607
|
+
} catch (err) {
|
|
608
|
+
this.db.exec("ROLLBACK");
|
|
609
|
+
throw err;
|
|
610
|
+
}
|
|
513
611
|
});
|
|
514
612
|
}
|
|
515
613
|
// ─── File metadata ──────────────────────────────────────────────────────────
|
|
@@ -532,14 +630,22 @@ var IndexStore = class {
|
|
|
532
630
|
).all(file);
|
|
533
631
|
if (!rows.length) return null;
|
|
534
632
|
const r = expectDefined(rows[0]);
|
|
535
|
-
return {
|
|
633
|
+
return {
|
|
634
|
+
file: r.file,
|
|
635
|
+
lang: r.lang,
|
|
636
|
+
mtimeMs: r.mtime_ms,
|
|
637
|
+
symbolCount: r.symbol_count,
|
|
638
|
+
lastIndexed: r.last_indexed
|
|
639
|
+
};
|
|
536
640
|
}
|
|
537
641
|
getAllFileMetas() {
|
|
538
|
-
return this.db.prepare(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
642
|
+
return this.db.prepare("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
643
|
+
file: r.file,
|
|
644
|
+
lang: r.lang,
|
|
645
|
+
mtimeMs: r.mtime_ms,
|
|
646
|
+
symbolCount: r.symbol_count,
|
|
647
|
+
lastIndexed: r.last_indexed
|
|
648
|
+
}));
|
|
543
649
|
}
|
|
544
650
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
545
651
|
search(query, filter) {
|
|
@@ -563,8 +669,8 @@ var IndexStore = class {
|
|
|
563
669
|
values.push(filter.lang);
|
|
564
670
|
}
|
|
565
671
|
if (filter?.file) {
|
|
566
|
-
conditions.push("file LIKE ?");
|
|
567
|
-
values.push(`%${filter.file}%`);
|
|
672
|
+
conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
673
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
568
674
|
}
|
|
569
675
|
if (query.trim()) {
|
|
570
676
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
@@ -602,9 +708,11 @@ var IndexStore = class {
|
|
|
602
708
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
603
709
|
*/
|
|
604
710
|
searchRanked(query, filter, limit) {
|
|
711
|
+
const rawLimit = Number.isFinite(limit) ? Math.trunc(limit) : 20;
|
|
712
|
+
const safeLimit = Math.max(1, Math.min(rawLimit, 100));
|
|
605
713
|
const tokens = tokenise(query);
|
|
606
714
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
607
|
-
return this.searchRankedFallback(query, filter,
|
|
715
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
608
716
|
}
|
|
609
717
|
let effectiveKind = filter?.kind;
|
|
610
718
|
if (filter?.lspKind !== void 0) {
|
|
@@ -624,11 +732,13 @@ var IndexStore = class {
|
|
|
624
732
|
values.push(filter.lang);
|
|
625
733
|
}
|
|
626
734
|
if (filter?.file) {
|
|
627
|
-
conditions.push("s.file LIKE ?");
|
|
628
|
-
values.push(`%${filter.file}%`);
|
|
735
|
+
conditions.push("replace(s.file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
736
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
629
737
|
}
|
|
630
738
|
const where = conditions.join(" AND ");
|
|
631
|
-
const countRows = this.db.prepare(
|
|
739
|
+
const countRows = this.db.prepare(
|
|
740
|
+
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
741
|
+
).all(...values);
|
|
632
742
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
633
743
|
if (total === 0) return { results: [], total: 0 };
|
|
634
744
|
const rows = this.db.prepare(
|
|
@@ -637,9 +747,13 @@ var IndexStore = class {
|
|
|
637
747
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
638
748
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
639
749
|
WHERE ${where}
|
|
640
|
-
ORDER BY
|
|
750
|
+
ORDER BY
|
|
751
|
+
CASE WHEN lower(s.name) = lower(?) THEN 0
|
|
752
|
+
WHEN lower(s.name) LIKE lower(?) ESCAPE '\\' THEN 1
|
|
753
|
+
ELSE 2 END,
|
|
754
|
+
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
641
755
|
LIMIT ?`
|
|
642
|
-
).all(...values,
|
|
756
|
+
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
643
757
|
return {
|
|
644
758
|
results: rows.map((r) => ({
|
|
645
759
|
id: r.id,
|
|
@@ -669,10 +783,28 @@ var IndexStore = class {
|
|
|
669
783
|
}
|
|
670
784
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
671
785
|
const bm25 = buildBm25Index(
|
|
672
|
-
candidates.map((c) => ({
|
|
786
|
+
candidates.map((c) => ({
|
|
787
|
+
id: c.id,
|
|
788
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
789
|
+
}))
|
|
673
790
|
);
|
|
674
791
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
675
|
-
|
|
792
|
+
const q = query.trim().toLowerCase();
|
|
793
|
+
const rank = (id) => {
|
|
794
|
+
const name = candidateById.get(id)?.name.toLowerCase() ?? "";
|
|
795
|
+
if (name === q) return 0;
|
|
796
|
+
if (name.startsWith(q)) return 1;
|
|
797
|
+
return 2;
|
|
798
|
+
};
|
|
799
|
+
scored.sort((a, b) => {
|
|
800
|
+
const rankDiff = rank(a.id) - rank(b.id);
|
|
801
|
+
if (rankDiff !== 0) return rankDiff;
|
|
802
|
+
const scoreDiff = b.score - a.score;
|
|
803
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
804
|
+
const left = expectDefined(candidateById.get(a.id));
|
|
805
|
+
const right = expectDefined(candidateById.get(b.id));
|
|
806
|
+
return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
|
|
807
|
+
});
|
|
676
808
|
const qTokens = tokenise(query);
|
|
677
809
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
678
810
|
const c = expectDefined(candidateById.get(id));
|
|
@@ -681,9 +813,7 @@ var IndexStore = class {
|
|
|
681
813
|
return { results, total: candidates.length };
|
|
682
814
|
}
|
|
683
815
|
getAllIndexable() {
|
|
684
|
-
return this.
|
|
685
|
-
({ id, text }) => ({ id, text })
|
|
686
|
-
);
|
|
816
|
+
return this.stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
687
817
|
}
|
|
688
818
|
/**
|
|
689
819
|
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
@@ -693,28 +823,22 @@ var IndexStore = class {
|
|
|
693
823
|
* `symbols.id`). Ids may have gaps — that is fine.
|
|
694
824
|
*/
|
|
695
825
|
getMaxSymbolId() {
|
|
696
|
-
const rows = this.
|
|
826
|
+
const rows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
697
827
|
return rows[0]?.m ?? 0;
|
|
698
828
|
}
|
|
699
829
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
700
830
|
getStats() {
|
|
701
831
|
const sizeBytes = this.sizeBytes();
|
|
702
|
-
const lastRows = this.db.prepare(
|
|
703
|
-
"SELECT value FROM metadata WHERE key = 'last_indexed'"
|
|
704
|
-
).all();
|
|
832
|
+
const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
705
833
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
706
|
-
const totalRows = this.
|
|
834
|
+
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
707
835
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
708
|
-
const fileRows = this.
|
|
836
|
+
const fileRows = this.stmt("SELECT COUNT(*) FROM files").all();
|
|
709
837
|
const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
|
|
710
|
-
const langRows = this.
|
|
711
|
-
"SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
|
|
712
|
-
).all();
|
|
838
|
+
const langRows = this.stmt("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
|
|
713
839
|
const byLang = {};
|
|
714
840
|
for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
|
|
715
|
-
const kindRows = this.
|
|
716
|
-
"SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
|
|
717
|
-
).all();
|
|
841
|
+
const kindRows = this.stmt("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
|
|
718
842
|
const byKind = {};
|
|
719
843
|
for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
|
|
720
844
|
return {
|
|
@@ -730,17 +854,34 @@ var IndexStore = class {
|
|
|
730
854
|
}
|
|
731
855
|
setLastIndexed(ts2) {
|
|
732
856
|
this.runWithRetry(() => {
|
|
733
|
-
this.db.prepare(
|
|
734
|
-
|
|
735
|
-
|
|
857
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
getMetadata(key) {
|
|
861
|
+
const rows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
862
|
+
return rows[0]?.value;
|
|
863
|
+
}
|
|
864
|
+
setMetadata(key, value) {
|
|
865
|
+
this.runWithRetry(() => {
|
|
866
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
736
867
|
});
|
|
737
868
|
}
|
|
738
869
|
clearAll() {
|
|
739
870
|
this.runWithRetry(() => {
|
|
740
|
-
this.db.exec("
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
871
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
872
|
+
try {
|
|
873
|
+
this.db.exec("DROP TABLE IF EXISTS refs");
|
|
874
|
+
this.db.exec("DROP TABLE IF EXISTS symbols");
|
|
875
|
+
this.db.exec("DROP TABLE IF EXISTS files");
|
|
876
|
+
this.db.exec("DROP TABLE IF EXISTS metadata");
|
|
877
|
+
if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
878
|
+
this.db.exec("COMMIT");
|
|
879
|
+
this.stmtCache.clear();
|
|
880
|
+
this.initSchema();
|
|
881
|
+
} catch (err) {
|
|
882
|
+
this.db.exec("ROLLBACK");
|
|
883
|
+
throw err;
|
|
884
|
+
}
|
|
744
885
|
});
|
|
745
886
|
}
|
|
746
887
|
// ─── Ref CRUD ────────────────────────────────────────────────────────────────
|
|
@@ -821,7 +962,7 @@ var IndexStore = class {
|
|
|
821
962
|
).run(...options.deleteForFiles);
|
|
822
963
|
this.db.prepare(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
823
964
|
}
|
|
824
|
-
const maxRows = this.
|
|
965
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
825
966
|
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
826
967
|
const symStmt = this.db.prepare(
|
|
827
968
|
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
@@ -831,6 +972,7 @@ var IndexStore = class {
|
|
|
831
972
|
const allInserted = [];
|
|
832
973
|
const refsToInsert = [];
|
|
833
974
|
for (const entry of entries) {
|
|
975
|
+
const insertedForEntry = [];
|
|
834
976
|
for (const s of entry.symbols) {
|
|
835
977
|
const id = nextId++;
|
|
836
978
|
symStmt.run(
|
|
@@ -848,9 +990,11 @@ var IndexStore = class {
|
|
|
848
990
|
s.file
|
|
849
991
|
);
|
|
850
992
|
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
851
|
-
|
|
993
|
+
const inserted = { ...s, id };
|
|
994
|
+
allInserted.push(inserted);
|
|
995
|
+
insertedForEntry.push(inserted);
|
|
852
996
|
}
|
|
853
|
-
|
|
997
|
+
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
854
998
|
}
|
|
855
999
|
if (refsToInsert.length > 0) {
|
|
856
1000
|
const refStmt = this.db.prepare(
|
|
@@ -888,9 +1032,7 @@ var IndexStore = class {
|
|
|
888
1032
|
*/
|
|
889
1033
|
deleteRefsForFile(file) {
|
|
890
1034
|
this.runWithRetry(() => {
|
|
891
|
-
this.db.prepare(
|
|
892
|
-
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
893
|
-
).run(file);
|
|
1035
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
894
1036
|
});
|
|
895
1037
|
}
|
|
896
1038
|
/**
|
|
@@ -931,9 +1073,7 @@ var IndexStore = class {
|
|
|
931
1073
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
932
1074
|
*/
|
|
933
1075
|
findRefsFrom(symbolId) {
|
|
934
|
-
return this.db.prepare(
|
|
935
|
-
"SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?"
|
|
936
|
-
).all(symbolId).map((r) => ({
|
|
1076
|
+
return this.db.prepare("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
937
1077
|
id: r.id,
|
|
938
1078
|
fromId: r.from_id,
|
|
939
1079
|
toName: r.to_name,
|
|
@@ -950,6 +1090,356 @@ var IndexStore = class {
|
|
|
950
1090
|
return 0;
|
|
951
1091
|
}
|
|
952
1092
|
}
|
|
1093
|
+
// ─── CodeMap graph aggregation ──────────────────────────────────────────────
|
|
1094
|
+
/**
|
|
1095
|
+
* Derive a monorepo package name from an absolute file path.
|
|
1096
|
+
* Handles both `packages/<name>/...` (workspace) and `src/<name>/...` layouts.
|
|
1097
|
+
* Returns `undefined` for files outside any recognisable package structure.
|
|
1098
|
+
*/
|
|
1099
|
+
static derivePackage(filePath) {
|
|
1100
|
+
const f = filePath.replace(/\\/g, "/");
|
|
1101
|
+
const pkgsIdx = f.indexOf("/packages/");
|
|
1102
|
+
if (pkgsIdx !== -1) {
|
|
1103
|
+
const rest = f.slice(pkgsIdx + "/packages/".length);
|
|
1104
|
+
const seg = rest.split("/")[0];
|
|
1105
|
+
return seg ? `@wrongstack/${seg}` : void 0;
|
|
1106
|
+
}
|
|
1107
|
+
const appsIdx = f.indexOf("/apps/");
|
|
1108
|
+
if (appsIdx !== -1) {
|
|
1109
|
+
const rest = f.slice(appsIdx + "/apps/".length);
|
|
1110
|
+
const seg = rest.split("/")[0];
|
|
1111
|
+
return seg ? `app:${seg}` : void 0;
|
|
1112
|
+
}
|
|
1113
|
+
return void 0;
|
|
1114
|
+
}
|
|
1115
|
+
static packageFromImport(moduleName) {
|
|
1116
|
+
if (!moduleName.startsWith("@wrongstack/")) return void 0;
|
|
1117
|
+
const parts = moduleName.split("/");
|
|
1118
|
+
return parts[1] ? `@wrongstack/${parts[1]}` : void 0;
|
|
1119
|
+
}
|
|
1120
|
+
static resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
1121
|
+
if (!moduleName.startsWith(".")) return void 0;
|
|
1122
|
+
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
1123
|
+
const absolute = path.posix.normalize(
|
|
1124
|
+
path.posix.join(path.posix.dirname(normalizedFrom), moduleName)
|
|
1125
|
+
);
|
|
1126
|
+
const extension = path.posix.extname(absolute);
|
|
1127
|
+
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
1128
|
+
const candidates = [
|
|
1129
|
+
absolute,
|
|
1130
|
+
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
1131
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path.posix.join(absolute, `index${ext}`)),
|
|
1132
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path.posix.join(base, `index${ext}`))
|
|
1133
|
+
];
|
|
1134
|
+
const indexedByPortablePath = new Map(
|
|
1135
|
+
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
1136
|
+
);
|
|
1137
|
+
for (const candidate of candidates) {
|
|
1138
|
+
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
1139
|
+
if (indexed) return indexed;
|
|
1140
|
+
}
|
|
1141
|
+
return void 0;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Package-level graph: each workspace package is a node; edges are derived
|
|
1145
|
+
* from cross-package symbol references (a symbol in package A references a
|
|
1146
|
+
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
1147
|
+
*/
|
|
1148
|
+
getPackageGraph() {
|
|
1149
|
+
const symbols = this.db.prepare("SELECT file, id FROM symbols ORDER BY id").all();
|
|
1150
|
+
const pkgNodes = /* @__PURE__ */ new Map();
|
|
1151
|
+
const fileToPkg = /* @__PURE__ */ new Map();
|
|
1152
|
+
const symbolToPkg = /* @__PURE__ */ new Map();
|
|
1153
|
+
for (const s of symbols) {
|
|
1154
|
+
const pkg = _IndexStore.derivePackage(s.file) ?? "(root)";
|
|
1155
|
+
fileToPkg.set(s.file, pkg);
|
|
1156
|
+
symbolToPkg.set(s.id, pkg);
|
|
1157
|
+
const node = pkgNodes.get(pkg);
|
|
1158
|
+
if (node) {
|
|
1159
|
+
node.symbolCount = (node.symbolCount ?? 0) + 1;
|
|
1160
|
+
} else {
|
|
1161
|
+
pkgNodes.set(pkg, {
|
|
1162
|
+
id: `pkg:${pkg}`,
|
|
1163
|
+
label: pkg,
|
|
1164
|
+
kind: "package",
|
|
1165
|
+
package: pkg,
|
|
1166
|
+
symbolCount: 1,
|
|
1167
|
+
fileCount: 0
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
const files = this.db.prepare("SELECT DISTINCT file FROM files").all();
|
|
1172
|
+
for (const { file } of files) {
|
|
1173
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
1174
|
+
const node = pkgNodes.get(pkg);
|
|
1175
|
+
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
1176
|
+
}
|
|
1177
|
+
const refRows = this.db.prepare(
|
|
1178
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
1179
|
+
FROM refs r
|
|
1180
|
+
WHERE r.to_id IS NOT NULL`
|
|
1181
|
+
).all();
|
|
1182
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
1183
|
+
for (const r of refRows) {
|
|
1184
|
+
if (r.call_type === "import") continue;
|
|
1185
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
1186
|
+
const toPkg = symbolToPkg.get(r.to_id);
|
|
1187
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
1188
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
1189
|
+
let e = edgeMap.get(key);
|
|
1190
|
+
if (!e) {
|
|
1191
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1192
|
+
edgeMap.set(key, e);
|
|
1193
|
+
}
|
|
1194
|
+
e.weight++;
|
|
1195
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
1196
|
+
}
|
|
1197
|
+
const importRows = this.db.prepare(
|
|
1198
|
+
`SELECT r.from_id, r.to_name
|
|
1199
|
+
FROM refs r
|
|
1200
|
+
WHERE r.call_type = 'import'`
|
|
1201
|
+
).all();
|
|
1202
|
+
for (const r of importRows) {
|
|
1203
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
1204
|
+
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
1205
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
1206
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
1207
|
+
let edge = edgeMap.get(key);
|
|
1208
|
+
if (!edge) {
|
|
1209
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1210
|
+
edgeMap.set(key, edge);
|
|
1211
|
+
}
|
|
1212
|
+
edge.weight++;
|
|
1213
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
1214
|
+
}
|
|
1215
|
+
const edges = [];
|
|
1216
|
+
for (const [key, e] of edgeMap) {
|
|
1217
|
+
const [source, target] = key.split("\0");
|
|
1218
|
+
let bestType = "call";
|
|
1219
|
+
let bestCount = 0;
|
|
1220
|
+
for (const [t, c] of e.types) {
|
|
1221
|
+
if (c > bestCount) {
|
|
1222
|
+
bestType = t;
|
|
1223
|
+
bestCount = c;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
edges.push({
|
|
1227
|
+
source: `pkg:${source}`,
|
|
1228
|
+
target: `pkg:${target}`,
|
|
1229
|
+
weight: e.weight,
|
|
1230
|
+
refType: bestType
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
return { nodes: [...pkgNodes.values()], edges };
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* File-level graph for a single package: each file is a node; edges are
|
|
1237
|
+
* derived from cross-file symbol references within the package.
|
|
1238
|
+
*/
|
|
1239
|
+
getFileGraph(packageFilter) {
|
|
1240
|
+
const allFiles = this.db.prepare("SELECT DISTINCT file FROM symbols").all();
|
|
1241
|
+
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
1242
|
+
const localFiles = new Set(pkgFilePaths);
|
|
1243
|
+
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
1244
|
+
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
1245
|
+
const pkgSyms = this.db.prepare(
|
|
1246
|
+
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
1247
|
+
).all(...pkgFilePaths);
|
|
1248
|
+
const fileNodes = /* @__PURE__ */ new Map();
|
|
1249
|
+
const symToFile = /* @__PURE__ */ new Map();
|
|
1250
|
+
const fileStats = /* @__PURE__ */ new Map();
|
|
1251
|
+
for (const s of pkgSyms) {
|
|
1252
|
+
symToFile.set(s.id, s.file);
|
|
1253
|
+
const current = fileStats.get(s.file);
|
|
1254
|
+
fileStats.set(s.file, {
|
|
1255
|
+
count: (current?.count ?? 0) + 1,
|
|
1256
|
+
lang: current?.lang ?? s.lang
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
const ensureFileNode = (file) => {
|
|
1260
|
+
if (fileNodes.has(file)) return;
|
|
1261
|
+
const stats = fileStats.get(file);
|
|
1262
|
+
fileNodes.set(file, {
|
|
1263
|
+
id: `file:${file}`,
|
|
1264
|
+
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
1265
|
+
kind: "file",
|
|
1266
|
+
package: _IndexStore.derivePackage(file) ?? "(root)",
|
|
1267
|
+
file,
|
|
1268
|
+
symbolCount: stats?.count ?? 0,
|
|
1269
|
+
lang: stats?.lang,
|
|
1270
|
+
external: !localFiles.has(file)
|
|
1271
|
+
});
|
|
1272
|
+
};
|
|
1273
|
+
for (const file of localFiles) {
|
|
1274
|
+
ensureFileNode(file);
|
|
1275
|
+
}
|
|
1276
|
+
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
1277
|
+
const refRows = this.db.prepare(
|
|
1278
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
1279
|
+
FROM refs r
|
|
1280
|
+
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
1281
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
|
|
1282
|
+
AND r.to_id IS NOT NULL`
|
|
1283
|
+
).all(...pkgFilePaths, ...pkgFilePaths);
|
|
1284
|
+
const knownSymIds = new Set(pkgSyms.map((s) => s.id));
|
|
1285
|
+
const crossRefIds = /* @__PURE__ */ new Set();
|
|
1286
|
+
for (const r of refRows) {
|
|
1287
|
+
if (!knownSymIds.has(r.from_id)) crossRefIds.add(r.from_id);
|
|
1288
|
+
if (!knownSymIds.has(r.to_id)) crossRefIds.add(r.to_id);
|
|
1289
|
+
}
|
|
1290
|
+
if (crossRefIds.size > 0) {
|
|
1291
|
+
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
1292
|
+
const extras = this.db.prepare(
|
|
1293
|
+
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
1294
|
+
).all(...crossRefIds);
|
|
1295
|
+
for (const x of extras) {
|
|
1296
|
+
symToFile.set(x.id, x.file);
|
|
1297
|
+
if (!fileStats.has(x.file)) {
|
|
1298
|
+
fileStats.set(x.file, { count: 0, lang: "ts" });
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
1303
|
+
for (const r of refRows) {
|
|
1304
|
+
if (r.call_type === "import") continue;
|
|
1305
|
+
const fromFile = symToFile.get(r.from_id);
|
|
1306
|
+
const toFile = symToFile.get(r.to_id);
|
|
1307
|
+
if (!fromFile || !toFile || fromFile === toFile) continue;
|
|
1308
|
+
if (!localFiles.has(fromFile) && !localFiles.has(toFile)) continue;
|
|
1309
|
+
ensureFileNode(fromFile);
|
|
1310
|
+
ensureFileNode(toFile);
|
|
1311
|
+
const key = `${fromFile}\0${toFile}`;
|
|
1312
|
+
let e = edgeMap.get(key);
|
|
1313
|
+
if (!e) {
|
|
1314
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1315
|
+
edgeMap.set(key, e);
|
|
1316
|
+
}
|
|
1317
|
+
e.weight++;
|
|
1318
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
1319
|
+
}
|
|
1320
|
+
const importRows = this.db.prepare(
|
|
1321
|
+
`SELECT r.from_id, r.to_name
|
|
1322
|
+
FROM refs r
|
|
1323
|
+
WHERE r.call_type = 'import'
|
|
1324
|
+
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))`
|
|
1325
|
+
).all(...pkgFilePaths);
|
|
1326
|
+
for (const r of importRows) {
|
|
1327
|
+
const fromFile = symToFile.get(r.from_id);
|
|
1328
|
+
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
1329
|
+
const toFile = _IndexStore.resolveRelativeImport(fromFile, r.to_name, indexedFiles);
|
|
1330
|
+
if (!toFile || fromFile === toFile) continue;
|
|
1331
|
+
ensureFileNode(fromFile);
|
|
1332
|
+
ensureFileNode(toFile);
|
|
1333
|
+
const key = `${fromFile}\0${toFile}`;
|
|
1334
|
+
let edge = edgeMap.get(key);
|
|
1335
|
+
if (!edge) {
|
|
1336
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1337
|
+
edgeMap.set(key, edge);
|
|
1338
|
+
}
|
|
1339
|
+
edge.weight++;
|
|
1340
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
1341
|
+
}
|
|
1342
|
+
const edges = [];
|
|
1343
|
+
for (const [key, e] of edgeMap) {
|
|
1344
|
+
const [source, target] = key.split("\0");
|
|
1345
|
+
let bestType = "call";
|
|
1346
|
+
let bestCount = 0;
|
|
1347
|
+
for (const [t, c] of e.types) {
|
|
1348
|
+
if (c > bestCount) {
|
|
1349
|
+
bestType = t;
|
|
1350
|
+
bestCount = c;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
edges.push({
|
|
1354
|
+
source: `file:${source}`,
|
|
1355
|
+
target: `file:${target}`,
|
|
1356
|
+
weight: e.weight,
|
|
1357
|
+
refType: bestType
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
return { nodes: [...fileNodes.values()], edges };
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Symbol-level graph for a single file: each symbol is a node; edges are
|
|
1364
|
+
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
1365
|
+
*/
|
|
1366
|
+
getSymbolGraph(fileFilter) {
|
|
1367
|
+
const syms = this.db.prepare(
|
|
1368
|
+
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
1369
|
+
).all(fileFilter);
|
|
1370
|
+
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
1371
|
+
const symById = new Map(syms.map((symbol) => [symbol.id, symbol]));
|
|
1372
|
+
const relatedIds = new Set(syms.map((symbol) => symbol.id));
|
|
1373
|
+
const toGraphNode = (s) => ({
|
|
1374
|
+
id: `sym:${s.id}`,
|
|
1375
|
+
label: s.name,
|
|
1376
|
+
kind: "symbol",
|
|
1377
|
+
symbolId: s.id,
|
|
1378
|
+
symbolKind: s.kind,
|
|
1379
|
+
file: s.file,
|
|
1380
|
+
package: _IndexStore.derivePackage(s.file) ?? "(root)",
|
|
1381
|
+
lang: s.lang,
|
|
1382
|
+
line: s.line,
|
|
1383
|
+
signature: s.signature,
|
|
1384
|
+
scope: s.scope,
|
|
1385
|
+
external: s.file !== fileFilter
|
|
1386
|
+
});
|
|
1387
|
+
const symIds = new Set(syms.map((s) => s.id));
|
|
1388
|
+
const refRows = this.db.prepare(
|
|
1389
|
+
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
1390
|
+
FROM refs r
|
|
1391
|
+
WHERE r.from_id IN (SELECT id FROM symbols WHERE file = ?)
|
|
1392
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file = ?)`
|
|
1393
|
+
).all(fileFilter, fileFilter);
|
|
1394
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
1395
|
+
for (const r of refRows) {
|
|
1396
|
+
if (r.to_id === null) continue;
|
|
1397
|
+
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
1398
|
+
relatedIds.add(r.from_id);
|
|
1399
|
+
relatedIds.add(r.to_id);
|
|
1400
|
+
const key = `${r.from_id}\0${r.to_id}`;
|
|
1401
|
+
let e = edgeMap.get(key);
|
|
1402
|
+
if (!e) {
|
|
1403
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1404
|
+
edgeMap.set(key, e);
|
|
1405
|
+
}
|
|
1406
|
+
e.weight++;
|
|
1407
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
1408
|
+
}
|
|
1409
|
+
const edges = [];
|
|
1410
|
+
for (const [key, e] of edgeMap) {
|
|
1411
|
+
const [fromId, toId] = key.split("\0").map(Number);
|
|
1412
|
+
let bestType = "call";
|
|
1413
|
+
let bestCount = 0;
|
|
1414
|
+
for (const [t, c] of e.types) {
|
|
1415
|
+
if (c > bestCount) {
|
|
1416
|
+
bestType = t;
|
|
1417
|
+
bestCount = c;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
edges.push({
|
|
1421
|
+
source: `sym:${fromId}`,
|
|
1422
|
+
target: `sym:${toId}`,
|
|
1423
|
+
weight: e.weight,
|
|
1424
|
+
refType: bestType
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
const loadedIds = new Set(syms.map((s) => s.id));
|
|
1428
|
+
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
1429
|
+
if (missingIds.length > 0) {
|
|
1430
|
+
const placeholders = missingIds.map(() => "?").join(",");
|
|
1431
|
+
const extras = this.db.prepare(
|
|
1432
|
+
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
1433
|
+
).all(...missingIds);
|
|
1434
|
+
for (const s of extras) symById.set(s.id, s);
|
|
1435
|
+
}
|
|
1436
|
+
const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
1437
|
+
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
1438
|
+
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
1439
|
+
return aExternal - bExternal || a.file.localeCompare(b.file) || a.line - b.line || a.id - b.id;
|
|
1440
|
+
}).map(toGraphNode);
|
|
1441
|
+
return { nodes, edges };
|
|
1442
|
+
}
|
|
953
1443
|
close() {
|
|
954
1444
|
this.stmtCache.clear();
|
|
955
1445
|
try {
|
|
@@ -968,6 +1458,7 @@ import { Worker } from "node:worker_threads";
|
|
|
968
1458
|
import { expectDefined as expectDefined5 } from "@wrongstack/core";
|
|
969
1459
|
import * as fs7 from "node:fs/promises";
|
|
970
1460
|
import * as path8 from "node:path";
|
|
1461
|
+
import { availableParallelism } from "node:os";
|
|
971
1462
|
import { compileGlob as compileGlob2 } from "@wrongstack/core";
|
|
972
1463
|
|
|
973
1464
|
// src/codebase-index/ts-parser.ts
|
|
@@ -1024,8 +1515,7 @@ function extToLang(ext) {
|
|
|
1024
1515
|
return null;
|
|
1025
1516
|
}
|
|
1026
1517
|
}
|
|
1027
|
-
function getSignature(node, sourceFile) {
|
|
1028
|
-
const printer = ts.createPrinter({});
|
|
1518
|
+
function getSignature(printer, node, sourceFile) {
|
|
1029
1519
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
1030
1520
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
1031
1521
|
}
|
|
@@ -1044,20 +1534,14 @@ function getJsDoc(node, sourceFile) {
|
|
|
1044
1534
|
}
|
|
1045
1535
|
return "";
|
|
1046
1536
|
}
|
|
1047
|
-
function
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
if (
|
|
1052
|
-
parts.
|
|
1053
|
-
} else if (ts.isMethodDeclaration(current) || ts.isGetAccessor(current) || ts.isSetAccessor(current) || ts.isPropertyDeclaration(current) || ts.isFunctionDeclaration(current)) {
|
|
1054
|
-
if (current.name && ts.isIdentifier(current.name)) {
|
|
1055
|
-
parts.unshift(current.name.text);
|
|
1056
|
-
}
|
|
1537
|
+
function pushScopeName(node, parts) {
|
|
1538
|
+
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
1539
|
+
parts.push(node.name?.text ?? "Anon");
|
|
1540
|
+
} else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
|
|
1541
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
1542
|
+
parts.push(node.name.text);
|
|
1057
1543
|
}
|
|
1058
|
-
current = current.parent;
|
|
1059
1544
|
}
|
|
1060
|
-
return parts.join(".");
|
|
1061
1545
|
}
|
|
1062
1546
|
function parseSymbols(opts) {
|
|
1063
1547
|
const { file, content, lang } = opts;
|
|
@@ -1068,41 +1552,39 @@ function parseSymbols(opts) {
|
|
|
1068
1552
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
1069
1553
|
}
|
|
1070
1554
|
const symbols = [];
|
|
1071
|
-
|
|
1555
|
+
const refs = [];
|
|
1556
|
+
const printer = ts.createPrinter({});
|
|
1557
|
+
function visit(node, funcDepth, scopeParts) {
|
|
1072
1558
|
const kind = kindOf(node);
|
|
1073
1559
|
if (kind) {
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1560
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
1561
|
+
} else {
|
|
1562
|
+
const nameNode = node.name;
|
|
1563
|
+
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
const name = nameNode.text;
|
|
1567
|
+
const pos2 = nameNode.getStart(sourceFile);
|
|
1568
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
1569
|
+
const scope = scopeParts.join(".");
|
|
1570
|
+
const signature = getSignature(printer, node, sourceFile);
|
|
1571
|
+
const docComment = getJsDoc(node, sourceFile);
|
|
1572
|
+
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
1573
|
+
symbols.push({
|
|
1574
|
+
id: 0,
|
|
1575
|
+
lang,
|
|
1576
|
+
kind,
|
|
1577
|
+
name,
|
|
1578
|
+
file,
|
|
1579
|
+
line: line2 + 1,
|
|
1580
|
+
col: character,
|
|
1581
|
+
signature,
|
|
1582
|
+
docComment,
|
|
1583
|
+
scope,
|
|
1584
|
+
text
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1096
1587
|
}
|
|
1097
|
-
ts.forEachChild(node, visit);
|
|
1098
|
-
}
|
|
1099
|
-
visit(sourceFile);
|
|
1100
|
-
const refs = extractRefs(sourceFile);
|
|
1101
|
-
return { file, lang, symbols, refs, mtimeMs: Date.now() };
|
|
1102
|
-
}
|
|
1103
|
-
function extractRefs(sourceFile) {
|
|
1104
|
-
const refs = [];
|
|
1105
|
-
function visit(node) {
|
|
1106
1588
|
const pos = node.getStart(sourceFile);
|
|
1107
1589
|
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
1108
1590
|
const lineNum = line + 1;
|
|
@@ -1127,10 +1609,14 @@ function extractRefs(sourceFile) {
|
|
|
1127
1609
|
const moduleName = getModuleName(node);
|
|
1128
1610
|
if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
|
|
1129
1611
|
}
|
|
1130
|
-
|
|
1612
|
+
const scopeIdx = scopeParts.length;
|
|
1613
|
+
pushScopeName(node, scopeParts);
|
|
1614
|
+
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
1615
|
+
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
1616
|
+
scopeParts.length = scopeIdx;
|
|
1131
1617
|
}
|
|
1132
|
-
visit(sourceFile);
|
|
1133
|
-
return deduplicateRefs(refs);
|
|
1618
|
+
visit(sourceFile, 0, []);
|
|
1619
|
+
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
1134
1620
|
}
|
|
1135
1621
|
function getTypeName(name) {
|
|
1136
1622
|
if (ts.isIdentifier(name)) return name.text;
|
|
@@ -2038,9 +2524,9 @@ function parseSymbols5(opts) {
|
|
|
2038
2524
|
function regexParse2(opts) {
|
|
2039
2525
|
const { file, content, lang } = opts;
|
|
2040
2526
|
const symbols = [];
|
|
2041
|
-
const
|
|
2042
|
-
const isPackageJson =
|
|
2043
|
-
const isTsconfig =
|
|
2527
|
+
const basename3 = path6.basename(file).toLowerCase();
|
|
2528
|
+
const isPackageJson = basename3 === "package.json";
|
|
2529
|
+
const isTsconfig = basename3 === "tsconfig.json" || basename3 === "tsconfig.build.json";
|
|
2044
2530
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
2045
2531
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
2046
2532
|
const lines = content.split("\n");
|
|
@@ -2435,16 +2921,14 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
2435
2921
|
|
|
2436
2922
|
// src/codebase-index/indexer.ts
|
|
2437
2923
|
var YIELD_EVERY_N = 50;
|
|
2438
|
-
var PARALLEL_BATCH =
|
|
2924
|
+
var PARALLEL_BATCH = Math.min(availableParallelism() * 4, 40);
|
|
2439
2925
|
function yieldEventLoop() {
|
|
2440
2926
|
return new Promise((resolve2) => setImmediate(resolve2));
|
|
2441
2927
|
}
|
|
2442
2928
|
function throwIfAborted(signal) {
|
|
2443
2929
|
if (!signal?.aborted) return;
|
|
2444
2930
|
if (signal.reason instanceof Error) throw signal.reason;
|
|
2445
|
-
throw new Error(
|
|
2446
|
-
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
2447
|
-
);
|
|
2931
|
+
throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
|
|
2448
2932
|
}
|
|
2449
2933
|
function isAbortError(err) {
|
|
2450
2934
|
return err instanceof DOMException && err.name === "AbortError";
|
|
@@ -2460,8 +2944,20 @@ var DEFAULT_IGNORE = [
|
|
|
2460
2944
|
"__snapshots__",
|
|
2461
2945
|
".nyc_output"
|
|
2462
2946
|
];
|
|
2947
|
+
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
2948
|
+
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
2949
|
+
function isWithinProject(projectRoot, file) {
|
|
2950
|
+
const rel = path8.relative(projectRoot, file);
|
|
2951
|
+
return rel !== "" && !rel.startsWith(`..${path8.sep}`) && rel !== ".." && !path8.isAbsolute(rel);
|
|
2952
|
+
}
|
|
2953
|
+
function isMissingPathError(err) {
|
|
2954
|
+
const code = err?.code;
|
|
2955
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
2956
|
+
}
|
|
2463
2957
|
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
2464
2958
|
const results = [];
|
|
2959
|
+
const errors = [];
|
|
2960
|
+
let complete = true;
|
|
2465
2961
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
2466
2962
|
const globs = [
|
|
2467
2963
|
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
@@ -2485,7 +2981,9 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2485
2981
|
let entries;
|
|
2486
2982
|
try {
|
|
2487
2983
|
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
2488
|
-
} catch {
|
|
2984
|
+
} catch (err) {
|
|
2985
|
+
complete = false;
|
|
2986
|
+
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2489
2987
|
return;
|
|
2490
2988
|
}
|
|
2491
2989
|
dirCount++;
|
|
@@ -2497,8 +2995,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2497
2995
|
if (isGitIgnored(rel, true)) continue;
|
|
2498
2996
|
await walk(full);
|
|
2499
2997
|
} else if (e.isFile()) {
|
|
2500
|
-
if (isGitIgnored(rel, false)) continue;
|
|
2501
|
-
const ext = path8.extname(e.name);
|
|
2998
|
+
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
2999
|
+
const ext = path8.extname(e.name).toLowerCase();
|
|
2502
3000
|
for (const { ext: extName, pat } of globs) {
|
|
2503
3001
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
2504
3002
|
results.push(full);
|
|
@@ -2509,7 +3007,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2509
3007
|
}
|
|
2510
3008
|
};
|
|
2511
3009
|
await walk(projectRoot);
|
|
2512
|
-
return results;
|
|
3010
|
+
return { files: results, complete, errors };
|
|
2513
3011
|
}
|
|
2514
3012
|
async function parseFile(file, content, lang) {
|
|
2515
3013
|
switch (lang) {
|
|
@@ -2532,6 +3030,26 @@ async function parseFile(file, content, lang) {
|
|
|
2532
3030
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2533
3031
|
}
|
|
2534
3032
|
}
|
|
3033
|
+
function assignRefsToSymbols2(refs, symbols) {
|
|
3034
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
3035
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
3036
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3037
|
+
const assigned = [];
|
|
3038
|
+
for (const ref of refs) {
|
|
3039
|
+
let owner;
|
|
3040
|
+
for (const symbol of ordered) {
|
|
3041
|
+
if (symbol.line > ref.line) break;
|
|
3042
|
+
owner = symbol;
|
|
3043
|
+
}
|
|
3044
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
3045
|
+
if (!owner || owner.id <= 0) continue;
|
|
3046
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}`;
|
|
3047
|
+
if (seen.has(key)) continue;
|
|
3048
|
+
seen.add(key);
|
|
3049
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
3050
|
+
}
|
|
3051
|
+
return assigned;
|
|
3052
|
+
}
|
|
2535
3053
|
async function runIndexer(_ctx, opts) {
|
|
2536
3054
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
2537
3055
|
try {
|
|
@@ -2544,7 +3062,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
2544
3062
|
}
|
|
2545
3063
|
}
|
|
2546
3064
|
async function runIndexerWithStore(store, opts) {
|
|
2547
|
-
const { projectRoot,
|
|
3065
|
+
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
3066
|
+
const relationGraphVersion = "2";
|
|
3067
|
+
const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
|
|
2548
3068
|
const startMs = Date.now();
|
|
2549
3069
|
const errors = [];
|
|
2550
3070
|
const langStats = {};
|
|
@@ -2553,10 +3073,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2553
3073
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
2554
3074
|
let files;
|
|
2555
3075
|
let discoveredFiles = null;
|
|
3076
|
+
let discoveryComplete = true;
|
|
2556
3077
|
if (opts.files && opts.files.length > 0) {
|
|
2557
|
-
files = opts.files.map((f) => path8.resolve(projectRoot, f)).filter((f) =>
|
|
3078
|
+
files = opts.files.map((f) => path8.resolve(projectRoot, f)).filter((f) => {
|
|
3079
|
+
if (!isWithinProject(projectRoot, f)) return false;
|
|
3080
|
+
const rel = path8.relative(projectRoot, f).replace(/\\/g, "/");
|
|
3081
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path8.basename(f)) && !isGitIgnored(rel, false);
|
|
3082
|
+
});
|
|
2558
3083
|
} else {
|
|
2559
|
-
|
|
3084
|
+
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
3085
|
+
files = discovery.files;
|
|
3086
|
+
errors.push(...discovery.errors);
|
|
3087
|
+
discoveryComplete = discovery.complete;
|
|
2560
3088
|
discoveredFiles = new Set(files);
|
|
2561
3089
|
}
|
|
2562
3090
|
if (langs && langs.length > 0) {
|
|
@@ -2571,46 +3099,79 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2571
3099
|
if (!force) {
|
|
2572
3100
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
2573
3101
|
}
|
|
3102
|
+
let filesSinceLastYield = 0;
|
|
2574
3103
|
for (let batchStart = 0; batchStart < files.length; batchStart += PARALLEL_BATCH) {
|
|
2575
3104
|
const batchEnd = Math.min(batchStart + PARALLEL_BATCH, files.length);
|
|
2576
3105
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
2577
3106
|
opts.onProgress?.(batchEnd, files.length);
|
|
2578
|
-
|
|
3107
|
+
filesSinceLastYield += batchFiles.length;
|
|
3108
|
+
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
3109
|
+
filesSinceLastYield = 0;
|
|
2579
3110
|
await yieldEventLoop();
|
|
2580
3111
|
throwIfAborted(signal);
|
|
2581
3112
|
}
|
|
2582
3113
|
const statOpts = signal ? { signal } : {};
|
|
2583
3114
|
const statReadParse = await Promise.allSettled(
|
|
2584
|
-
batchFiles.map(
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
if (
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
3115
|
+
batchFiles.map(
|
|
3116
|
+
async (file) => {
|
|
3117
|
+
let stat2;
|
|
3118
|
+
try {
|
|
3119
|
+
stat2 = await fs7.stat(file, statOpts);
|
|
3120
|
+
} catch (e) {
|
|
3121
|
+
if (isAbortError(e)) throw e;
|
|
3122
|
+
return {
|
|
3123
|
+
file,
|
|
3124
|
+
stat: null,
|
|
3125
|
+
lang: "",
|
|
3126
|
+
parsed: null,
|
|
3127
|
+
error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
|
|
3128
|
+
missing: isMissingPathError(e)
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
if (!stat2.isFile()) return { file, stat: stat2, lang: "", parsed: null };
|
|
3132
|
+
const lang = detectLang(file);
|
|
3133
|
+
if (!lang) return { file, stat: stat2, lang: "", parsed: null };
|
|
3134
|
+
if (stat2.size > MAX_INDEX_FILE_BYTES) {
|
|
3135
|
+
return {
|
|
3136
|
+
file,
|
|
3137
|
+
stat: stat2,
|
|
3138
|
+
lang,
|
|
3139
|
+
parsed: null,
|
|
3140
|
+
error: `file too large (${stat2.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
const meta = existingMeta.get(file);
|
|
3144
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat2.mtimeMs)) {
|
|
3145
|
+
return { file, stat: stat2, lang, parsed: null, skippedMeta: meta };
|
|
3146
|
+
}
|
|
3147
|
+
let content;
|
|
3148
|
+
try {
|
|
3149
|
+
content = await fs7.readFile(file, { encoding: "utf8", signal });
|
|
3150
|
+
} catch (e) {
|
|
3151
|
+
if (isAbortError(e)) throw e;
|
|
3152
|
+
return {
|
|
3153
|
+
file,
|
|
3154
|
+
stat: stat2,
|
|
3155
|
+
lang,
|
|
3156
|
+
parsed: null,
|
|
3157
|
+
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
let parsed;
|
|
3161
|
+
try {
|
|
3162
|
+
parsed = await parseFile(file, content, lang);
|
|
3163
|
+
} catch (e) {
|
|
3164
|
+
return {
|
|
3165
|
+
file,
|
|
3166
|
+
stat: stat2,
|
|
3167
|
+
lang,
|
|
3168
|
+
parsed: null,
|
|
3169
|
+
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
return { file, stat: stat2, lang, parsed, content };
|
|
2611
3173
|
}
|
|
2612
|
-
|
|
2613
|
-
})
|
|
3174
|
+
)
|
|
2614
3175
|
);
|
|
2615
3176
|
const batchEntries = [];
|
|
2616
3177
|
const deleteForFiles = [];
|
|
@@ -2625,8 +3186,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2625
3186
|
}
|
|
2626
3187
|
const result = settled.value;
|
|
2627
3188
|
if (result.error) {
|
|
2628
|
-
if (result.
|
|
2629
|
-
|
|
3189
|
+
if (result.missing) store.deleteFile(file);
|
|
3190
|
+
errors.push(`${file}: ${result.error}`);
|
|
2630
3191
|
continue;
|
|
2631
3192
|
}
|
|
2632
3193
|
const { stat: stat2, lang, parsed } = result;
|
|
@@ -2650,6 +3211,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2650
3211
|
continue;
|
|
2651
3212
|
}
|
|
2652
3213
|
if (parsed.symbols.length === 0) {
|
|
3214
|
+
store.deleteRefsForFile(file);
|
|
3215
|
+
store.deleteSymbolsForFile(file);
|
|
2653
3216
|
store.upsertFile({
|
|
2654
3217
|
file,
|
|
2655
3218
|
lang,
|
|
@@ -2660,15 +3223,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2660
3223
|
filesIndexed++;
|
|
2661
3224
|
continue;
|
|
2662
3225
|
}
|
|
2663
|
-
const refs = [];
|
|
2664
|
-
if (parsed.refs && parsed.refs.length > 0) {
|
|
2665
|
-
for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
|
|
2666
|
-
}
|
|
2667
3226
|
batchEntries.push({
|
|
2668
3227
|
file,
|
|
2669
3228
|
lang,
|
|
2670
3229
|
symbols: parsed.symbols,
|
|
2671
|
-
refs,
|
|
3230
|
+
refs: parsed.refs ?? [],
|
|
2672
3231
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
2673
3232
|
symbolCount: parsed.symbols.length
|
|
2674
3233
|
});
|
|
@@ -2676,34 +3235,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2676
3235
|
}
|
|
2677
3236
|
if (batchEntries.length > 0) {
|
|
2678
3237
|
try {
|
|
2679
|
-
|
|
2680
|
-
let cursor = 0;
|
|
3238
|
+
store.commitBatch(batchEntries, { deleteForFiles });
|
|
2681
3239
|
for (const entry of batchEntries) {
|
|
2682
3240
|
const count = entry.symbols.length;
|
|
2683
|
-
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
2684
|
-
cursor += count;
|
|
2685
3241
|
symbolsIndexed += count;
|
|
2686
3242
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
2687
3243
|
filesIndexed++;
|
|
2688
|
-
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
2689
|
-
const refsByLine = /* @__PURE__ */ new Map();
|
|
2690
|
-
for (let i = 0; i < symbolsWithIds.length; i++) {
|
|
2691
|
-
const sym = symbolsWithIds[i];
|
|
2692
|
-
let arr = refsByLine.get(sym.line);
|
|
2693
|
-
if (!arr) {
|
|
2694
|
-
arr = [];
|
|
2695
|
-
refsByLine.set(sym.line, arr);
|
|
2696
|
-
}
|
|
2697
|
-
arr.push(i);
|
|
2698
|
-
}
|
|
2699
|
-
for (const ref of entry.refs) {
|
|
2700
|
-
const indices = refsByLine.get(ref.line);
|
|
2701
|
-
if (indices && indices.length > 0) {
|
|
2702
|
-
const idx = indices.shift();
|
|
2703
|
-
ref.fromId = symbolsWithIds[idx].id;
|
|
2704
|
-
}
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
3244
|
}
|
|
2708
3245
|
} catch (err) {
|
|
2709
3246
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2717,23 +3254,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2717
3254
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
2718
3255
|
filesIndexed++;
|
|
2719
3256
|
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
2720
|
-
const
|
|
2721
|
-
for (const sym of symbolsWithIds) {
|
|
2722
|
-
let arr = refsByLine.get(sym.line);
|
|
2723
|
-
if (!arr) {
|
|
2724
|
-
arr = [];
|
|
2725
|
-
refsByLine.set(sym.line, arr);
|
|
2726
|
-
}
|
|
2727
|
-
arr.push(sym);
|
|
2728
|
-
}
|
|
2729
|
-
const fallbackBatch = [];
|
|
2730
|
-
for (const ref of entry.refs) {
|
|
2731
|
-
const syms = refsByLine.get(ref.line);
|
|
2732
|
-
if (syms && syms.length > 0) {
|
|
2733
|
-
const sym = syms.shift();
|
|
2734
|
-
fallbackBatch.push({ ...ref, fromId: sym.id });
|
|
2735
|
-
}
|
|
2736
|
-
}
|
|
3257
|
+
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
2737
3258
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
2738
3259
|
}
|
|
2739
3260
|
store.upsertFile({
|
|
@@ -2744,19 +3265,23 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2744
3265
|
lastIndexed: Date.now()
|
|
2745
3266
|
});
|
|
2746
3267
|
} catch (innerErr) {
|
|
2747
|
-
errors.push(
|
|
3268
|
+
errors.push(
|
|
3269
|
+
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
3270
|
+
);
|
|
2748
3271
|
}
|
|
2749
3272
|
}
|
|
2750
3273
|
}
|
|
2751
3274
|
}
|
|
2752
3275
|
}
|
|
2753
|
-
if (discoveredFiles) {
|
|
3276
|
+
if (discoveredFiles && discoveryComplete) {
|
|
2754
3277
|
for (const [file_] of existingMeta) {
|
|
2755
3278
|
if (!discoveredFiles.has(file_)) {
|
|
2756
3279
|
store.deleteFile(file_);
|
|
2757
3280
|
}
|
|
2758
3281
|
}
|
|
2759
3282
|
}
|
|
3283
|
+
store.resolveRefs();
|
|
3284
|
+
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
2760
3285
|
const durationMs = Date.now() - startMs;
|
|
2761
3286
|
store.setLastIndexed(Date.now());
|
|
2762
3287
|
return {
|
|
@@ -2792,7 +3317,7 @@ async function indexService(args, hooks = {}) {
|
|
|
2792
3317
|
});
|
|
2793
3318
|
}
|
|
2794
3319
|
function searchService(args) {
|
|
2795
|
-
const store =
|
|
3320
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
2796
3321
|
try {
|
|
2797
3322
|
return store.searchRanked(
|
|
2798
3323
|
args.query,
|
|
@@ -2805,22 +3330,46 @@ function searchService(args) {
|
|
|
2805
3330
|
args.limit
|
|
2806
3331
|
);
|
|
2807
3332
|
} finally {
|
|
2808
|
-
|
|
3333
|
+
indexStorePool.release(store);
|
|
2809
3334
|
}
|
|
2810
3335
|
}
|
|
2811
3336
|
function statsService(args) {
|
|
2812
|
-
const store =
|
|
3337
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
2813
3338
|
try {
|
|
2814
3339
|
return store.getStats();
|
|
2815
3340
|
} finally {
|
|
2816
|
-
|
|
3341
|
+
indexStorePool.release(store);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
function packageGraphService(args) {
|
|
3345
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
3346
|
+
try {
|
|
3347
|
+
return store.getPackageGraph();
|
|
3348
|
+
} finally {
|
|
3349
|
+
indexStorePool.release(store);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
function fileGraphService(args) {
|
|
3353
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
3354
|
+
try {
|
|
3355
|
+
return store.getFileGraph(args.packageFilter);
|
|
3356
|
+
} finally {
|
|
3357
|
+
indexStorePool.release(store);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
function symbolGraphService(args) {
|
|
3361
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
3362
|
+
try {
|
|
3363
|
+
return store.getSymbolGraph(args.fileFilter);
|
|
3364
|
+
} finally {
|
|
3365
|
+
indexStorePool.release(store);
|
|
2817
3366
|
}
|
|
2818
3367
|
}
|
|
2819
3368
|
|
|
2820
3369
|
// src/codebase-index/background-indexer.ts
|
|
2821
3370
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
2822
3371
|
var DEFAULT_INCREMENTAL_TIMEOUT_MS = 6e4;
|
|
2823
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
3372
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
2824
3373
|
var _ready = false;
|
|
2825
3374
|
var _indexing = false;
|
|
2826
3375
|
var _currentFile = 0;
|
|
@@ -2898,14 +3447,22 @@ function ensureWorker() {
|
|
|
2898
3447
|
if (!entry) return;
|
|
2899
3448
|
pending.delete(msg.id);
|
|
2900
3449
|
if (msg.ok) entry.resolve(msg.result);
|
|
2901
|
-
else
|
|
3450
|
+
else {
|
|
3451
|
+
const error = msg.errorName === "LockError" ? new LockError(msg.error) : new Error(msg.error);
|
|
3452
|
+
if (msg.errorName && msg.errorName !== "Error") {
|
|
3453
|
+
error.name = msg.errorName;
|
|
3454
|
+
}
|
|
3455
|
+
entry.reject(error);
|
|
3456
|
+
}
|
|
2902
3457
|
});
|
|
2903
3458
|
w.on("error", (err) => {
|
|
3459
|
+
if (worker !== w) return;
|
|
2904
3460
|
worker = null;
|
|
2905
3461
|
failAllPending(err);
|
|
2906
3462
|
});
|
|
2907
3463
|
w.on("exit", () => {
|
|
2908
|
-
if (worker
|
|
3464
|
+
if (worker !== w) return;
|
|
3465
|
+
worker = null;
|
|
2909
3466
|
failAllPending(new Error("codebase-index worker exited"));
|
|
2910
3467
|
});
|
|
2911
3468
|
worker = w;
|
|
@@ -2922,14 +3479,28 @@ function terminateWorker(reason) {
|
|
|
2922
3479
|
if (w) void w.terminate().catch(() => {
|
|
2923
3480
|
});
|
|
2924
3481
|
}
|
|
2925
|
-
function shutdownCodebaseIndexHost() {
|
|
3482
|
+
async function shutdownCodebaseIndexHost() {
|
|
2926
3483
|
cancelPendingReindexes();
|
|
2927
|
-
|
|
3484
|
+
indexStorePool.closeAll();
|
|
3485
|
+
const w = worker;
|
|
3486
|
+
worker = null;
|
|
3487
|
+
failAllPending(new Error("codebase-index host shut down"));
|
|
2928
3488
|
workerUnavailable = false;
|
|
3489
|
+
if (w) {
|
|
3490
|
+
try {
|
|
3491
|
+
await w.terminate();
|
|
3492
|
+
} catch {
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
2929
3495
|
}
|
|
2930
3496
|
function callIndexOp(op, args, opts) {
|
|
2931
3497
|
const w = ensureWorker();
|
|
2932
3498
|
if (!w) return callInline(op, args, opts);
|
|
3499
|
+
if (opts.signal?.aborted) {
|
|
3500
|
+
return Promise.reject(
|
|
3501
|
+
opts.signal.reason instanceof Error ? opts.signal.reason : new Error("Indexing cancelled")
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
2933
3504
|
return new Promise((resolve2, reject) => {
|
|
2934
3505
|
const id = nextRpcId++;
|
|
2935
3506
|
const timer = setTimeout(() => {
|
|
@@ -2944,8 +3515,7 @@ function callIndexOp(op, args, opts) {
|
|
|
2944
3515
|
const onAbort = () => {
|
|
2945
3516
|
w.postMessage({ type: "cancel", id });
|
|
2946
3517
|
};
|
|
2947
|
-
|
|
2948
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3518
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2949
3519
|
const cleanup = () => {
|
|
2950
3520
|
clearTimeout(timer);
|
|
2951
3521
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -3133,7 +3703,7 @@ var codebaseIndexTool = {
|
|
|
3133
3703
|
category: "Project",
|
|
3134
3704
|
icon: "index",
|
|
3135
3705
|
description: "Build or incrementally update the project-wide symbol index. This powers fast codebase search and understanding. By default it only processes files that have changed since the last indexing run.",
|
|
3136
|
-
usageHint: "
|
|
3706
|
+
usageHint: "CREATE OR REFRESH THE SEARCH INDEX:\n\n- When `codebase-stats` or `codebase-search` reports no persisted index, call this without arguments, then retry the search.\n- Normal and first-time usage: call without arguments for an incremental build.\n- Use `force: true` only for a corrupt/stale index or an explicitly requested clean rebuild.\n- Use `langs` to restrict to specific languages if you only care about certain parts of the project.\nThis tool is relatively expensive \u2014 do not call it on every turn. Use it when the index is stale or before heavy codebase-search sessions.",
|
|
3137
3707
|
permission: "confirm",
|
|
3138
3708
|
mutating: true,
|
|
3139
3709
|
capabilities: ["fs.write.outside-project"],
|
|
@@ -3189,12 +3759,14 @@ var codebaseSearchTool = {
|
|
|
3189
3759
|
name: "codebase-search",
|
|
3190
3760
|
category: "Project",
|
|
3191
3761
|
icon: "index",
|
|
3192
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback.
|
|
3193
|
-
usageHint: "
|
|
3762
|
+
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback. Prefer this before broad `tree`, `glob`, or `grep` exploration when finding code by name or concept. Set `preferLsp: true` for live precision when the LSP plugin is active (supersedes codebase-lsp-search).",
|
|
3763
|
+
usageHint: "FIRST CHOICE FOR INDEXABLE CODE UNDERSTANDING:\n\n- Call before broad `tree`, `glob`, or `grep` exploration when locating symbols, concepts, definitions, or candidate modules.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\n- If `indexStatus` reports no persisted data, run `codebase-index` and retry.\nUse `grep` afterwards for exact text, regexes, unsupported content, or concrete usage sites.",
|
|
3194
3764
|
permission: "auto",
|
|
3195
3765
|
mutating: false,
|
|
3196
3766
|
capabilities: ["fs.read"],
|
|
3197
|
-
|
|
3767
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
3768
|
+
// worker teardown and structured timeout reporting.
|
|
3769
|
+
timeoutMs: 35e3,
|
|
3198
3770
|
inputSchema: {
|
|
3199
3771
|
type: "object",
|
|
3200
3772
|
properties: {
|
|
@@ -3204,11 +3776,34 @@ var codebaseSearchTool = {
|
|
|
3204
3776
|
},
|
|
3205
3777
|
kind: {
|
|
3206
3778
|
type: "string",
|
|
3207
|
-
|
|
3779
|
+
enum: [
|
|
3780
|
+
"class",
|
|
3781
|
+
"interface",
|
|
3782
|
+
"enum",
|
|
3783
|
+
"type",
|
|
3784
|
+
"function",
|
|
3785
|
+
"method",
|
|
3786
|
+
"var",
|
|
3787
|
+
"const",
|
|
3788
|
+
"let",
|
|
3789
|
+
"property",
|
|
3790
|
+
"parameter",
|
|
3791
|
+
"namespace",
|
|
3792
|
+
"object",
|
|
3793
|
+
"literal",
|
|
3794
|
+
"schema",
|
|
3795
|
+
"struct",
|
|
3796
|
+
"trait",
|
|
3797
|
+
"impl",
|
|
3798
|
+
"static",
|
|
3799
|
+
"mod"
|
|
3800
|
+
],
|
|
3801
|
+
description: "Filter by indexed symbol kind"
|
|
3208
3802
|
},
|
|
3209
3803
|
lang: {
|
|
3210
3804
|
type: "string",
|
|
3211
|
-
|
|
3805
|
+
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
3806
|
+
description: "Filter by indexed language"
|
|
3212
3807
|
},
|
|
3213
3808
|
lspKind: {
|
|
3214
3809
|
type: "integer",
|
|
@@ -3233,20 +3828,12 @@ var codebaseSearchTool = {
|
|
|
3233
3828
|
},
|
|
3234
3829
|
async execute(input, ctx, execOpts) {
|
|
3235
3830
|
const state = getIndexState();
|
|
3236
|
-
if (!state.ready) {
|
|
3237
|
-
return {
|
|
3238
|
-
results: [],
|
|
3239
|
-
total: 0,
|
|
3240
|
-
query: input.query,
|
|
3241
|
-
indexStatus: state.indexing ? `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.` : "Index not yet built. The codebase is being indexed at startup \u2014 search will be available shortly."
|
|
3242
|
-
};
|
|
3243
|
-
}
|
|
3244
|
-
if (state.indexing) {
|
|
3831
|
+
if (state.indexing && !state.ready) {
|
|
3245
3832
|
return {
|
|
3246
3833
|
results: [],
|
|
3247
3834
|
total: 0,
|
|
3248
3835
|
query: input.query,
|
|
3249
|
-
indexStatus: `
|
|
3836
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
3250
3837
|
};
|
|
3251
3838
|
}
|
|
3252
3839
|
if (state.lastError) {
|
|
@@ -3259,21 +3846,37 @@ var codebaseSearchTool = {
|
|
|
3259
3846
|
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
3260
3847
|
};
|
|
3261
3848
|
}
|
|
3262
|
-
const limit = Math.min(input.limit ?? 20, 100);
|
|
3849
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
3263
3850
|
const { results, total } = await searchCodebaseIndex(
|
|
3264
3851
|
{
|
|
3265
3852
|
projectRoot: ctx.projectRoot,
|
|
3266
3853
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
3267
3854
|
query: input.query,
|
|
3268
|
-
kind: input.kind,
|
|
3269
|
-
lang: input.lang,
|
|
3855
|
+
kind: input.kind?.toLowerCase(),
|
|
3856
|
+
lang: input.lang?.toLowerCase(),
|
|
3270
3857
|
file: input.file,
|
|
3271
3858
|
lspKind: input.lspKind,
|
|
3272
3859
|
limit
|
|
3273
3860
|
},
|
|
3274
3861
|
{ signal: execOpts?.signal }
|
|
3275
3862
|
);
|
|
3276
|
-
|
|
3863
|
+
let hasPersistedIndex = state.ready || total > 0;
|
|
3864
|
+
if (!hasPersistedIndex) {
|
|
3865
|
+
try {
|
|
3866
|
+
const stats = await codebaseIndexStats(
|
|
3867
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
3868
|
+
{ signal: execOpts?.signal }
|
|
3869
|
+
);
|
|
3870
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
3871
|
+
} catch {
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
return {
|
|
3875
|
+
results,
|
|
3876
|
+
total,
|
|
3877
|
+
query: input.query,
|
|
3878
|
+
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
3879
|
+
};
|
|
3277
3880
|
}
|
|
3278
3881
|
};
|
|
3279
3882
|
|
|
@@ -3282,12 +3885,15 @@ var codebaseStatsTool = {
|
|
|
3282
3885
|
name: "codebase-stats",
|
|
3283
3886
|
category: "Project",
|
|
3284
3887
|
icon: "index",
|
|
3285
|
-
description: "
|
|
3286
|
-
usageHint: "
|
|
3888
|
+
description: "Check whether a persisted codebase index exists and report its health and statistics (symbols, files, language/kind breakdown, size, last update).",
|
|
3889
|
+
usageHint: "CHECK ONCE BEFORE BROAD CODE EXPLORATION:\n\n- Call before broad `tree`, `glob`, or `grep` exploration to see whether index-backed search is available.\n- If it reports no persisted data, run `codebase-index`, then use `codebase-search`.\n- No arguments required.\nLightweight and safe to call frequently.",
|
|
3287
3890
|
permission: "auto",
|
|
3288
3891
|
mutating: false,
|
|
3289
3892
|
capabilities: ["fs.read"],
|
|
3290
|
-
|
|
3893
|
+
// The index host has its own 30s read watchdog. Keep the outer tool budget
|
|
3894
|
+
// longer so the host can return a structured timeout instead of being
|
|
3895
|
+
// pre-empted by the generic tool executor.
|
|
3896
|
+
timeoutMs: 35e3,
|
|
3291
3897
|
inputSchema: {
|
|
3292
3898
|
type: "object",
|
|
3293
3899
|
properties: {},
|
|
@@ -3295,7 +3901,8 @@ var codebaseStatsTool = {
|
|
|
3295
3901
|
},
|
|
3296
3902
|
async execute(_input, ctx, execOpts) {
|
|
3297
3903
|
const idxState = getIndexState();
|
|
3298
|
-
|
|
3904
|
+
const indexPath = resolveIndexDir(ctx.projectRoot, codebaseIndexDirOverride(ctx));
|
|
3905
|
+
if (idxState.indexing) {
|
|
3299
3906
|
return {
|
|
3300
3907
|
totalSymbols: 0,
|
|
3301
3908
|
totalFiles: 0,
|
|
@@ -3303,22 +3910,47 @@ var codebaseStatsTool = {
|
|
|
3303
3910
|
byKind: {},
|
|
3304
3911
|
lastIndexed: null,
|
|
3305
3912
|
sizeBytes: 0,
|
|
3306
|
-
indexPath
|
|
3913
|
+
indexPath,
|
|
3307
3914
|
version: SCHEMA_VERSION,
|
|
3308
|
-
|
|
3915
|
+
statsAvailable: false,
|
|
3916
|
+
indexing: {
|
|
3917
|
+
currentFile: idxState.currentFile,
|
|
3918
|
+
totalFiles: idxState.totalFiles
|
|
3919
|
+
},
|
|
3920
|
+
indexStatus: idxState.ready ? `Index refresh in progress (${idxState.currentFile}/${idxState.totalFiles} files). The persisted snapshot remains available to codebase-search; retry stats after refresh.` : `Startup indexing in progress (${idxState.currentFile}/${idxState.totalFiles} files). Do not start another index; retry stats or codebase-search after completion.`
|
|
3309
3921
|
};
|
|
3310
3922
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3923
|
+
let stats;
|
|
3924
|
+
try {
|
|
3925
|
+
stats = await codebaseIndexStats(
|
|
3926
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
3927
|
+
{ signal: execOpts?.signal }
|
|
3928
|
+
);
|
|
3929
|
+
} catch (err) {
|
|
3930
|
+
if (!(err instanceof IndexTimeoutError) && err?.name !== "IndexTimeoutError") {
|
|
3931
|
+
throw err;
|
|
3932
|
+
}
|
|
3316
3933
|
return {
|
|
3317
|
-
|
|
3318
|
-
|
|
3934
|
+
totalSymbols: 0,
|
|
3935
|
+
totalFiles: 0,
|
|
3936
|
+
byLang: {},
|
|
3937
|
+
byKind: {},
|
|
3938
|
+
lastIndexed: null,
|
|
3939
|
+
sizeBytes: 0,
|
|
3940
|
+
indexPath,
|
|
3941
|
+
version: SCHEMA_VERSION,
|
|
3942
|
+
statsAvailable: false,
|
|
3943
|
+
indexStatus: "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback."
|
|
3319
3944
|
};
|
|
3320
3945
|
}
|
|
3321
3946
|
const circuit = idxState.circuit;
|
|
3947
|
+
const hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
3948
|
+
let indexStatus;
|
|
3949
|
+
if (circuit.state === "open") {
|
|
3950
|
+
indexStatus = `${hasPersistedIndex ? "Indexing" : "No persisted index data found, and indexing"} is paused after repeated failures (last: ${circuit.lastFailure ?? "unknown"}); auto-retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s, or run /codebase-reindex. ` + (hasPersistedIndex ? "Stats reflect the last successful build." : "Build the index after recovery.");
|
|
3951
|
+
} else if (!hasPersistedIndex) {
|
|
3952
|
+
indexStatus = "No persisted index data found. Run codebase-index to build it before broad code exploration.";
|
|
3953
|
+
}
|
|
3322
3954
|
return {
|
|
3323
3955
|
totalSymbols: stats.totalSymbols,
|
|
3324
3956
|
totalFiles: stats.totalFiles,
|
|
@@ -3328,9 +3960,8 @@ var codebaseStatsTool = {
|
|
|
3328
3960
|
sizeBytes: stats.sizeBytes,
|
|
3329
3961
|
indexPath: stats.indexPath,
|
|
3330
3962
|
version: stats.version,
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
} : {}
|
|
3963
|
+
statsAvailable: true,
|
|
3964
|
+
...indexStatus ? { indexStatus } : {}
|
|
3334
3965
|
};
|
|
3335
3966
|
}
|
|
3336
3967
|
};
|
|
@@ -3349,6 +3980,7 @@ export {
|
|
|
3349
3980
|
codebaseSearchTool,
|
|
3350
3981
|
codebaseStatsTool,
|
|
3351
3982
|
enqueueReindex,
|
|
3983
|
+
fileGraphService,
|
|
3352
3984
|
getIndexState,
|
|
3353
3985
|
indexCircuitBreaker,
|
|
3354
3986
|
internalKindToLspKind,
|
|
@@ -3357,12 +3989,14 @@ export {
|
|
|
3357
3989
|
isIndexing,
|
|
3358
3990
|
lspKindToInternalKind,
|
|
3359
3991
|
onIndexStateChange,
|
|
3992
|
+
packageGraphService,
|
|
3360
3993
|
resetIndexCircuitBreaker,
|
|
3361
3994
|
resolveIndexDir,
|
|
3362
3995
|
runIndexer,
|
|
3363
3996
|
runStartupIndex,
|
|
3364
3997
|
searchCodebaseIndex,
|
|
3365
3998
|
shutdownCodebaseIndexHost,
|
|
3999
|
+
symbolGraphService,
|
|
3366
4000
|
tokenise
|
|
3367
4001
|
};
|
|
3368
4002
|
//# sourceMappingURL=index.js.map
|