@wrongstack/tools 0.286.0 → 0.289.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/README.md +2 -2
- package/dist/_edit-match.d.ts.map +1 -1
- 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/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/batch-tool-use.d.ts +1 -1
- package/dist/batch-tool-use.d.ts.map +1 -1
- package/dist/batch-tool-use.js +26 -8
- package/dist/batch-tool-use.js.map +2 -2
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/types.d.ts +1 -1
- package/dist/browser/types.d.ts.map +1 -1
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +2107 -864
- package/dist/builtin.js.map +4 -4
- 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/go-parser.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 +934 -283
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/py-parser.d.ts.map +1 -1
- package/dist/codebase-index/rs-parser.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 +793 -241
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer.d.ts +27 -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/edit.d.ts.map +1 -1
- package/dist/edit.js +36 -12
- package/dist/edit.js.map +3 -3
- 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 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2535 -865
- package/dist/index.js.map +4 -4
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +16 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +795 -532
- package/dist/kanban.js.map +4 -4
- 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/next-steps.d.ts +6 -2
- package/dist/next-steps.d.ts.map +1 -1
- package/dist/next-steps.js +3 -3
- package/dist/next-steps.js.map +2 -2
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +2104 -861
- package/dist/pack.js.map +4 -4
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +158 -4
- package/dist/plan.js.map +4 -4
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +47 -0
- package/dist/session-kanban.d.ts.map +1 -0
- package/dist/session-kanban.js +626 -0
- package/dist/session-kanban.js.map +7 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +167 -4
- package/dist/task.js.map +4 -4
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +159 -7
- package/dist/todo.js.map +4 -4
- package/dist/tool-icon-map.d.ts.map +1 -1
- package/dist/tool-icons.d.ts.map +1 -1
- package/dist/tool-icons.js +7 -1
- package/dist/tool-icons.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 +11 -3
|
@@ -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
|
}
|
|
@@ -299,7 +323,7 @@ function sleepSync(ms) {
|
|
|
299
323
|
} catch {
|
|
300
324
|
}
|
|
301
325
|
}
|
|
302
|
-
var IndexStore = class {
|
|
326
|
+
var IndexStore = class _IndexStore {
|
|
303
327
|
db;
|
|
304
328
|
/** Absolute path to this project's index directory. */
|
|
305
329
|
indexDir;
|
|
@@ -351,10 +375,7 @@ var IndexStore = class {
|
|
|
351
375
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
352
376
|
throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
|
|
353
377
|
}
|
|
354
|
-
const delay = Math.min(
|
|
355
|
-
LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
|
|
356
|
-
LOCK_RETRY_MAX_DELAY_MS
|
|
357
|
-
);
|
|
378
|
+
const delay = Math.min(LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt, LOCK_RETRY_MAX_DELAY_MS);
|
|
358
379
|
sleepSync(delay);
|
|
359
380
|
}
|
|
360
381
|
}
|
|
@@ -436,8 +457,24 @@ var IndexStore = class {
|
|
|
436
457
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
|
|
437
458
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
|
|
438
459
|
try {
|
|
439
|
-
this.db.exec(
|
|
460
|
+
this.db.exec(
|
|
461
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')"
|
|
462
|
+
);
|
|
440
463
|
this.ftsAvailable = true;
|
|
464
|
+
const symbolCount = Number(
|
|
465
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
466
|
+
);
|
|
467
|
+
const ftsCount = Number(
|
|
468
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
469
|
+
);
|
|
470
|
+
if (symbolCount !== ftsCount) {
|
|
471
|
+
this.db.exec("DELETE FROM symbols_fts");
|
|
472
|
+
const insert = this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)");
|
|
473
|
+
const rows = this.db.prepare("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
474
|
+
for (const row of rows) {
|
|
475
|
+
insert.run(row.id, buildIndexableText(row.name, row.signature, row.doc_comment));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
441
478
|
} catch {
|
|
442
479
|
this.ftsAvailable = false;
|
|
443
480
|
}
|
|
@@ -495,7 +532,9 @@ var IndexStore = class {
|
|
|
495
532
|
deleteSymbolsForFile(file) {
|
|
496
533
|
this.runWithRetry(() => {
|
|
497
534
|
if (this.ftsAvailable) {
|
|
498
|
-
this.stmt(
|
|
535
|
+
this.stmt(
|
|
536
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
537
|
+
).run(file);
|
|
499
538
|
}
|
|
500
539
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
501
540
|
});
|
|
@@ -507,9 +546,21 @@ var IndexStore = class {
|
|
|
507
546
|
*/
|
|
508
547
|
deleteFile(file) {
|
|
509
548
|
this.runWithRetry(() => {
|
|
510
|
-
this.
|
|
511
|
-
|
|
512
|
-
|
|
549
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
550
|
+
try {
|
|
551
|
+
if (this.ftsAvailable) {
|
|
552
|
+
this.db.prepare(
|
|
553
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
554
|
+
).run(file);
|
|
555
|
+
}
|
|
556
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
|
|
557
|
+
this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
558
|
+
this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
|
|
559
|
+
this.db.exec("COMMIT");
|
|
560
|
+
} catch (err) {
|
|
561
|
+
this.db.exec("ROLLBACK");
|
|
562
|
+
throw err;
|
|
563
|
+
}
|
|
513
564
|
});
|
|
514
565
|
}
|
|
515
566
|
// ─── File metadata ──────────────────────────────────────────────────────────
|
|
@@ -532,14 +583,22 @@ var IndexStore = class {
|
|
|
532
583
|
).all(file);
|
|
533
584
|
if (!rows.length) return null;
|
|
534
585
|
const r = expectDefined(rows[0]);
|
|
535
|
-
return {
|
|
586
|
+
return {
|
|
587
|
+
file: r.file,
|
|
588
|
+
lang: r.lang,
|
|
589
|
+
mtimeMs: r.mtime_ms,
|
|
590
|
+
symbolCount: r.symbol_count,
|
|
591
|
+
lastIndexed: r.last_indexed
|
|
592
|
+
};
|
|
536
593
|
}
|
|
537
594
|
getAllFileMetas() {
|
|
538
|
-
return this.db.prepare(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
595
|
+
return this.db.prepare("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
596
|
+
file: r.file,
|
|
597
|
+
lang: r.lang,
|
|
598
|
+
mtimeMs: r.mtime_ms,
|
|
599
|
+
symbolCount: r.symbol_count,
|
|
600
|
+
lastIndexed: r.last_indexed
|
|
601
|
+
}));
|
|
543
602
|
}
|
|
544
603
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
545
604
|
search(query, filter) {
|
|
@@ -563,8 +622,8 @@ var IndexStore = class {
|
|
|
563
622
|
values.push(filter.lang);
|
|
564
623
|
}
|
|
565
624
|
if (filter?.file) {
|
|
566
|
-
conditions.push("file LIKE ?");
|
|
567
|
-
values.push(`%${filter.file}%`);
|
|
625
|
+
conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
626
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
568
627
|
}
|
|
569
628
|
if (query.trim()) {
|
|
570
629
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
@@ -602,9 +661,11 @@ var IndexStore = class {
|
|
|
602
661
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
603
662
|
*/
|
|
604
663
|
searchRanked(query, filter, limit) {
|
|
664
|
+
const rawLimit = Number.isFinite(limit) ? Math.trunc(limit) : 20;
|
|
665
|
+
const safeLimit = Math.max(1, Math.min(rawLimit, 100));
|
|
605
666
|
const tokens = tokenise(query);
|
|
606
667
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
607
|
-
return this.searchRankedFallback(query, filter,
|
|
668
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
608
669
|
}
|
|
609
670
|
let effectiveKind = filter?.kind;
|
|
610
671
|
if (filter?.lspKind !== void 0) {
|
|
@@ -624,11 +685,13 @@ var IndexStore = class {
|
|
|
624
685
|
values.push(filter.lang);
|
|
625
686
|
}
|
|
626
687
|
if (filter?.file) {
|
|
627
|
-
conditions.push("s.file LIKE ?");
|
|
628
|
-
values.push(`%${filter.file}%`);
|
|
688
|
+
conditions.push("replace(s.file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
689
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
629
690
|
}
|
|
630
691
|
const where = conditions.join(" AND ");
|
|
631
|
-
const countRows = this.db.prepare(
|
|
692
|
+
const countRows = this.db.prepare(
|
|
693
|
+
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
694
|
+
).all(...values);
|
|
632
695
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
633
696
|
if (total === 0) return { results: [], total: 0 };
|
|
634
697
|
const rows = this.db.prepare(
|
|
@@ -637,9 +700,13 @@ var IndexStore = class {
|
|
|
637
700
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
638
701
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
639
702
|
WHERE ${where}
|
|
640
|
-
ORDER BY
|
|
703
|
+
ORDER BY
|
|
704
|
+
CASE WHEN lower(s.name) = lower(?) THEN 0
|
|
705
|
+
WHEN lower(s.name) LIKE lower(?) ESCAPE '\\' THEN 1
|
|
706
|
+
ELSE 2 END,
|
|
707
|
+
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
641
708
|
LIMIT ?`
|
|
642
|
-
).all(...values,
|
|
709
|
+
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
643
710
|
return {
|
|
644
711
|
results: rows.map((r) => ({
|
|
645
712
|
id: r.id,
|
|
@@ -669,10 +736,28 @@ var IndexStore = class {
|
|
|
669
736
|
}
|
|
670
737
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
671
738
|
const bm25 = buildBm25Index(
|
|
672
|
-
candidates.map((c) => ({
|
|
739
|
+
candidates.map((c) => ({
|
|
740
|
+
id: c.id,
|
|
741
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
742
|
+
}))
|
|
673
743
|
);
|
|
674
744
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
675
|
-
|
|
745
|
+
const q = query.trim().toLowerCase();
|
|
746
|
+
const rank = (id) => {
|
|
747
|
+
const name = candidateById.get(id)?.name.toLowerCase() ?? "";
|
|
748
|
+
if (name === q) return 0;
|
|
749
|
+
if (name.startsWith(q)) return 1;
|
|
750
|
+
return 2;
|
|
751
|
+
};
|
|
752
|
+
scored.sort((a, b) => {
|
|
753
|
+
const rankDiff = rank(a.id) - rank(b.id);
|
|
754
|
+
if (rankDiff !== 0) return rankDiff;
|
|
755
|
+
const scoreDiff = b.score - a.score;
|
|
756
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
757
|
+
const left = expectDefined(candidateById.get(a.id));
|
|
758
|
+
const right = expectDefined(candidateById.get(b.id));
|
|
759
|
+
return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
|
|
760
|
+
});
|
|
676
761
|
const qTokens = tokenise(query);
|
|
677
762
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
678
763
|
const c = expectDefined(candidateById.get(id));
|
|
@@ -681,9 +766,7 @@ var IndexStore = class {
|
|
|
681
766
|
return { results, total: candidates.length };
|
|
682
767
|
}
|
|
683
768
|
getAllIndexable() {
|
|
684
|
-
return this.db.prepare("SELECT id, text FROM symbols").all().map(
|
|
685
|
-
({ id, text }) => ({ id, text })
|
|
686
|
-
);
|
|
769
|
+
return this.db.prepare("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
687
770
|
}
|
|
688
771
|
/**
|
|
689
772
|
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
@@ -699,22 +782,16 @@ var IndexStore = class {
|
|
|
699
782
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
700
783
|
getStats() {
|
|
701
784
|
const sizeBytes = this.sizeBytes();
|
|
702
|
-
const lastRows = this.db.prepare(
|
|
703
|
-
"SELECT value FROM metadata WHERE key = 'last_indexed'"
|
|
704
|
-
).all();
|
|
785
|
+
const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
705
786
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
706
787
|
const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
|
|
707
788
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
708
789
|
const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
|
|
709
790
|
const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
|
|
710
|
-
const langRows = this.db.prepare(
|
|
711
|
-
"SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
|
|
712
|
-
).all();
|
|
791
|
+
const langRows = this.db.prepare("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
|
|
713
792
|
const byLang = {};
|
|
714
793
|
for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
|
|
715
|
-
const kindRows = this.db.prepare(
|
|
716
|
-
"SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
|
|
717
|
-
).all();
|
|
794
|
+
const kindRows = this.db.prepare("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
|
|
718
795
|
const byKind = {};
|
|
719
796
|
for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
|
|
720
797
|
return {
|
|
@@ -730,17 +807,31 @@ var IndexStore = class {
|
|
|
730
807
|
}
|
|
731
808
|
setLastIndexed(ts2) {
|
|
732
809
|
this.runWithRetry(() => {
|
|
733
|
-
this.db.prepare(
|
|
734
|
-
|
|
735
|
-
|
|
810
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
getMetadata(key) {
|
|
814
|
+
const rows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
815
|
+
return rows[0]?.value;
|
|
816
|
+
}
|
|
817
|
+
setMetadata(key, value) {
|
|
818
|
+
this.runWithRetry(() => {
|
|
819
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
736
820
|
});
|
|
737
821
|
}
|
|
738
822
|
clearAll() {
|
|
739
823
|
this.runWithRetry(() => {
|
|
740
|
-
this.db.exec("
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
824
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
825
|
+
try {
|
|
826
|
+
this.db.exec("DELETE FROM refs");
|
|
827
|
+
this.db.exec("DELETE FROM symbols");
|
|
828
|
+
this.db.exec("DELETE FROM files");
|
|
829
|
+
if (this.ftsAvailable) this.db.exec("DELETE FROM symbols_fts");
|
|
830
|
+
this.db.exec("COMMIT");
|
|
831
|
+
} catch (err) {
|
|
832
|
+
this.db.exec("ROLLBACK");
|
|
833
|
+
throw err;
|
|
834
|
+
}
|
|
744
835
|
});
|
|
745
836
|
}
|
|
746
837
|
// ─── Ref CRUD ────────────────────────────────────────────────────────────────
|
|
@@ -831,6 +922,7 @@ var IndexStore = class {
|
|
|
831
922
|
const allInserted = [];
|
|
832
923
|
const refsToInsert = [];
|
|
833
924
|
for (const entry of entries) {
|
|
925
|
+
const insertedForEntry = [];
|
|
834
926
|
for (const s of entry.symbols) {
|
|
835
927
|
const id = nextId++;
|
|
836
928
|
symStmt.run(
|
|
@@ -848,9 +940,11 @@ var IndexStore = class {
|
|
|
848
940
|
s.file
|
|
849
941
|
);
|
|
850
942
|
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
851
|
-
|
|
943
|
+
const inserted = { ...s, id };
|
|
944
|
+
allInserted.push(inserted);
|
|
945
|
+
insertedForEntry.push(inserted);
|
|
852
946
|
}
|
|
853
|
-
|
|
947
|
+
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
854
948
|
}
|
|
855
949
|
if (refsToInsert.length > 0) {
|
|
856
950
|
const refStmt = this.db.prepare(
|
|
@@ -888,9 +982,7 @@ var IndexStore = class {
|
|
|
888
982
|
*/
|
|
889
983
|
deleteRefsForFile(file) {
|
|
890
984
|
this.runWithRetry(() => {
|
|
891
|
-
this.db.prepare(
|
|
892
|
-
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
893
|
-
).run(file);
|
|
985
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
894
986
|
});
|
|
895
987
|
}
|
|
896
988
|
/**
|
|
@@ -931,9 +1023,7 @@ var IndexStore = class {
|
|
|
931
1023
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
932
1024
|
*/
|
|
933
1025
|
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) => ({
|
|
1026
|
+
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
1027
|
id: r.id,
|
|
938
1028
|
fromId: r.from_id,
|
|
939
1029
|
toName: r.to_name,
|
|
@@ -950,6 +1040,325 @@ var IndexStore = class {
|
|
|
950
1040
|
return 0;
|
|
951
1041
|
}
|
|
952
1042
|
}
|
|
1043
|
+
// ─── CodeMap graph aggregation ──────────────────────────────────────────────
|
|
1044
|
+
/**
|
|
1045
|
+
* Derive a monorepo package name from an absolute file path.
|
|
1046
|
+
* Handles both `packages/<name>/...` (workspace) and `src/<name>/...` layouts.
|
|
1047
|
+
* Returns `undefined` for files outside any recognisable package structure.
|
|
1048
|
+
*/
|
|
1049
|
+
static derivePackage(filePath) {
|
|
1050
|
+
const f = filePath.replace(/\\/g, "/");
|
|
1051
|
+
const pkgsIdx = f.indexOf("/packages/");
|
|
1052
|
+
if (pkgsIdx !== -1) {
|
|
1053
|
+
const rest = f.slice(pkgsIdx + "/packages/".length);
|
|
1054
|
+
const seg = rest.split("/")[0];
|
|
1055
|
+
return seg ? `@wrongstack/${seg}` : void 0;
|
|
1056
|
+
}
|
|
1057
|
+
const appsIdx = f.indexOf("/apps/");
|
|
1058
|
+
if (appsIdx !== -1) {
|
|
1059
|
+
const rest = f.slice(appsIdx + "/apps/".length);
|
|
1060
|
+
const seg = rest.split("/")[0];
|
|
1061
|
+
return seg ? `app:${seg}` : void 0;
|
|
1062
|
+
}
|
|
1063
|
+
return void 0;
|
|
1064
|
+
}
|
|
1065
|
+
static packageFromImport(moduleName) {
|
|
1066
|
+
if (!moduleName.startsWith("@wrongstack/")) return void 0;
|
|
1067
|
+
const parts = moduleName.split("/");
|
|
1068
|
+
return parts[1] ? `@wrongstack/${parts[1]}` : void 0;
|
|
1069
|
+
}
|
|
1070
|
+
static resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
1071
|
+
if (!moduleName.startsWith(".")) return void 0;
|
|
1072
|
+
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
1073
|
+
const absolute = path.posix.normalize(
|
|
1074
|
+
path.posix.join(path.posix.dirname(normalizedFrom), moduleName)
|
|
1075
|
+
);
|
|
1076
|
+
const extension = path.posix.extname(absolute);
|
|
1077
|
+
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
1078
|
+
const candidates = [
|
|
1079
|
+
absolute,
|
|
1080
|
+
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
1081
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path.posix.join(absolute, `index${ext}`)),
|
|
1082
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path.posix.join(base, `index${ext}`))
|
|
1083
|
+
];
|
|
1084
|
+
const indexedByPortablePath = new Map(
|
|
1085
|
+
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
1086
|
+
);
|
|
1087
|
+
for (const candidate of candidates) {
|
|
1088
|
+
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
1089
|
+
if (indexed) return indexed;
|
|
1090
|
+
}
|
|
1091
|
+
return void 0;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Package-level graph: each workspace package is a node; edges are derived
|
|
1095
|
+
* from cross-package symbol references (a symbol in package A references a
|
|
1096
|
+
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
1097
|
+
*/
|
|
1098
|
+
getPackageGraph() {
|
|
1099
|
+
const symbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
|
|
1100
|
+
const pkgNodes = /* @__PURE__ */ new Map();
|
|
1101
|
+
const fileToPkg = /* @__PURE__ */ new Map();
|
|
1102
|
+
const symbolToPkg = /* @__PURE__ */ new Map();
|
|
1103
|
+
for (const s of symbols) {
|
|
1104
|
+
const pkg = _IndexStore.derivePackage(s.file) ?? "(root)";
|
|
1105
|
+
fileToPkg.set(s.file, pkg);
|
|
1106
|
+
symbolToPkg.set(s.id, pkg);
|
|
1107
|
+
const node = pkgNodes.get(pkg);
|
|
1108
|
+
if (node) {
|
|
1109
|
+
node.symbolCount = (node.symbolCount ?? 0) + 1;
|
|
1110
|
+
} else {
|
|
1111
|
+
pkgNodes.set(pkg, {
|
|
1112
|
+
id: `pkg:${pkg}`,
|
|
1113
|
+
label: pkg,
|
|
1114
|
+
kind: "package",
|
|
1115
|
+
package: pkg,
|
|
1116
|
+
symbolCount: 1,
|
|
1117
|
+
fileCount: 0
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
const files = this.db.prepare("SELECT DISTINCT file FROM files").all();
|
|
1122
|
+
for (const { file } of files) {
|
|
1123
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
1124
|
+
const node = pkgNodes.get(pkg);
|
|
1125
|
+
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
1126
|
+
}
|
|
1127
|
+
const refRows = this.db.prepare(
|
|
1128
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
1129
|
+
FROM refs r
|
|
1130
|
+
WHERE r.to_id IS NOT NULL`
|
|
1131
|
+
).all();
|
|
1132
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
1133
|
+
for (const r of refRows) {
|
|
1134
|
+
if (r.call_type === "import") continue;
|
|
1135
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
1136
|
+
const toPkg = symbolToPkg.get(r.to_id);
|
|
1137
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
1138
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
1139
|
+
let e = edgeMap.get(key);
|
|
1140
|
+
if (!e) {
|
|
1141
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1142
|
+
edgeMap.set(key, e);
|
|
1143
|
+
}
|
|
1144
|
+
e.weight++;
|
|
1145
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
1146
|
+
}
|
|
1147
|
+
const importRows = this.db.prepare(
|
|
1148
|
+
`SELECT r.from_id, r.to_name
|
|
1149
|
+
FROM refs r
|
|
1150
|
+
WHERE r.call_type = 'import'`
|
|
1151
|
+
).all();
|
|
1152
|
+
for (const r of importRows) {
|
|
1153
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
1154
|
+
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
1155
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
1156
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
1157
|
+
let edge = edgeMap.get(key);
|
|
1158
|
+
if (!edge) {
|
|
1159
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1160
|
+
edgeMap.set(key, edge);
|
|
1161
|
+
}
|
|
1162
|
+
edge.weight++;
|
|
1163
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
1164
|
+
}
|
|
1165
|
+
const edges = [];
|
|
1166
|
+
for (const [key, e] of edgeMap) {
|
|
1167
|
+
const [source, target] = key.split("\0");
|
|
1168
|
+
let bestType = "call";
|
|
1169
|
+
let bestCount = 0;
|
|
1170
|
+
for (const [t, c] of e.types) {
|
|
1171
|
+
if (c > bestCount) {
|
|
1172
|
+
bestType = t;
|
|
1173
|
+
bestCount = c;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
edges.push({
|
|
1177
|
+
source: `pkg:${source}`,
|
|
1178
|
+
target: `pkg:${target}`,
|
|
1179
|
+
weight: e.weight,
|
|
1180
|
+
refType: bestType
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
return { nodes: [...pkgNodes.values()], edges };
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* File-level graph for a single package: each file is a node; edges are
|
|
1187
|
+
* derived from cross-file symbol references within the package.
|
|
1188
|
+
*/
|
|
1189
|
+
getFileGraph(packageFilter) {
|
|
1190
|
+
const allSymbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
|
|
1191
|
+
const pkgSyms = allSymbols.filter(
|
|
1192
|
+
(s) => (_IndexStore.derivePackage(s.file) ?? "(root)") === packageFilter
|
|
1193
|
+
);
|
|
1194
|
+
if (pkgSyms.length === 0) return { nodes: [], edges: [] };
|
|
1195
|
+
const fileNodes = /* @__PURE__ */ new Map();
|
|
1196
|
+
const symToFile = /* @__PURE__ */ new Map();
|
|
1197
|
+
const fileStats = /* @__PURE__ */ new Map();
|
|
1198
|
+
for (const s of allSymbols) {
|
|
1199
|
+
symToFile.set(s.id, s.file);
|
|
1200
|
+
const current = fileStats.get(s.file);
|
|
1201
|
+
fileStats.set(s.file, {
|
|
1202
|
+
count: (current?.count ?? 0) + 1,
|
|
1203
|
+
lang: current?.lang ?? s.lang
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
const localFiles = new Set(pkgSyms.map((s) => s.file));
|
|
1207
|
+
const indexedFiles = new Set(allSymbols.map((s) => s.file));
|
|
1208
|
+
const ensureFileNode = (file) => {
|
|
1209
|
+
if (fileNodes.has(file)) return;
|
|
1210
|
+
const stats = fileStats.get(file);
|
|
1211
|
+
fileNodes.set(file, {
|
|
1212
|
+
id: `file:${file}`,
|
|
1213
|
+
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
1214
|
+
kind: "file",
|
|
1215
|
+
package: _IndexStore.derivePackage(file) ?? "(root)",
|
|
1216
|
+
file,
|
|
1217
|
+
symbolCount: stats?.count ?? 0,
|
|
1218
|
+
lang: stats?.lang,
|
|
1219
|
+
external: !localFiles.has(file)
|
|
1220
|
+
});
|
|
1221
|
+
};
|
|
1222
|
+
for (const file of localFiles) {
|
|
1223
|
+
ensureFileNode(file);
|
|
1224
|
+
}
|
|
1225
|
+
const refRows = this.db.prepare(
|
|
1226
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
1227
|
+
FROM refs r
|
|
1228
|
+
WHERE r.to_id IS NOT NULL`
|
|
1229
|
+
).all();
|
|
1230
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
1231
|
+
for (const r of refRows) {
|
|
1232
|
+
if (r.call_type === "import") continue;
|
|
1233
|
+
const fromFile = symToFile.get(r.from_id);
|
|
1234
|
+
const toFile = symToFile.get(r.to_id);
|
|
1235
|
+
if (!fromFile || !toFile || fromFile === toFile) continue;
|
|
1236
|
+
if (!localFiles.has(fromFile) && !localFiles.has(toFile)) continue;
|
|
1237
|
+
ensureFileNode(fromFile);
|
|
1238
|
+
ensureFileNode(toFile);
|
|
1239
|
+
const key = `${fromFile}\0${toFile}`;
|
|
1240
|
+
let e = edgeMap.get(key);
|
|
1241
|
+
if (!e) {
|
|
1242
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1243
|
+
edgeMap.set(key, e);
|
|
1244
|
+
}
|
|
1245
|
+
e.weight++;
|
|
1246
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
1247
|
+
}
|
|
1248
|
+
const importRows = this.db.prepare(
|
|
1249
|
+
`SELECT r.from_id, r.to_name
|
|
1250
|
+
FROM refs r
|
|
1251
|
+
WHERE r.call_type = 'import'`
|
|
1252
|
+
).all();
|
|
1253
|
+
for (const r of importRows) {
|
|
1254
|
+
const fromFile = symToFile.get(r.from_id);
|
|
1255
|
+
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
1256
|
+
const toFile = _IndexStore.resolveRelativeImport(fromFile, r.to_name, indexedFiles);
|
|
1257
|
+
if (!toFile || fromFile === toFile) continue;
|
|
1258
|
+
ensureFileNode(fromFile);
|
|
1259
|
+
ensureFileNode(toFile);
|
|
1260
|
+
const key = `${fromFile}\0${toFile}`;
|
|
1261
|
+
let edge = edgeMap.get(key);
|
|
1262
|
+
if (!edge) {
|
|
1263
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1264
|
+
edgeMap.set(key, edge);
|
|
1265
|
+
}
|
|
1266
|
+
edge.weight++;
|
|
1267
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
1268
|
+
}
|
|
1269
|
+
const edges = [];
|
|
1270
|
+
for (const [key, e] of edgeMap) {
|
|
1271
|
+
const [source, target] = key.split("\0");
|
|
1272
|
+
let bestType = "call";
|
|
1273
|
+
let bestCount = 0;
|
|
1274
|
+
for (const [t, c] of e.types) {
|
|
1275
|
+
if (c > bestCount) {
|
|
1276
|
+
bestType = t;
|
|
1277
|
+
bestCount = c;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
edges.push({
|
|
1281
|
+
source: `file:${source}`,
|
|
1282
|
+
target: `file:${target}`,
|
|
1283
|
+
weight: e.weight,
|
|
1284
|
+
refType: bestType
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
return { nodes: [...fileNodes.values()], edges };
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Symbol-level graph for a single file: each symbol is a node; edges are
|
|
1291
|
+
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
1292
|
+
*/
|
|
1293
|
+
getSymbolGraph(fileFilter) {
|
|
1294
|
+
const allSymbols = this.db.prepare(
|
|
1295
|
+
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols ORDER BY file, line, id"
|
|
1296
|
+
).all();
|
|
1297
|
+
const syms = allSymbols.filter((symbol) => symbol.file === fileFilter);
|
|
1298
|
+
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
1299
|
+
const symById = new Map(allSymbols.map((symbol) => [symbol.id, symbol]));
|
|
1300
|
+
const relatedIds = new Set(syms.map((symbol) => symbol.id));
|
|
1301
|
+
const toGraphNode = (s) => ({
|
|
1302
|
+
id: `sym:${s.id}`,
|
|
1303
|
+
label: s.name,
|
|
1304
|
+
kind: "symbol",
|
|
1305
|
+
symbolId: s.id,
|
|
1306
|
+
symbolKind: s.kind,
|
|
1307
|
+
file: s.file,
|
|
1308
|
+
package: _IndexStore.derivePackage(s.file) ?? "(root)",
|
|
1309
|
+
lang: s.lang,
|
|
1310
|
+
line: s.line,
|
|
1311
|
+
signature: s.signature,
|
|
1312
|
+
scope: s.scope,
|
|
1313
|
+
external: s.file !== fileFilter
|
|
1314
|
+
});
|
|
1315
|
+
const symIds = new Set(syms.map((s) => s.id));
|
|
1316
|
+
const refRows = this.db.prepare(
|
|
1317
|
+
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
1318
|
+
FROM refs r
|
|
1319
|
+
WHERE r.from_id IN (SELECT id FROM symbols WHERE file = ?)
|
|
1320
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file = ?)`
|
|
1321
|
+
).all(fileFilter, fileFilter);
|
|
1322
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
1323
|
+
for (const r of refRows) {
|
|
1324
|
+
if (r.to_id === null) continue;
|
|
1325
|
+
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
1326
|
+
relatedIds.add(r.from_id);
|
|
1327
|
+
relatedIds.add(r.to_id);
|
|
1328
|
+
const key = `${r.from_id}\0${r.to_id}`;
|
|
1329
|
+
let e = edgeMap.get(key);
|
|
1330
|
+
if (!e) {
|
|
1331
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
1332
|
+
edgeMap.set(key, e);
|
|
1333
|
+
}
|
|
1334
|
+
e.weight++;
|
|
1335
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
1336
|
+
}
|
|
1337
|
+
const edges = [];
|
|
1338
|
+
for (const [key, e] of edgeMap) {
|
|
1339
|
+
const [fromId, toId] = key.split("\0").map(Number);
|
|
1340
|
+
let bestType = "call";
|
|
1341
|
+
let bestCount = 0;
|
|
1342
|
+
for (const [t, c] of e.types) {
|
|
1343
|
+
if (c > bestCount) {
|
|
1344
|
+
bestType = t;
|
|
1345
|
+
bestCount = c;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
edges.push({
|
|
1349
|
+
source: `sym:${fromId}`,
|
|
1350
|
+
target: `sym:${toId}`,
|
|
1351
|
+
weight: e.weight,
|
|
1352
|
+
refType: bestType
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
1356
|
+
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
1357
|
+
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
1358
|
+
return aExternal - bExternal || a.file.localeCompare(b.file) || a.line - b.line || a.id - b.id;
|
|
1359
|
+
}).map(toGraphNode);
|
|
1360
|
+
return { nodes, edges };
|
|
1361
|
+
}
|
|
953
1362
|
close() {
|
|
954
1363
|
this.stmtCache.clear();
|
|
955
1364
|
try {
|
|
@@ -960,14 +1369,14 @@ var IndexStore = class {
|
|
|
960
1369
|
};
|
|
961
1370
|
|
|
962
1371
|
// src/codebase-index/background-indexer.ts
|
|
963
|
-
import * as
|
|
1372
|
+
import * as fs8 from "node:fs";
|
|
964
1373
|
import { fileURLToPath } from "node:url";
|
|
965
1374
|
import { Worker } from "node:worker_threads";
|
|
966
1375
|
|
|
967
1376
|
// src/codebase-index/indexer.ts
|
|
968
1377
|
import { expectDefined as expectDefined5 } from "@wrongstack/core";
|
|
969
|
-
import * as
|
|
970
|
-
import * as
|
|
1378
|
+
import * as fs7 from "node:fs/promises";
|
|
1379
|
+
import * as path8 from "node:path";
|
|
971
1380
|
import { compileGlob as compileGlob2 } from "@wrongstack/core";
|
|
972
1381
|
|
|
973
1382
|
// src/codebase-index/ts-parser.ts
|
|
@@ -1044,6 +1453,14 @@ function getJsDoc(node, sourceFile) {
|
|
|
1044
1453
|
}
|
|
1045
1454
|
return "";
|
|
1046
1455
|
}
|
|
1456
|
+
function hasFunctionLikeAncestor(node) {
|
|
1457
|
+
let current = node.parent;
|
|
1458
|
+
while (current) {
|
|
1459
|
+
if (ts.isFunctionLike(current)) return true;
|
|
1460
|
+
current = current.parent;
|
|
1461
|
+
}
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1047
1464
|
function buildScope(node) {
|
|
1048
1465
|
const parts = [];
|
|
1049
1466
|
let current = node.parent;
|
|
@@ -1071,6 +1488,10 @@ function parseSymbols(opts) {
|
|
|
1071
1488
|
function visit(node) {
|
|
1072
1489
|
const kind = kindOf(node);
|
|
1073
1490
|
if (kind) {
|
|
1491
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && hasFunctionLikeAncestor(node)) {
|
|
1492
|
+
ts.forEachChild(node, visit);
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1074
1495
|
const nameNode = node.name;
|
|
1075
1496
|
if (!nameNode || !ts.isIdentifier(nameNode)) return;
|
|
1076
1497
|
const name = nameNode.text;
|
|
@@ -1160,8 +1581,34 @@ function detectLang(file) {
|
|
|
1160
1581
|
// src/codebase-index/go-parser.ts
|
|
1161
1582
|
import { spawn } from "node:child_process";
|
|
1162
1583
|
import * as os from "node:os";
|
|
1584
|
+
import * as path3 from "node:path";
|
|
1585
|
+
import * as fs3 from "node:fs/promises";
|
|
1586
|
+
|
|
1587
|
+
// src/_win32-resolve.ts
|
|
1588
|
+
import * as fs2 from "node:fs";
|
|
1163
1589
|
import * as path2 from "node:path";
|
|
1164
|
-
|
|
1590
|
+
function resolveWin32Command(cmd) {
|
|
1591
|
+
if (process.platform !== "win32") return cmd;
|
|
1592
|
+
if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
|
|
1593
|
+
return cmd;
|
|
1594
|
+
}
|
|
1595
|
+
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
1596
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
|
|
1597
|
+
for (const dir of pathDirs) {
|
|
1598
|
+
const base = path2.join(dir, cmd);
|
|
1599
|
+
for (const ext of pathext) {
|
|
1600
|
+
const full = `${base}${ext}`;
|
|
1601
|
+
try {
|
|
1602
|
+
fs2.accessSync(full, fs2.constants.X_OK);
|
|
1603
|
+
return full;
|
|
1604
|
+
} catch {
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return cmd;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// src/codebase-index/go-parser.ts
|
|
1165
1612
|
async function parseSymbols2(opts) {
|
|
1166
1613
|
const { file, content, lang } = opts;
|
|
1167
1614
|
try {
|
|
@@ -1462,31 +1909,46 @@ func formatType(t ast.Expr) string {
|
|
|
1462
1909
|
}
|
|
1463
1910
|
`;
|
|
1464
1911
|
async function syncGoParse(filePath, content, lang) {
|
|
1465
|
-
const tmpDir = await
|
|
1912
|
+
const tmpDir = await fs3.mkdtemp(path3.join(os.tmpdir(), "ws-go-parse-"));
|
|
1466
1913
|
try {
|
|
1467
|
-
const scriptPath =
|
|
1468
|
-
await
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1914
|
+
const scriptPath = path3.join(tmpDir, "parse.go");
|
|
1915
|
+
await fs3.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
1916
|
+
const goBinary = resolveWin32Command("go");
|
|
1917
|
+
const goResult = await new Promise(
|
|
1918
|
+
(resolve2, reject) => {
|
|
1919
|
+
let settled = false;
|
|
1920
|
+
const proc = spawn(goBinary, ["run", scriptPath], {
|
|
1921
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1922
|
+
windowsHide: true
|
|
1923
|
+
});
|
|
1924
|
+
proc.on("error", (err) => {
|
|
1925
|
+
if (settled) return;
|
|
1926
|
+
settled = true;
|
|
1927
|
+
reject(err);
|
|
1928
|
+
});
|
|
1929
|
+
let stdout2 = "";
|
|
1930
|
+
proc.stdout?.on("data", (chunk) => {
|
|
1931
|
+
stdout2 += chunk.toString();
|
|
1932
|
+
});
|
|
1933
|
+
proc.stderr?.resume();
|
|
1934
|
+
proc.stdin?.write(content);
|
|
1935
|
+
proc.stdin?.end();
|
|
1936
|
+
const timer = setTimeout(() => {
|
|
1937
|
+
if (settled) return;
|
|
1938
|
+
settled = true;
|
|
1485
1939
|
proc.kill("SIGKILL");
|
|
1486
1940
|
reject(new Error("timeout"));
|
|
1487
|
-
}, 15e3)
|
|
1488
|
-
|
|
1489
|
-
|
|
1941
|
+
}, 15e3);
|
|
1942
|
+
timer.unref?.();
|
|
1943
|
+
proc.on("close", (code2) => {
|
|
1944
|
+
if (settled) return;
|
|
1945
|
+
settled = true;
|
|
1946
|
+
clearTimeout(timer);
|
|
1947
|
+
resolve2({ code: code2, stdout: stdout2 });
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
);
|
|
1951
|
+
const { code, stdout } = goResult;
|
|
1490
1952
|
if (code !== 0 || !stdout.trim()) {
|
|
1491
1953
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1492
1954
|
}
|
|
@@ -1508,15 +1970,15 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
1508
1970
|
} catch {
|
|
1509
1971
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1510
1972
|
} finally {
|
|
1511
|
-
await
|
|
1973
|
+
await fs3.rm(tmpDir, { recursive: true, force: true });
|
|
1512
1974
|
}
|
|
1513
1975
|
}
|
|
1514
1976
|
|
|
1515
1977
|
// src/codebase-index/py-parser.ts
|
|
1516
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
1517
|
-
import * as
|
|
1978
|
+
import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
1979
|
+
import * as fs4 from "node:fs/promises";
|
|
1518
1980
|
import * as os2 from "node:os";
|
|
1519
|
-
import * as
|
|
1981
|
+
import * as path4 from "node:path";
|
|
1520
1982
|
async function parseSymbols3(opts) {
|
|
1521
1983
|
const { file, content, lang } = opts;
|
|
1522
1984
|
try {
|
|
@@ -1728,36 +2190,74 @@ visitor.visit(tree)
|
|
|
1728
2190
|
|
|
1729
2191
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
1730
2192
|
`;
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
2193
|
+
function resolvePython() {
|
|
2194
|
+
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
2195
|
+
for (const name of candidates) {
|
|
2196
|
+
const resolved = resolveWin32Command(name);
|
|
2197
|
+
const result = spawnSync(resolved, ["--version"], {
|
|
2198
|
+
stdio: "pipe",
|
|
2199
|
+
timeout: 5e3
|
|
2200
|
+
});
|
|
2201
|
+
if (result.error) continue;
|
|
2202
|
+
if (result.status !== 0) continue;
|
|
2203
|
+
return resolved;
|
|
2204
|
+
}
|
|
2205
|
+
return null;
|
|
2206
|
+
}
|
|
2207
|
+
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
2208
|
+
return new Promise((resolve2, reject) => {
|
|
2209
|
+
let settled = false;
|
|
2210
|
+
const proc = spawn2(pyBinary, [scriptPath, filePath], {
|
|
1741
2211
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1742
2212
|
windowsHide: true
|
|
1743
2213
|
});
|
|
2214
|
+
proc.on("error", (err) => {
|
|
2215
|
+
if (settled) return;
|
|
2216
|
+
settled = true;
|
|
2217
|
+
reject(err);
|
|
2218
|
+
});
|
|
1744
2219
|
proc.stdin?.write(content);
|
|
1745
2220
|
proc.stdin?.end();
|
|
1746
2221
|
let stdout = "";
|
|
1747
2222
|
proc.stdout?.on("data", (chunk) => {
|
|
1748
2223
|
stdout += chunk.toString();
|
|
1749
2224
|
});
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
)
|
|
1760
|
-
|
|
2225
|
+
proc.stderr?.resume();
|
|
2226
|
+
const timer = setTimeout(() => {
|
|
2227
|
+
if (settled) return;
|
|
2228
|
+
settled = true;
|
|
2229
|
+
proc.kill("SIGKILL");
|
|
2230
|
+
reject(new Error("timeout"));
|
|
2231
|
+
}, 15e3);
|
|
2232
|
+
timer.unref?.();
|
|
2233
|
+
proc.on("close", (code) => {
|
|
2234
|
+
if (settled) return;
|
|
2235
|
+
settled = true;
|
|
2236
|
+
clearTimeout(timer);
|
|
2237
|
+
resolve2({ code, stdout });
|
|
2238
|
+
});
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
var _cachedScriptPath = null;
|
|
2242
|
+
var _cachedPyBinary = null;
|
|
2243
|
+
async function syncPyParse(filePath, content, lang) {
|
|
2244
|
+
try {
|
|
2245
|
+
if (!_cachedScriptPath) {
|
|
2246
|
+
const tmpDir = path4.join(os2.tmpdir(), "ws-py-parse");
|
|
2247
|
+
await fs4.mkdir(tmpDir, { recursive: true });
|
|
2248
|
+
_cachedScriptPath = path4.join(tmpDir, "parse.py");
|
|
2249
|
+
await fs4.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
2250
|
+
}
|
|
2251
|
+
if (!_cachedPyBinary) {
|
|
2252
|
+
_cachedPyBinary = resolvePython();
|
|
2253
|
+
if (!_cachedPyBinary) return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2254
|
+
}
|
|
2255
|
+
const { code, stdout } = await spawnPyParser(
|
|
2256
|
+
_cachedPyBinary,
|
|
2257
|
+
_cachedScriptPath,
|
|
2258
|
+
filePath,
|
|
2259
|
+
content
|
|
2260
|
+
);
|
|
1761
2261
|
if (code !== 0 || !stdout.trim()) {
|
|
1762
2262
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1763
2263
|
}
|
|
@@ -1784,8 +2284,8 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
1784
2284
|
// src/codebase-index/rs-parser.ts
|
|
1785
2285
|
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
1786
2286
|
import { execFileSync, spawn as spawn3 } from "node:child_process";
|
|
1787
|
-
import * as
|
|
1788
|
-
import * as
|
|
2287
|
+
import * as fs5 from "node:fs/promises";
|
|
2288
|
+
import * as path5 from "node:path";
|
|
1789
2289
|
async function parseSymbols4(opts) {
|
|
1790
2290
|
const { file, content, lang } = opts;
|
|
1791
2291
|
const nativeAvailable = checkNativeParser();
|
|
@@ -1798,7 +2298,7 @@ async function parseSymbols4(opts) {
|
|
|
1798
2298
|
function checkNativeParser() {
|
|
1799
2299
|
try {
|
|
1800
2300
|
execFileSync("rustc", ["--version"], { stdio: "pipe", windowsHide: true });
|
|
1801
|
-
const toolsDir =
|
|
2301
|
+
const toolsDir = path5.join(process.cwd(), "tools");
|
|
1802
2302
|
try {
|
|
1803
2303
|
execFileSync(
|
|
1804
2304
|
"cargo",
|
|
@@ -1808,7 +2308,7 @@ function checkNativeParser() {
|
|
|
1808
2308
|
"--format-version",
|
|
1809
2309
|
"1",
|
|
1810
2310
|
"--manifest-path",
|
|
1811
|
-
|
|
2311
|
+
path5.join(toolsDir, "Cargo.toml")
|
|
1812
2312
|
],
|
|
1813
2313
|
{ stdio: "pipe", windowsHide: true }
|
|
1814
2314
|
);
|
|
@@ -1822,34 +2322,49 @@ function checkNativeParser() {
|
|
|
1822
2322
|
}
|
|
1823
2323
|
async function tryNativeParse(file, content) {
|
|
1824
2324
|
try {
|
|
1825
|
-
const toolsDir =
|
|
1826
|
-
const crateDir =
|
|
1827
|
-
const tmpFile =
|
|
1828
|
-
await
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2325
|
+
const toolsDir = path5.join(process.cwd(), "tools");
|
|
2326
|
+
const crateDir = path5.join(toolsDir, "syn-parser");
|
|
2327
|
+
const tmpFile = path5.join(crateDir, "src", "input.rs");
|
|
2328
|
+
await fs5.writeFile(tmpFile, content, "utf8");
|
|
2329
|
+
const cargoBinary = resolveWin32Command("cargo");
|
|
2330
|
+
const result = await new Promise(
|
|
2331
|
+
(resolve2, reject) => {
|
|
2332
|
+
let settled = false;
|
|
2333
|
+
const proc = spawn3(
|
|
2334
|
+
cargoBinary,
|
|
2335
|
+
["run", "--manifest-path", path5.join(toolsDir, "Cargo.toml")],
|
|
2336
|
+
{
|
|
2337
|
+
cwd: process.cwd(),
|
|
2338
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2339
|
+
windowsHide: true
|
|
2340
|
+
}
|
|
2341
|
+
);
|
|
2342
|
+
proc.on("error", (err) => {
|
|
2343
|
+
if (settled) return;
|
|
2344
|
+
settled = true;
|
|
2345
|
+
reject(err);
|
|
2346
|
+
});
|
|
2347
|
+
let stdout2 = "";
|
|
2348
|
+
proc.stdout?.on("data", (chunk) => {
|
|
2349
|
+
stdout2 += chunk.toString();
|
|
2350
|
+
});
|
|
2351
|
+
proc.stderr?.resume();
|
|
2352
|
+
const timer = setTimeout(() => {
|
|
2353
|
+
if (settled) return;
|
|
2354
|
+
settled = true;
|
|
1848
2355
|
proc.kill("SIGKILL");
|
|
1849
2356
|
reject(new Error("timeout"));
|
|
1850
|
-
}, 15e3)
|
|
1851
|
-
|
|
1852
|
-
|
|
2357
|
+
}, 15e3);
|
|
2358
|
+
timer.unref?.();
|
|
2359
|
+
proc.on("close", (c) => {
|
|
2360
|
+
if (settled) return;
|
|
2361
|
+
settled = true;
|
|
2362
|
+
clearTimeout(timer);
|
|
2363
|
+
resolve2({ code: c, stdout: stdout2 });
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
);
|
|
2367
|
+
const { code, stdout } = result;
|
|
1853
2368
|
if (code === 0 && stdout.trim()) {
|
|
1854
2369
|
const symbols = JSON.parse(stdout.trim());
|
|
1855
2370
|
return {
|
|
@@ -1932,7 +2447,7 @@ function regexParse(opts) {
|
|
|
1932
2447
|
|
|
1933
2448
|
// src/codebase-index/json-parser.ts
|
|
1934
2449
|
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
1935
|
-
import * as
|
|
2450
|
+
import * as path6 from "node:path";
|
|
1936
2451
|
function parseSymbols5(opts) {
|
|
1937
2452
|
const { file, content, lang } = opts;
|
|
1938
2453
|
try {
|
|
@@ -1944,9 +2459,9 @@ function parseSymbols5(opts) {
|
|
|
1944
2459
|
function regexParse2(opts) {
|
|
1945
2460
|
const { file, content, lang } = opts;
|
|
1946
2461
|
const symbols = [];
|
|
1947
|
-
const
|
|
1948
|
-
const isPackageJson =
|
|
1949
|
-
const isTsconfig =
|
|
2462
|
+
const basename3 = path6.basename(file).toLowerCase();
|
|
2463
|
+
const isPackageJson = basename3 === "package.json";
|
|
2464
|
+
const isTsconfig = basename3 === "tsconfig.json" || basename3 === "tsconfig.build.json";
|
|
1950
2465
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
1951
2466
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
1952
2467
|
const lines = content.split("\n");
|
|
@@ -1970,11 +2485,11 @@ function regexParse2(opts) {
|
|
|
1970
2485
|
const line = lineFromOffset(offset);
|
|
1971
2486
|
symbols.push(
|
|
1972
2487
|
makeSymbol({
|
|
1973
|
-
name:
|
|
2488
|
+
name: path6.basename(file),
|
|
1974
2489
|
kind: "object",
|
|
1975
2490
|
line,
|
|
1976
2491
|
col: 0,
|
|
1977
|
-
signature: `"${
|
|
2492
|
+
signature: `"${path6.basename(file)}" = { ... }`,
|
|
1978
2493
|
file,
|
|
1979
2494
|
lang
|
|
1980
2495
|
})
|
|
@@ -2285,8 +2800,8 @@ function makeSymbol2(opts) {
|
|
|
2285
2800
|
}
|
|
2286
2801
|
|
|
2287
2802
|
// src/codebase-index/gitignore.ts
|
|
2288
|
-
import * as
|
|
2289
|
-
import * as
|
|
2803
|
+
import * as fs6 from "node:fs/promises";
|
|
2804
|
+
import * as path7 from "node:path";
|
|
2290
2805
|
import { compileGlob } from "@wrongstack/core";
|
|
2291
2806
|
function globBody(glob) {
|
|
2292
2807
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
@@ -2332,7 +2847,7 @@ function compileGitignore(lines) {
|
|
|
2332
2847
|
async function loadGitignoreMatcher(projectRoot) {
|
|
2333
2848
|
let lines = [];
|
|
2334
2849
|
try {
|
|
2335
|
-
const raw = await
|
|
2850
|
+
const raw = await fs6.readFile(path7.join(projectRoot, ".gitignore"), "utf8");
|
|
2336
2851
|
lines = raw.split("\n");
|
|
2337
2852
|
} catch {
|
|
2338
2853
|
}
|
|
@@ -2348,9 +2863,7 @@ function yieldEventLoop() {
|
|
|
2348
2863
|
function throwIfAborted(signal) {
|
|
2349
2864
|
if (!signal?.aborted) return;
|
|
2350
2865
|
if (signal.reason instanceof Error) throw signal.reason;
|
|
2351
|
-
throw new Error(
|
|
2352
|
-
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
2353
|
-
);
|
|
2866
|
+
throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
|
|
2354
2867
|
}
|
|
2355
2868
|
function isAbortError(err) {
|
|
2356
2869
|
return err instanceof DOMException && err.name === "AbortError";
|
|
@@ -2366,8 +2879,20 @@ var DEFAULT_IGNORE = [
|
|
|
2366
2879
|
"__snapshots__",
|
|
2367
2880
|
".nyc_output"
|
|
2368
2881
|
];
|
|
2882
|
+
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
2883
|
+
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
2884
|
+
function isWithinProject(projectRoot, file) {
|
|
2885
|
+
const rel = path8.relative(projectRoot, file);
|
|
2886
|
+
return rel !== "" && !rel.startsWith(`..${path8.sep}`) && rel !== ".." && !path8.isAbsolute(rel);
|
|
2887
|
+
}
|
|
2888
|
+
function isMissingPathError(err) {
|
|
2889
|
+
const code = err?.code;
|
|
2890
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
2891
|
+
}
|
|
2369
2892
|
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
2370
2893
|
const results = [];
|
|
2894
|
+
const errors = [];
|
|
2895
|
+
let complete = true;
|
|
2371
2896
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
2372
2897
|
const globs = [
|
|
2373
2898
|
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
@@ -2390,21 +2915,23 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2390
2915
|
}
|
|
2391
2916
|
let entries;
|
|
2392
2917
|
try {
|
|
2393
|
-
entries = await
|
|
2394
|
-
} catch {
|
|
2918
|
+
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
2919
|
+
} catch (err) {
|
|
2920
|
+
complete = false;
|
|
2921
|
+
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2395
2922
|
return;
|
|
2396
2923
|
}
|
|
2397
2924
|
dirCount++;
|
|
2398
2925
|
for (const e of entries) {
|
|
2399
2926
|
if (ignoreSet.has(e.name)) continue;
|
|
2400
|
-
const full =
|
|
2401
|
-
const rel =
|
|
2927
|
+
const full = path8.join(dir, e.name);
|
|
2928
|
+
const rel = path8.relative(projectRoot, full).replace(/\\/g, "/");
|
|
2402
2929
|
if (e.isDirectory()) {
|
|
2403
2930
|
if (isGitIgnored(rel, true)) continue;
|
|
2404
2931
|
await walk(full);
|
|
2405
2932
|
} else if (e.isFile()) {
|
|
2406
|
-
if (isGitIgnored(rel, false)) continue;
|
|
2407
|
-
const ext =
|
|
2933
|
+
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
2934
|
+
const ext = path8.extname(e.name).toLowerCase();
|
|
2408
2935
|
for (const { ext: extName, pat } of globs) {
|
|
2409
2936
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
2410
2937
|
results.push(full);
|
|
@@ -2415,7 +2942,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2415
2942
|
}
|
|
2416
2943
|
};
|
|
2417
2944
|
await walk(projectRoot);
|
|
2418
|
-
return results;
|
|
2945
|
+
return { files: results, complete, errors };
|
|
2419
2946
|
}
|
|
2420
2947
|
async function parseFile(file, content, lang) {
|
|
2421
2948
|
switch (lang) {
|
|
@@ -2438,6 +2965,26 @@ async function parseFile(file, content, lang) {
|
|
|
2438
2965
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2439
2966
|
}
|
|
2440
2967
|
}
|
|
2968
|
+
function assignRefsToSymbols2(refs, symbols) {
|
|
2969
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
2970
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
2971
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2972
|
+
const assigned = [];
|
|
2973
|
+
for (const ref of refs) {
|
|
2974
|
+
let owner;
|
|
2975
|
+
for (const symbol of ordered) {
|
|
2976
|
+
if (symbol.line > ref.line) break;
|
|
2977
|
+
owner = symbol;
|
|
2978
|
+
}
|
|
2979
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
2980
|
+
if (!owner || owner.id <= 0) continue;
|
|
2981
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}`;
|
|
2982
|
+
if (seen.has(key)) continue;
|
|
2983
|
+
seen.add(key);
|
|
2984
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
2985
|
+
}
|
|
2986
|
+
return assigned;
|
|
2987
|
+
}
|
|
2441
2988
|
async function runIndexer(_ctx, opts) {
|
|
2442
2989
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
2443
2990
|
try {
|
|
@@ -2450,7 +2997,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
2450
2997
|
}
|
|
2451
2998
|
}
|
|
2452
2999
|
async function runIndexerWithStore(store, opts) {
|
|
2453
|
-
const { projectRoot,
|
|
3000
|
+
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
3001
|
+
const relationGraphVersion = "2";
|
|
3002
|
+
const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
|
|
2454
3003
|
const startMs = Date.now();
|
|
2455
3004
|
const errors = [];
|
|
2456
3005
|
const langStats = {};
|
|
@@ -2459,10 +3008,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2459
3008
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
2460
3009
|
let files;
|
|
2461
3010
|
let discoveredFiles = null;
|
|
3011
|
+
let discoveryComplete = true;
|
|
2462
3012
|
if (opts.files && opts.files.length > 0) {
|
|
2463
|
-
files = opts.files.map((f) =>
|
|
3013
|
+
files = opts.files.map((f) => path8.resolve(projectRoot, f)).filter((f) => {
|
|
3014
|
+
if (!isWithinProject(projectRoot, f)) return false;
|
|
3015
|
+
const rel = path8.relative(projectRoot, f).replace(/\\/g, "/");
|
|
3016
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path8.basename(f)) && !isGitIgnored(rel, false);
|
|
3017
|
+
});
|
|
2464
3018
|
} else {
|
|
2465
|
-
|
|
3019
|
+
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
3020
|
+
files = discovery.files;
|
|
3021
|
+
errors.push(...discovery.errors);
|
|
3022
|
+
discoveryComplete = discovery.complete;
|
|
2466
3023
|
discoveredFiles = new Set(files);
|
|
2467
3024
|
}
|
|
2468
3025
|
if (langs && langs.length > 0) {
|
|
@@ -2487,36 +3044,66 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2487
3044
|
}
|
|
2488
3045
|
const statOpts = signal ? { signal } : {};
|
|
2489
3046
|
const statReadParse = await Promise.allSettled(
|
|
2490
|
-
batchFiles.map(
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
if (
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
3047
|
+
batchFiles.map(
|
|
3048
|
+
async (file) => {
|
|
3049
|
+
let stat2;
|
|
3050
|
+
try {
|
|
3051
|
+
stat2 = await fs7.stat(file, statOpts);
|
|
3052
|
+
} catch (e) {
|
|
3053
|
+
if (isAbortError(e)) throw e;
|
|
3054
|
+
return {
|
|
3055
|
+
file,
|
|
3056
|
+
stat: null,
|
|
3057
|
+
lang: "",
|
|
3058
|
+
parsed: null,
|
|
3059
|
+
error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
|
|
3060
|
+
missing: isMissingPathError(e)
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
if (!stat2.isFile()) return { file, stat: stat2, lang: "", parsed: null };
|
|
3064
|
+
const lang = detectLang(file);
|
|
3065
|
+
if (!lang) return { file, stat: stat2, lang: "", parsed: null };
|
|
3066
|
+
if (stat2.size > MAX_INDEX_FILE_BYTES) {
|
|
3067
|
+
return {
|
|
3068
|
+
file,
|
|
3069
|
+
stat: stat2,
|
|
3070
|
+
lang,
|
|
3071
|
+
parsed: null,
|
|
3072
|
+
error: `file too large (${stat2.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
const meta = existingMeta.get(file);
|
|
3076
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat2.mtimeMs)) {
|
|
3077
|
+
return { file, stat: stat2, lang, parsed: null, skippedMeta: meta };
|
|
3078
|
+
}
|
|
3079
|
+
let content;
|
|
3080
|
+
try {
|
|
3081
|
+
content = await fs7.readFile(file, { encoding: "utf8", signal });
|
|
3082
|
+
} catch (e) {
|
|
3083
|
+
if (isAbortError(e)) throw e;
|
|
3084
|
+
return {
|
|
3085
|
+
file,
|
|
3086
|
+
stat: stat2,
|
|
3087
|
+
lang,
|
|
3088
|
+
parsed: null,
|
|
3089
|
+
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
let parsed;
|
|
3093
|
+
try {
|
|
3094
|
+
parsed = await parseFile(file, content, lang);
|
|
3095
|
+
} catch (e) {
|
|
3096
|
+
return {
|
|
3097
|
+
file,
|
|
3098
|
+
stat: stat2,
|
|
3099
|
+
lang,
|
|
3100
|
+
parsed: null,
|
|
3101
|
+
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
return { file, stat: stat2, lang, parsed, content };
|
|
2517
3105
|
}
|
|
2518
|
-
|
|
2519
|
-
})
|
|
3106
|
+
)
|
|
2520
3107
|
);
|
|
2521
3108
|
const batchEntries = [];
|
|
2522
3109
|
const deleteForFiles = [];
|
|
@@ -2531,8 +3118,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2531
3118
|
}
|
|
2532
3119
|
const result = settled.value;
|
|
2533
3120
|
if (result.error) {
|
|
2534
|
-
if (result.
|
|
2535
|
-
|
|
3121
|
+
if (result.missing) store.deleteFile(file);
|
|
3122
|
+
errors.push(`${file}: ${result.error}`);
|
|
2536
3123
|
continue;
|
|
2537
3124
|
}
|
|
2538
3125
|
const { stat: stat2, lang, parsed } = result;
|
|
@@ -2556,6 +3143,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2556
3143
|
continue;
|
|
2557
3144
|
}
|
|
2558
3145
|
if (parsed.symbols.length === 0) {
|
|
3146
|
+
store.deleteRefsForFile(file);
|
|
3147
|
+
store.deleteSymbolsForFile(file);
|
|
2559
3148
|
store.upsertFile({
|
|
2560
3149
|
file,
|
|
2561
3150
|
lang,
|
|
@@ -2566,15 +3155,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2566
3155
|
filesIndexed++;
|
|
2567
3156
|
continue;
|
|
2568
3157
|
}
|
|
2569
|
-
const refs = [];
|
|
2570
|
-
if (parsed.refs && parsed.refs.length > 0) {
|
|
2571
|
-
for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
|
|
2572
|
-
}
|
|
2573
3158
|
batchEntries.push({
|
|
2574
3159
|
file,
|
|
2575
3160
|
lang,
|
|
2576
3161
|
symbols: parsed.symbols,
|
|
2577
|
-
refs,
|
|
3162
|
+
refs: parsed.refs ?? [],
|
|
2578
3163
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
2579
3164
|
symbolCount: parsed.symbols.length
|
|
2580
3165
|
});
|
|
@@ -2582,34 +3167,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2582
3167
|
}
|
|
2583
3168
|
if (batchEntries.length > 0) {
|
|
2584
3169
|
try {
|
|
2585
|
-
|
|
2586
|
-
let cursor = 0;
|
|
3170
|
+
store.commitBatch(batchEntries, { deleteForFiles });
|
|
2587
3171
|
for (const entry of batchEntries) {
|
|
2588
3172
|
const count = entry.symbols.length;
|
|
2589
|
-
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
2590
|
-
cursor += count;
|
|
2591
3173
|
symbolsIndexed += count;
|
|
2592
3174
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
2593
3175
|
filesIndexed++;
|
|
2594
|
-
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
2595
|
-
const refsByLine = /* @__PURE__ */ new Map();
|
|
2596
|
-
for (let i = 0; i < symbolsWithIds.length; i++) {
|
|
2597
|
-
const sym = symbolsWithIds[i];
|
|
2598
|
-
let arr = refsByLine.get(sym.line);
|
|
2599
|
-
if (!arr) {
|
|
2600
|
-
arr = [];
|
|
2601
|
-
refsByLine.set(sym.line, arr);
|
|
2602
|
-
}
|
|
2603
|
-
arr.push(i);
|
|
2604
|
-
}
|
|
2605
|
-
for (const ref of entry.refs) {
|
|
2606
|
-
const indices = refsByLine.get(ref.line);
|
|
2607
|
-
if (indices && indices.length > 0) {
|
|
2608
|
-
const idx = indices.shift();
|
|
2609
|
-
ref.fromId = symbolsWithIds[idx].id;
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
3176
|
}
|
|
2614
3177
|
} catch (err) {
|
|
2615
3178
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2623,23 +3186,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2623
3186
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
2624
3187
|
filesIndexed++;
|
|
2625
3188
|
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
2626
|
-
const
|
|
2627
|
-
for (const sym of symbolsWithIds) {
|
|
2628
|
-
let arr = refsByLine.get(sym.line);
|
|
2629
|
-
if (!arr) {
|
|
2630
|
-
arr = [];
|
|
2631
|
-
refsByLine.set(sym.line, arr);
|
|
2632
|
-
}
|
|
2633
|
-
arr.push(sym);
|
|
2634
|
-
}
|
|
2635
|
-
const fallbackBatch = [];
|
|
2636
|
-
for (const ref of entry.refs) {
|
|
2637
|
-
const syms = refsByLine.get(ref.line);
|
|
2638
|
-
if (syms && syms.length > 0) {
|
|
2639
|
-
const sym = syms.shift();
|
|
2640
|
-
fallbackBatch.push({ ...ref, fromId: sym.id });
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
3189
|
+
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
2643
3190
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
2644
3191
|
}
|
|
2645
3192
|
store.upsertFile({
|
|
@@ -2650,19 +3197,23 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2650
3197
|
lastIndexed: Date.now()
|
|
2651
3198
|
});
|
|
2652
3199
|
} catch (innerErr) {
|
|
2653
|
-
errors.push(
|
|
3200
|
+
errors.push(
|
|
3201
|
+
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
3202
|
+
);
|
|
2654
3203
|
}
|
|
2655
3204
|
}
|
|
2656
3205
|
}
|
|
2657
3206
|
}
|
|
2658
3207
|
}
|
|
2659
|
-
if (discoveredFiles) {
|
|
3208
|
+
if (discoveredFiles && discoveryComplete) {
|
|
2660
3209
|
for (const [file_] of existingMeta) {
|
|
2661
3210
|
if (!discoveredFiles.has(file_)) {
|
|
2662
3211
|
store.deleteFile(file_);
|
|
2663
3212
|
}
|
|
2664
3213
|
}
|
|
2665
3214
|
}
|
|
3215
|
+
store.resolveRefs();
|
|
3216
|
+
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
2666
3217
|
const durationMs = Date.now() - startMs;
|
|
2667
3218
|
store.setLastIndexed(Date.now());
|
|
2668
3219
|
return {
|
|
@@ -2722,11 +3273,35 @@ function statsService(args) {
|
|
|
2722
3273
|
store.close();
|
|
2723
3274
|
}
|
|
2724
3275
|
}
|
|
3276
|
+
function packageGraphService(args) {
|
|
3277
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
3278
|
+
try {
|
|
3279
|
+
return store.getPackageGraph();
|
|
3280
|
+
} finally {
|
|
3281
|
+
store.close();
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
function fileGraphService(args) {
|
|
3285
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
3286
|
+
try {
|
|
3287
|
+
return store.getFileGraph(args.packageFilter);
|
|
3288
|
+
} finally {
|
|
3289
|
+
store.close();
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
function symbolGraphService(args) {
|
|
3293
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
3294
|
+
try {
|
|
3295
|
+
return store.getSymbolGraph(args.fileFilter);
|
|
3296
|
+
} finally {
|
|
3297
|
+
store.close();
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
2725
3300
|
|
|
2726
3301
|
// src/codebase-index/background-indexer.ts
|
|
2727
3302
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
2728
3303
|
var DEFAULT_INCREMENTAL_TIMEOUT_MS = 6e4;
|
|
2729
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
3304
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
2730
3305
|
var _ready = false;
|
|
2731
3306
|
var _indexing = false;
|
|
2732
3307
|
var _currentFile = 0;
|
|
@@ -2773,7 +3348,7 @@ function resolveWorkerUrl() {
|
|
|
2773
3348
|
for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
|
|
2774
3349
|
try {
|
|
2775
3350
|
const url = new URL(rel, import.meta.url);
|
|
2776
|
-
if (url.protocol === "file:" &&
|
|
3351
|
+
if (url.protocol === "file:" && fs8.existsSync(fileURLToPath(url))) return url;
|
|
2777
3352
|
} catch {
|
|
2778
3353
|
}
|
|
2779
3354
|
}
|
|
@@ -2804,14 +3379,22 @@ function ensureWorker() {
|
|
|
2804
3379
|
if (!entry) return;
|
|
2805
3380
|
pending.delete(msg.id);
|
|
2806
3381
|
if (msg.ok) entry.resolve(msg.result);
|
|
2807
|
-
else
|
|
3382
|
+
else {
|
|
3383
|
+
const error = msg.errorName === "LockError" ? new LockError(msg.error) : new Error(msg.error);
|
|
3384
|
+
if (msg.errorName && msg.errorName !== "Error") {
|
|
3385
|
+
error.name = msg.errorName;
|
|
3386
|
+
}
|
|
3387
|
+
entry.reject(error);
|
|
3388
|
+
}
|
|
2808
3389
|
});
|
|
2809
3390
|
w.on("error", (err) => {
|
|
3391
|
+
if (worker !== w) return;
|
|
2810
3392
|
worker = null;
|
|
2811
3393
|
failAllPending(err);
|
|
2812
3394
|
});
|
|
2813
3395
|
w.on("exit", () => {
|
|
2814
|
-
if (worker
|
|
3396
|
+
if (worker !== w) return;
|
|
3397
|
+
worker = null;
|
|
2815
3398
|
failAllPending(new Error("codebase-index worker exited"));
|
|
2816
3399
|
});
|
|
2817
3400
|
worker = w;
|
|
@@ -2836,6 +3419,11 @@ function shutdownCodebaseIndexHost() {
|
|
|
2836
3419
|
function callIndexOp(op, args, opts) {
|
|
2837
3420
|
const w = ensureWorker();
|
|
2838
3421
|
if (!w) return callInline(op, args, opts);
|
|
3422
|
+
if (opts.signal?.aborted) {
|
|
3423
|
+
return Promise.reject(
|
|
3424
|
+
opts.signal.reason instanceof Error ? opts.signal.reason : new Error("Indexing cancelled")
|
|
3425
|
+
);
|
|
3426
|
+
}
|
|
2839
3427
|
return new Promise((resolve2, reject) => {
|
|
2840
3428
|
const id = nextRpcId++;
|
|
2841
3429
|
const timer = setTimeout(() => {
|
|
@@ -2850,8 +3438,7 @@ function callIndexOp(op, args, opts) {
|
|
|
2850
3438
|
const onAbort = () => {
|
|
2851
3439
|
w.postMessage({ type: "cancel", id });
|
|
2852
3440
|
};
|
|
2853
|
-
|
|
2854
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3441
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2855
3442
|
const cleanup = () => {
|
|
2856
3443
|
clearTimeout(timer);
|
|
2857
3444
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -3039,7 +3626,7 @@ var codebaseIndexTool = {
|
|
|
3039
3626
|
category: "Project",
|
|
3040
3627
|
icon: "index",
|
|
3041
3628
|
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.",
|
|
3042
|
-
usageHint: "
|
|
3629
|
+
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.",
|
|
3043
3630
|
permission: "confirm",
|
|
3044
3631
|
mutating: true,
|
|
3045
3632
|
capabilities: ["fs.write.outside-project"],
|
|
@@ -3095,12 +3682,14 @@ var codebaseSearchTool = {
|
|
|
3095
3682
|
name: "codebase-search",
|
|
3096
3683
|
category: "Project",
|
|
3097
3684
|
icon: "index",
|
|
3098
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback.
|
|
3099
|
-
usageHint: "
|
|
3685
|
+
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).",
|
|
3686
|
+
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.",
|
|
3100
3687
|
permission: "auto",
|
|
3101
3688
|
mutating: false,
|
|
3102
3689
|
capabilities: ["fs.read"],
|
|
3103
|
-
|
|
3690
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
3691
|
+
// worker teardown and structured timeout reporting.
|
|
3692
|
+
timeoutMs: 35e3,
|
|
3104
3693
|
inputSchema: {
|
|
3105
3694
|
type: "object",
|
|
3106
3695
|
properties: {
|
|
@@ -3110,11 +3699,34 @@ var codebaseSearchTool = {
|
|
|
3110
3699
|
},
|
|
3111
3700
|
kind: {
|
|
3112
3701
|
type: "string",
|
|
3113
|
-
|
|
3702
|
+
enum: [
|
|
3703
|
+
"class",
|
|
3704
|
+
"interface",
|
|
3705
|
+
"enum",
|
|
3706
|
+
"type",
|
|
3707
|
+
"function",
|
|
3708
|
+
"method",
|
|
3709
|
+
"var",
|
|
3710
|
+
"const",
|
|
3711
|
+
"let",
|
|
3712
|
+
"property",
|
|
3713
|
+
"parameter",
|
|
3714
|
+
"namespace",
|
|
3715
|
+
"object",
|
|
3716
|
+
"literal",
|
|
3717
|
+
"schema",
|
|
3718
|
+
"struct",
|
|
3719
|
+
"trait",
|
|
3720
|
+
"impl",
|
|
3721
|
+
"static",
|
|
3722
|
+
"mod"
|
|
3723
|
+
],
|
|
3724
|
+
description: "Filter by indexed symbol kind"
|
|
3114
3725
|
},
|
|
3115
3726
|
lang: {
|
|
3116
3727
|
type: "string",
|
|
3117
|
-
|
|
3728
|
+
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
3729
|
+
description: "Filter by indexed language"
|
|
3118
3730
|
},
|
|
3119
3731
|
lspKind: {
|
|
3120
3732
|
type: "integer",
|
|
@@ -3139,20 +3751,12 @@ var codebaseSearchTool = {
|
|
|
3139
3751
|
},
|
|
3140
3752
|
async execute(input, ctx, execOpts) {
|
|
3141
3753
|
const state = getIndexState();
|
|
3142
|
-
if (!state.ready) {
|
|
3754
|
+
if (state.indexing && !state.ready) {
|
|
3143
3755
|
return {
|
|
3144
3756
|
results: [],
|
|
3145
3757
|
total: 0,
|
|
3146
3758
|
query: input.query,
|
|
3147
|
-
indexStatus:
|
|
3148
|
-
};
|
|
3149
|
-
}
|
|
3150
|
-
if (state.indexing) {
|
|
3151
|
-
return {
|
|
3152
|
-
results: [],
|
|
3153
|
-
total: 0,
|
|
3154
|
-
query: input.query,
|
|
3155
|
-
indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files). Results may be incomplete.`
|
|
3759
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
3156
3760
|
};
|
|
3157
3761
|
}
|
|
3158
3762
|
if (state.lastError) {
|
|
@@ -3165,21 +3769,37 @@ var codebaseSearchTool = {
|
|
|
3165
3769
|
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
3166
3770
|
};
|
|
3167
3771
|
}
|
|
3168
|
-
const limit = Math.min(input.limit ?? 20, 100);
|
|
3772
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
3169
3773
|
const { results, total } = await searchCodebaseIndex(
|
|
3170
3774
|
{
|
|
3171
3775
|
projectRoot: ctx.projectRoot,
|
|
3172
3776
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
3173
3777
|
query: input.query,
|
|
3174
|
-
kind: input.kind,
|
|
3175
|
-
lang: input.lang,
|
|
3778
|
+
kind: input.kind?.toLowerCase(),
|
|
3779
|
+
lang: input.lang?.toLowerCase(),
|
|
3176
3780
|
file: input.file,
|
|
3177
3781
|
lspKind: input.lspKind,
|
|
3178
3782
|
limit
|
|
3179
3783
|
},
|
|
3180
3784
|
{ signal: execOpts?.signal }
|
|
3181
3785
|
);
|
|
3182
|
-
|
|
3786
|
+
let hasPersistedIndex = state.ready || total > 0;
|
|
3787
|
+
if (!hasPersistedIndex) {
|
|
3788
|
+
try {
|
|
3789
|
+
const stats = await codebaseIndexStats(
|
|
3790
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
3791
|
+
{ signal: execOpts?.signal }
|
|
3792
|
+
);
|
|
3793
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
3794
|
+
} catch {
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
return {
|
|
3798
|
+
results,
|
|
3799
|
+
total,
|
|
3800
|
+
query: input.query,
|
|
3801
|
+
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
3802
|
+
};
|
|
3183
3803
|
}
|
|
3184
3804
|
};
|
|
3185
3805
|
|
|
@@ -3188,12 +3808,15 @@ var codebaseStatsTool = {
|
|
|
3188
3808
|
name: "codebase-stats",
|
|
3189
3809
|
category: "Project",
|
|
3190
3810
|
icon: "index",
|
|
3191
|
-
description: "
|
|
3192
|
-
usageHint: "
|
|
3811
|
+
description: "Check whether a persisted codebase index exists and report its health and statistics (symbols, files, language/kind breakdown, size, last update).",
|
|
3812
|
+
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.",
|
|
3193
3813
|
permission: "auto",
|
|
3194
3814
|
mutating: false,
|
|
3195
3815
|
capabilities: ["fs.read"],
|
|
3196
|
-
|
|
3816
|
+
// The index host has its own 30s read watchdog. Keep the outer tool budget
|
|
3817
|
+
// longer so the host can return a structured timeout instead of being
|
|
3818
|
+
// pre-empted by the generic tool executor.
|
|
3819
|
+
timeoutMs: 35e3,
|
|
3197
3820
|
inputSchema: {
|
|
3198
3821
|
type: "object",
|
|
3199
3822
|
properties: {},
|
|
@@ -3201,7 +3824,8 @@ var codebaseStatsTool = {
|
|
|
3201
3824
|
},
|
|
3202
3825
|
async execute(_input, ctx, execOpts) {
|
|
3203
3826
|
const idxState = getIndexState();
|
|
3204
|
-
|
|
3827
|
+
const indexPath = resolveIndexDir(ctx.projectRoot, codebaseIndexDirOverride(ctx));
|
|
3828
|
+
if (idxState.indexing) {
|
|
3205
3829
|
return {
|
|
3206
3830
|
totalSymbols: 0,
|
|
3207
3831
|
totalFiles: 0,
|
|
@@ -3209,22 +3833,47 @@ var codebaseStatsTool = {
|
|
|
3209
3833
|
byKind: {},
|
|
3210
3834
|
lastIndexed: null,
|
|
3211
3835
|
sizeBytes: 0,
|
|
3212
|
-
indexPath
|
|
3836
|
+
indexPath,
|
|
3213
3837
|
version: SCHEMA_VERSION,
|
|
3214
|
-
|
|
3838
|
+
statsAvailable: false,
|
|
3839
|
+
indexing: {
|
|
3840
|
+
currentFile: idxState.currentFile,
|
|
3841
|
+
totalFiles: idxState.totalFiles
|
|
3842
|
+
},
|
|
3843
|
+
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.`
|
|
3215
3844
|
};
|
|
3216
3845
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3846
|
+
let stats;
|
|
3847
|
+
try {
|
|
3848
|
+
stats = await codebaseIndexStats(
|
|
3849
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
3850
|
+
{ signal: execOpts?.signal }
|
|
3851
|
+
);
|
|
3852
|
+
} catch (err) {
|
|
3853
|
+
if (!(err instanceof IndexTimeoutError) && err?.name !== "IndexTimeoutError") {
|
|
3854
|
+
throw err;
|
|
3855
|
+
}
|
|
3222
3856
|
return {
|
|
3223
|
-
|
|
3224
|
-
|
|
3857
|
+
totalSymbols: 0,
|
|
3858
|
+
totalFiles: 0,
|
|
3859
|
+
byLang: {},
|
|
3860
|
+
byKind: {},
|
|
3861
|
+
lastIndexed: null,
|
|
3862
|
+
sizeBytes: 0,
|
|
3863
|
+
indexPath,
|
|
3864
|
+
version: SCHEMA_VERSION,
|
|
3865
|
+
statsAvailable: false,
|
|
3866
|
+
indexStatus: "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback."
|
|
3225
3867
|
};
|
|
3226
3868
|
}
|
|
3227
3869
|
const circuit = idxState.circuit;
|
|
3870
|
+
const hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
3871
|
+
let indexStatus;
|
|
3872
|
+
if (circuit.state === "open") {
|
|
3873
|
+
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.");
|
|
3874
|
+
} else if (!hasPersistedIndex) {
|
|
3875
|
+
indexStatus = "No persisted index data found. Run codebase-index to build it before broad code exploration.";
|
|
3876
|
+
}
|
|
3228
3877
|
return {
|
|
3229
3878
|
totalSymbols: stats.totalSymbols,
|
|
3230
3879
|
totalFiles: stats.totalFiles,
|
|
@@ -3234,9 +3883,8 @@ var codebaseStatsTool = {
|
|
|
3234
3883
|
sizeBytes: stats.sizeBytes,
|
|
3235
3884
|
indexPath: stats.indexPath,
|
|
3236
3885
|
version: stats.version,
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
} : {}
|
|
3886
|
+
statsAvailable: true,
|
|
3887
|
+
...indexStatus ? { indexStatus } : {}
|
|
3240
3888
|
};
|
|
3241
3889
|
}
|
|
3242
3890
|
};
|
|
@@ -3255,6 +3903,7 @@ export {
|
|
|
3255
3903
|
codebaseSearchTool,
|
|
3256
3904
|
codebaseStatsTool,
|
|
3257
3905
|
enqueueReindex,
|
|
3906
|
+
fileGraphService,
|
|
3258
3907
|
getIndexState,
|
|
3259
3908
|
indexCircuitBreaker,
|
|
3260
3909
|
internalKindToLspKind,
|
|
@@ -3263,12 +3912,14 @@ export {
|
|
|
3263
3912
|
isIndexing,
|
|
3264
3913
|
lspKindToInternalKind,
|
|
3265
3914
|
onIndexStateChange,
|
|
3915
|
+
packageGraphService,
|
|
3266
3916
|
resetIndexCircuitBreaker,
|
|
3267
3917
|
resolveIndexDir,
|
|
3268
3918
|
runIndexer,
|
|
3269
3919
|
runStartupIndex,
|
|
3270
3920
|
searchCodebaseIndex,
|
|
3271
3921
|
shutdownCodebaseIndexHost,
|
|
3922
|
+
symbolGraphService,
|
|
3272
3923
|
tokenise
|
|
3273
3924
|
};
|
|
3274
3925
|
//# sourceMappingURL=index.js.map
|