@danypops/lector 0.1.7 → 0.1.9
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 +11 -7
- package/package.json +5 -1
- package/src/adapters/fallback-code-intelligence-index.ts +73 -0
- package/src/adapters/find-source-files.ts +1 -1
- package/src/adapters/git-repo-fetcher.ts +154 -37
- package/src/adapters/lsp/discover-seed-file.ts +17 -9
- package/src/adapters/lsp/json-rpc-stream.ts +52 -2
- package/src/adapters/lsp/language-server-process.ts +54 -10
- package/src/adapters/lsp/lsp-symbol-index.ts +167 -41
- package/src/adapters/normalize-npm-repository.ts +77 -0
- package/src/adapters/npm-lockfile-version-resolver.ts +519 -0
- package/src/adapters/npm-package-source-resolver.ts +322 -0
- package/src/adapters/npm-registry-client.ts +304 -0
- package/src/adapters/polyglot-code-intelligence-index.ts +135 -0
- package/src/adapters/sqlite-symbol-graph.ts +68 -3
- package/src/adapters/tree-sitter/typescript-tree-sitter-symbol-index.ts +61 -11
- package/src/adapters/typescript-compiler-symbol-index.ts +129 -0
- package/src/cli.ts +97 -23
- package/src/daemon.ts +4 -0
- package/src/domain/find-workspace-symbols.ts +4 -3
- package/src/domain/installed-package-version.ts +66 -0
- package/src/domain/intelligence-provenance.ts +27 -0
- package/src/domain/language-server-descriptor.ts +22 -0
- package/src/domain/npm-package-metadata.ts +26 -0
- package/src/domain/package-source.ts +143 -0
- package/src/domain/repo-fetch-result.ts +33 -1
- package/src/domain/resolve-package-source.ts +204 -0
- package/src/domain/symbol-graph-generation.ts +5 -0
- package/src/domain/symbol-query.ts +16 -0
- package/src/domain/workspace-symbol.ts +13 -0
- package/src/index.ts +68 -4
- package/src/ports/installed-package-version-resolver-port.ts +5 -0
- package/src/ports/npm-registry-port.ts +5 -0
- package/src/ports/package-source-resolver-port.ts +5 -0
- package/src/ports/repo-fetcher-port.ts +2 -2
- package/src/ports/symbol-index-port.ts +4 -2
- package/src/service.ts +152 -43
package/src/service.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { stat } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { FallbackCodeIntelligenceIndex } from "./adapters/fallback-code-intelligence-index.ts";
|
|
4
5
|
import { InMemorySearchCache } from "./adapters/in-memory-search-cache.ts";
|
|
5
6
|
import { InMemorySymbolGraph } from "./adapters/in-memory-symbol-graph.ts";
|
|
6
7
|
import { LocalFilesystemWorkspace } from "./adapters/local-filesystem-workspace.ts";
|
|
7
8
|
import { LocalGit } from "./adapters/local-git.ts";
|
|
8
|
-
import { discoverWorkspaceDescriptor } from "./adapters/lsp/discover-seed-file.ts";
|
|
9
|
+
import { discoverWorkspaceDescriptor, discoverWorkspaceDescriptors } from "./adapters/lsp/discover-seed-file.ts";
|
|
9
10
|
import { LspSymbolIndex } from "./adapters/lsp/lsp-symbol-index.ts";
|
|
11
|
+
import { NpmLockfileVersionResolver } from "./adapters/npm-lockfile-version-resolver.ts";
|
|
12
|
+
import { NpmPackageSourceResolver } from "./adapters/npm-package-source-resolver.ts";
|
|
13
|
+
import { NpmRegistryClient } from "./adapters/npm-registry-client.ts";
|
|
14
|
+
import { PolyglotCodeIntelligenceIndex } from "./adapters/polyglot-code-intelligence-index.ts";
|
|
10
15
|
import { ReadOnlyWorkspace } from "./adapters/read-only-workspace.ts";
|
|
11
16
|
import { RipgrepTextSearch } from "./adapters/ripgrep-text-search.ts";
|
|
12
17
|
import { deriveSourceManifest } from "./adapters/source-manifest.ts";
|
|
18
|
+
import { TreeSitterSymbolIndex } from "./adapters/tree-sitter/typescript-tree-sitter-symbol-index.ts";
|
|
19
|
+
import { TypeScriptCompilerSymbolIndex } from "./adapters/typescript-compiler-symbol-index.ts";
|
|
13
20
|
import { BoundedJobExecutor, type JobSnapshot } from "./domain/bounded-job-executor.ts";
|
|
14
21
|
import type { CallHierarchyEntry, IncomingCall, OutgoingCall } from "./domain/call-hierarchy.ts";
|
|
15
22
|
import type { Diagnostic } from "./domain/diagnostic.ts";
|
|
@@ -27,8 +34,10 @@ import { goToImplementation as goToImplementationQuery } from "./domain/go-to-im
|
|
|
27
34
|
import type { Hover } from "./domain/hover.ts";
|
|
28
35
|
import { hoverAt } from "./domain/hover-at.ts";
|
|
29
36
|
import { incomingCalls as incomingCallsQuery } from "./domain/incoming-calls.ts";
|
|
37
|
+
import type { IntelligenceProvenance } from "./domain/intelligence-provenance.ts";
|
|
30
38
|
import { descriptorForPath, LANGUAGE_SERVER_DESCRIPTORS, type LanguageServerDescriptor } from "./domain/language-server-descriptor.ts";
|
|
31
39
|
import { outgoingCalls as outgoingCallsQuery } from "./domain/outgoing-calls.ts";
|
|
40
|
+
import type { PackageSourceBounds, PackageSourceOperationResult, PackageSourceRequest } from "./domain/package-source.ts";
|
|
32
41
|
import { type PopulateSymbolGraphResult, populateSymbolGraph as populateSymbolGraphQuery } from "./domain/populate-symbol-graph.ts";
|
|
33
42
|
import { prepareCallHierarchy as prepareCallHierarchyQuery } from "./domain/prepare-call-hierarchy.ts";
|
|
34
43
|
import { raceWorkspaceQuery } from "./domain/race-workspace-query.ts";
|
|
@@ -36,16 +45,19 @@ import { type RawRead, rawRead, WorkspaceEntryNotFound } from "./domain/raw-read
|
|
|
36
45
|
import { reachableSymbolsFrom } from "./domain/reachable-symbols-from.ts";
|
|
37
46
|
import type { RepoFetchResult } from "./domain/repo-fetch-result.ts";
|
|
38
47
|
import type { RepoReference } from "./domain/repo-reference.ts";
|
|
48
|
+
import { resolvePackageSource } from "./domain/resolve-package-source.ts";
|
|
39
49
|
import { searchText as searchTextQuery } from "./domain/search-text.ts";
|
|
40
50
|
import { symbolEdgesFrom } from "./domain/symbol-edges-from.ts";
|
|
41
51
|
import { symbolEdgesTo } from "./domain/symbol-edges-to.ts";
|
|
42
52
|
import type { WorkspaceCacheStatus } from "./domain/symbol-graph-generation.ts";
|
|
43
53
|
import { deriveSymbolNodeId } from "./domain/symbol-node-id.ts";
|
|
54
|
+
import { assertBoundedSymbolQuery } from "./domain/symbol-query.ts";
|
|
44
55
|
import type { TextSearchResult } from "./domain/text-search-result.ts";
|
|
45
56
|
import type { WorkspaceQueryOutcome } from "./domain/workspace-query-outcome.ts";
|
|
46
|
-
import type {
|
|
57
|
+
import type { SymbolSearchResult, WorkspaceLocation } from "./domain/workspace-symbol.ts";
|
|
47
58
|
import type { CodeIntelligencePort } from "./ports/code-intelligence-port.ts";
|
|
48
59
|
import type { GitPort } from "./ports/git-port.ts";
|
|
60
|
+
import type { PackageSourceResolverPort } from "./ports/package-source-resolver-port.ts";
|
|
49
61
|
import type { RepoFetcherPort } from "./ports/repo-fetcher-port.ts";
|
|
50
62
|
import type { SearchCachePort } from "./ports/search-cache-port.ts";
|
|
51
63
|
import type { SymbolEdgeKind, SymbolGraphPort, SymbolNode } from "./ports/symbol-graph-port.ts";
|
|
@@ -128,6 +140,13 @@ export class RepoFetcherNotConfigured extends Error {
|
|
|
128
140
|
}
|
|
129
141
|
}
|
|
130
142
|
|
|
143
|
+
export class PackageSourceResolverNotConfigured extends Error {
|
|
144
|
+
constructor() {
|
|
145
|
+
super("package.resolveSource requires a service constructed with repository fetching");
|
|
146
|
+
this.name = "PackageSourceResolverNotConfigured";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
131
150
|
export class UnsupportedJobOperation extends Error {
|
|
132
151
|
constructor(readonly operation: string) {
|
|
133
152
|
super(`operation "${operation}" cannot run as a background job; supported operations: workspace.populateSymbolGraph`);
|
|
@@ -185,6 +204,7 @@ export type OperationName =
|
|
|
185
204
|
| "workspace.gitLog"
|
|
186
205
|
| "workspace.gitDiff"
|
|
187
206
|
| "repo.fetch"
|
|
207
|
+
| "package.resolveSource"
|
|
188
208
|
| "workspace.searchText"
|
|
189
209
|
| "search.symbols"
|
|
190
210
|
| "search.text"
|
|
@@ -215,6 +235,7 @@ export const OPERATION_NAMES: readonly OperationName[] = [
|
|
|
215
235
|
"workspace.gitLog",
|
|
216
236
|
"workspace.gitDiff",
|
|
217
237
|
"repo.fetch",
|
|
238
|
+
"package.resolveSource",
|
|
218
239
|
"workspace.searchText",
|
|
219
240
|
"search.symbols",
|
|
220
241
|
"search.text",
|
|
@@ -234,7 +255,7 @@ export interface OperationInputs {
|
|
|
234
255
|
"workspace.rawRead": { workspaceId: WorkspaceId; path: string };
|
|
235
256
|
"workspace.exactEdit": { workspaceId: WorkspaceId } & ExpectedHashEdit;
|
|
236
257
|
"workspace.registerPath": { path: string };
|
|
237
|
-
"workspace.findSymbols": { workspaceId: WorkspaceId; query: string; seedFile?: string };
|
|
258
|
+
"workspace.findSymbols": { workspaceId: WorkspaceId; query: string; seedFile?: string; maxResults?: number };
|
|
238
259
|
"workspace.goToDefinition": WorkspacePosition;
|
|
239
260
|
"workspace.goToImplementation": WorkspacePosition;
|
|
240
261
|
"workspace.findReferences": WorkspacePosition & { includeDeclaration: boolean };
|
|
@@ -254,6 +275,7 @@ export interface OperationInputs {
|
|
|
254
275
|
"workspace.gitLog": { workspaceId: WorkspaceId; maxCount: number };
|
|
255
276
|
"workspace.gitDiff": { workspaceId: WorkspaceId; ref?: string; maxBytes: number };
|
|
256
277
|
"repo.fetch": RepoReference;
|
|
278
|
+
"package.resolveSource": { request: PackageSourceRequest; bounds: PackageSourceBounds };
|
|
257
279
|
"workspace.searchText": { workspaceId: WorkspaceId; query: string; maxMatches: number; maxBytes: number };
|
|
258
280
|
/**
|
|
259
281
|
* No single workspaceId -- fans out across several at once, unlike every other findSymbols-
|
|
@@ -275,20 +297,22 @@ export interface OperationInputs {
|
|
|
275
297
|
"job.status": { jobId: string };
|
|
276
298
|
}
|
|
277
299
|
|
|
300
|
+
type Provenanced<T> = T & { readonly provenance: IntelligenceProvenance };
|
|
301
|
+
|
|
278
302
|
export interface OperationOutputs {
|
|
279
303
|
"workspace.rawRead": RawRead;
|
|
280
304
|
"workspace.exactEdit": EditOutcome;
|
|
281
305
|
"workspace.registerPath": { workspaceId: WorkspaceId; created: boolean };
|
|
282
|
-
"workspace.findSymbols":
|
|
283
|
-
"workspace.goToDefinition": { locations: readonly WorkspaceLocation[] }
|
|
284
|
-
"workspace.goToImplementation": { locations: readonly WorkspaceLocation[] }
|
|
285
|
-
"workspace.findReferences": { locations: readonly WorkspaceLocation[] }
|
|
286
|
-
"workspace.hover": { hover: Hover | undefined }
|
|
287
|
-
"workspace.documentSymbols": { symbols: readonly DocumentSymbolEntry[] }
|
|
288
|
-
"workspace.diagnostics": { diagnostics: readonly Diagnostic[] }
|
|
289
|
-
"workspace.prepareCallHierarchy": { items: readonly CallHierarchyEntry[] }
|
|
290
|
-
"workspace.incomingCalls": { calls: readonly IncomingCall[] }
|
|
291
|
-
"workspace.outgoingCalls": { calls: readonly OutgoingCall[] }
|
|
306
|
+
"workspace.findSymbols": SymbolSearchResult;
|
|
307
|
+
"workspace.goToDefinition": Provenanced<{ locations: readonly WorkspaceLocation[] }>;
|
|
308
|
+
"workspace.goToImplementation": Provenanced<{ locations: readonly WorkspaceLocation[] }>;
|
|
309
|
+
"workspace.findReferences": Provenanced<{ locations: readonly WorkspaceLocation[] }>;
|
|
310
|
+
"workspace.hover": Provenanced<{ hover: Hover | undefined }>;
|
|
311
|
+
"workspace.documentSymbols": Provenanced<{ symbols: readonly DocumentSymbolEntry[] }>;
|
|
312
|
+
"workspace.diagnostics": Provenanced<{ diagnostics: readonly Diagnostic[] }>;
|
|
313
|
+
"workspace.prepareCallHierarchy": Provenanced<{ items: readonly CallHierarchyEntry[] }>;
|
|
314
|
+
"workspace.incomingCalls": Provenanced<{ calls: readonly IncomingCall[] }>;
|
|
315
|
+
"workspace.outgoingCalls": Provenanced<{ calls: readonly OutgoingCall[] }>;
|
|
292
316
|
"workspace.populateSymbolGraph": { filesProcessed: number; symbolsProcessed: number; nodesAdded: number; edgesAdded: number };
|
|
293
317
|
"workspace.reachableFrom": { symbols: readonly SymbolNode[] };
|
|
294
318
|
"workspace.symbolEdgesFrom": { symbols: readonly SymbolNode[] };
|
|
@@ -299,8 +323,9 @@ export interface OperationOutputs {
|
|
|
299
323
|
"workspace.gitLog": { entries: readonly GitLogEntry[] };
|
|
300
324
|
"workspace.gitDiff": GitDiffResult;
|
|
301
325
|
"repo.fetch": RepoFetchResult & { workspaceId: WorkspaceId };
|
|
326
|
+
"package.resolveSource": PackageSourceOperationResult;
|
|
302
327
|
"workspace.searchText": TextSearchResult;
|
|
303
|
-
"search.symbols": { results: readonly WorkspaceQueryOutcome<
|
|
328
|
+
"search.symbols": { results: readonly WorkspaceQueryOutcome<SymbolSearchResult>[] };
|
|
304
329
|
"search.text": { results: readonly WorkspaceQueryOutcome<TextSearchResult>[] };
|
|
305
330
|
"job.submit": { job: JobSnapshot<PopulateSymbolGraphResult> };
|
|
306
331
|
"job.status": { job: JobSnapshot<PopulateSymbolGraphResult> };
|
|
@@ -366,6 +391,8 @@ export interface LectorServiceOptions {
|
|
|
366
391
|
createGitPort?: (rootPath: string) => GitPort;
|
|
367
392
|
/** Factory for the port backing repo.fetch. No default -- unlike createSymbolGraph's safe in-memory fallback, fetching a real external repo always needs a real disk location only a host (daemon.ts) can supply. Called once at construction and reused, not per-call. */
|
|
368
393
|
createRepoFetcher?: () => RepoFetcherPort;
|
|
394
|
+
/** Override package-source resolution. With a repo fetcher configured, the default composes npm lockfiles, registry metadata, and exact Git fetching. */
|
|
395
|
+
createPackageSourceResolver?: () => PackageSourceResolverPort;
|
|
369
396
|
/** Factory for the port backing workspace.searchText. Defaults to RipgrepTextSearch -- cheap to construct, no disk dependency, safe like createGitPort's default. Called once at construction and reused. */
|
|
370
397
|
createTextSearch?: () => TextSearchPort;
|
|
371
398
|
/** Factory for workspace.searchText's result cache. Defaults to an in-memory-only InMemorySearchCache -- safe, no disk dependency. A host wanting the disk-backed tier too (surviving a restart) supplies a TieredSearchCache here. Called once at construction and reused. */
|
|
@@ -382,7 +409,7 @@ function resolveWorkspace(registry: MutableRegistry, workspaceId: WorkspaceId):
|
|
|
382
409
|
|
|
383
410
|
/** True when a warm SymbolIndexPort is also a real CodeIntelligencePort (currently: any LspSymbolIndex, never TreeSitterSymbolIndex). */
|
|
384
411
|
function supportsCodeIntelligence(index: SymbolIndexPort): index is SymbolIndexPort & CodeIntelligencePort {
|
|
385
|
-
return
|
|
412
|
+
return "goToDefinition" in index && typeof index.goToDefinition === "function";
|
|
386
413
|
}
|
|
387
414
|
|
|
388
415
|
type OperationHandlers = {
|
|
@@ -418,6 +445,7 @@ async function registerPath(registry: MutableRegistry, input: OperationInputs["w
|
|
|
418
445
|
*/
|
|
419
446
|
const DEFAULT_CROSS_WORKSPACE_TIMEOUT_MS = 3000;
|
|
420
447
|
const MAX_INITIAL_JOB_WAIT_MS = 30_000;
|
|
448
|
+
const MAX_SYMBOL_RESULTS = 5_000;
|
|
421
449
|
const MAX_SOURCE_MANIFEST_BYTES = 50 * 1024 * 1024;
|
|
422
450
|
|
|
423
451
|
/**
|
|
@@ -455,7 +483,11 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
455
483
|
const symbolIndexes = new Map<string, { index: ClosableSymbolIndex; workspaceId: WorkspaceId; lastUsedAt: number }>();
|
|
456
484
|
const createSymbolIndex =
|
|
457
485
|
options.createSymbolIndex ??
|
|
458
|
-
((rootPath: string, descriptor: LanguageServerDescriptor, seedFile?: string) =>
|
|
486
|
+
((rootPath: string, descriptor: LanguageServerDescriptor, seedFile?: string) => {
|
|
487
|
+
const semantic = new LspSymbolIndex(rootPath, descriptor, seedFile);
|
|
488
|
+
if (descriptor.languageId !== "typescript") return semantic;
|
|
489
|
+
return new FallbackCodeIntelligenceIndex(semantic, [new TypeScriptCompilerSymbolIndex(rootPath), new TreeSitterSymbolIndex(rootPath)]);
|
|
490
|
+
});
|
|
459
491
|
function symbolIndexKey(workspaceId: WorkspaceId, languageId: string): string {
|
|
460
492
|
return `${workspaceId}:${languageId}`;
|
|
461
493
|
}
|
|
@@ -481,6 +513,11 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
481
513
|
// every time, wastefully, and would risk losing the in-memory LRU's recency ordering
|
|
482
514
|
// between calls for no benefit (the index itself is what makes rehydration correct at all).
|
|
483
515
|
const repoFetcher = options.createRepoFetcher?.();
|
|
516
|
+
const packageSourceResolver =
|
|
517
|
+
options.createPackageSourceResolver?.() ??
|
|
518
|
+
(repoFetcher
|
|
519
|
+
? new NpmPackageSourceResolver({ versions: new NpmLockfileVersionResolver(), registry: new NpmRegistryClient(), repositories: repoFetcher })
|
|
520
|
+
: undefined);
|
|
484
521
|
const textSearch = options.createTextSearch?.() ?? new RipgrepTextSearch();
|
|
485
522
|
const searchCache = options.createSearchCache?.() ?? new InMemorySearchCache();
|
|
486
523
|
|
|
@@ -521,6 +558,28 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
521
558
|
return { workspaceId, ...result };
|
|
522
559
|
}
|
|
523
560
|
|
|
561
|
+
async function packageSourceHandler(
|
|
562
|
+
registry: MutableRegistry,
|
|
563
|
+
input: OperationInputs["package.resolveSource"],
|
|
564
|
+
): Promise<OperationOutputs["package.resolveSource"]> {
|
|
565
|
+
if (!packageSourceResolver) throw new PackageSourceResolverNotConfigured();
|
|
566
|
+
const outcome = await resolvePackageSource(packageSourceResolver, input.request, input.bounds);
|
|
567
|
+
if (outcome.status !== "verified") return { outcome, workspaceId: null };
|
|
568
|
+
const absolutePath = resolve(outcome.workspace.cachePath);
|
|
569
|
+
let sourceStats: Awaited<ReturnType<typeof stat>>;
|
|
570
|
+
try {
|
|
571
|
+
sourceStats = await stat(absolutePath);
|
|
572
|
+
} catch {
|
|
573
|
+
throw new InvalidWorkspaceRoot(absolutePath, "verified package source does not exist or is not accessible");
|
|
574
|
+
}
|
|
575
|
+
if (!sourceStats.isDirectory()) throw new InvalidWorkspaceRoot(absolutePath, "verified package source is not a directory");
|
|
576
|
+
const workspaceId = deriveWorkspaceId(absolutePath);
|
|
577
|
+
if (!registry.has(workspaceId)) {
|
|
578
|
+
registry.set(workspaceId, { port: new ReadOnlyWorkspace(new LocalFilesystemWorkspace(absolutePath)), rootPath: absolutePath, origin: "remote" });
|
|
579
|
+
}
|
|
580
|
+
return { outcome, workspaceId };
|
|
581
|
+
}
|
|
582
|
+
|
|
524
583
|
async function searchTextHandler(
|
|
525
584
|
registry: MutableRegistry,
|
|
526
585
|
input: OperationInputs["workspace.searchText"],
|
|
@@ -619,6 +678,18 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
619
678
|
return { descriptor: discovered.descriptor, seedFile: discovered.seedFile };
|
|
620
679
|
}
|
|
621
680
|
|
|
681
|
+
function ensureLanguageIndex(workspaceId: WorkspaceId, rootPath: string, descriptor: LanguageServerDescriptor, seedFile?: string): ClosableSymbolIndex {
|
|
682
|
+
const key = symbolIndexKey(workspaceId, descriptor.languageId);
|
|
683
|
+
let entryIndex = symbolIndexes.get(key);
|
|
684
|
+
if (!entryIndex) {
|
|
685
|
+
entryIndex = { index: createSymbolIndex(rootPath, descriptor, seedFile), workspaceId, lastUsedAt: Date.now() };
|
|
686
|
+
symbolIndexes.set(key, entryIndex);
|
|
687
|
+
} else {
|
|
688
|
+
entryIndex.lastUsedAt = Date.now();
|
|
689
|
+
}
|
|
690
|
+
return entryIndex.index;
|
|
691
|
+
}
|
|
692
|
+
|
|
622
693
|
async function ensureWarmIndex(input: {
|
|
623
694
|
workspaceId: WorkspaceId;
|
|
624
695
|
path?: string;
|
|
@@ -627,18 +698,45 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
627
698
|
const entry = registry.get(input.workspaceId);
|
|
628
699
|
if (!entry) throw new UnknownWorkspace(input.workspaceId);
|
|
629
700
|
if (!entry.rootPath) throw new SymbolQueryUnavailable(input.workspaceId);
|
|
630
|
-
const
|
|
701
|
+
const { descriptor, seedFile } = resolveDescriptor(entry.rootPath, input);
|
|
702
|
+
return { index: ensureLanguageIndex(input.workspaceId, entry.rootPath, descriptor, seedFile), descriptor };
|
|
703
|
+
}
|
|
631
704
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
705
|
+
function ensureWorkspaceIndex(
|
|
706
|
+
workspaceId: WorkspaceId,
|
|
707
|
+
preferredSeedFile?: string,
|
|
708
|
+
): {
|
|
709
|
+
index: SymbolIndexPort;
|
|
710
|
+
descriptors: readonly LanguageServerDescriptor[];
|
|
711
|
+
sources: readonly IntelligenceProvenance[];
|
|
712
|
+
} {
|
|
713
|
+
const entry = registry.get(workspaceId);
|
|
714
|
+
if (!entry) throw new UnknownWorkspace(workspaceId);
|
|
715
|
+
if (!entry.rootPath) throw new SymbolQueryUnavailable(workspaceId);
|
|
716
|
+
const rootPath = entry.rootPath;
|
|
717
|
+
const preferredDescriptor = preferredSeedFile ? descriptorForPath(preferredSeedFile) : undefined;
|
|
718
|
+
if (preferredSeedFile && !preferredDescriptor) throw new UnsupportedLanguage(preferredSeedFile);
|
|
719
|
+
const discovered = [...discoverWorkspaceDescriptors(rootPath, LANGUAGE_SERVER_DESCRIPTORS)];
|
|
720
|
+
if (preferredDescriptor && preferredSeedFile && !discovered.some(({ descriptor }) => descriptor.languageId === preferredDescriptor.languageId)) {
|
|
721
|
+
discovered.push({ descriptor: preferredDescriptor, seedFile: preferredSeedFile });
|
|
722
|
+
}
|
|
723
|
+
if (discovered.length === 0) throw new UnsupportedLanguage(rootPath);
|
|
724
|
+
const indexes = discovered.map(({ descriptor, seedFile }) => ({
|
|
725
|
+
descriptor,
|
|
726
|
+
index: ensureLanguageIndex(workspaceId, rootPath, descriptor, preferredDescriptor?.languageId === descriptor.languageId ? preferredSeedFile : seedFile),
|
|
727
|
+
}));
|
|
728
|
+
const first = indexes[0];
|
|
729
|
+
let index: SymbolIndexPort;
|
|
730
|
+
if (indexes.length === 1 && first) {
|
|
731
|
+
index = first.index;
|
|
638
732
|
} else {
|
|
639
|
-
|
|
733
|
+
index = new PolyglotCodeIntelligenceIndex(indexes);
|
|
640
734
|
}
|
|
641
|
-
return { index:
|
|
735
|
+
return { index, descriptors: discovered.map(({ descriptor }) => descriptor), sources: indexes.map(({ index: source }) => source.provenance) };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function workspaceSourceExtensions(descriptors: readonly LanguageServerDescriptor[]): readonly string[] {
|
|
739
|
+
return Array.from(new Set(descriptors.flatMap((descriptor) => descriptor.extensions)));
|
|
642
740
|
}
|
|
643
741
|
|
|
644
742
|
/** Never spawns -- a caller deciding whether to enrich a result with LSP-backed info must not pay a cold-start cost just to check. With a path, checks that file's own language; without one, whether anything is warm for the workspace at all. */
|
|
@@ -660,9 +758,13 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
660
758
|
}
|
|
661
759
|
|
|
662
760
|
async function findSymbols(_registry: MutableRegistry, input: OperationInputs["workspace.findSymbols"]): Promise<OperationOutputs["workspace.findSymbols"]> {
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
761
|
+
assertBoundedSymbolQuery(input.query);
|
|
762
|
+
const maxResults = input.maxResults ?? 1_000;
|
|
763
|
+
if (!Number.isSafeInteger(maxResults) || maxResults < 1 || maxResults > MAX_SYMBOL_RESULTS) {
|
|
764
|
+
throw new TypeError(`maxResults must be a positive safe integer no greater than ${MAX_SYMBOL_RESULTS}`);
|
|
765
|
+
}
|
|
766
|
+
const { index } = ensureWorkspaceIndex(input.workspaceId, input.seedFile);
|
|
767
|
+
return findWorkspaceSymbols(index, input.query, { maxResults });
|
|
666
768
|
}
|
|
667
769
|
|
|
668
770
|
async function requireCodeIntelligence(input: {
|
|
@@ -681,7 +783,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
681
783
|
): Promise<OperationOutputs["workspace.goToDefinition"]> {
|
|
682
784
|
const { index } = await requireCodeIntelligence(input);
|
|
683
785
|
const locations = await goToDefinitionQuery(index, { path: input.path, line: input.line, character: input.character });
|
|
684
|
-
return { locations };
|
|
786
|
+
return { locations, provenance: index.provenance };
|
|
685
787
|
}
|
|
686
788
|
|
|
687
789
|
async function goToImplementation(
|
|
@@ -690,7 +792,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
690
792
|
): Promise<OperationOutputs["workspace.goToImplementation"]> {
|
|
691
793
|
const { index } = await requireCodeIntelligence(input);
|
|
692
794
|
const locations = await goToImplementationQuery(index, { path: input.path, line: input.line, character: input.character });
|
|
693
|
-
return { locations };
|
|
795
|
+
return { locations, provenance: index.provenance };
|
|
694
796
|
}
|
|
695
797
|
|
|
696
798
|
async function findReferences(
|
|
@@ -699,13 +801,13 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
699
801
|
): Promise<OperationOutputs["workspace.findReferences"]> {
|
|
700
802
|
const { index } = await requireCodeIntelligence(input);
|
|
701
803
|
const locations = await findReferencesQuery(index, { path: input.path, line: input.line, character: input.character }, input.includeDeclaration);
|
|
702
|
-
return { locations };
|
|
804
|
+
return { locations, provenance: index.provenance };
|
|
703
805
|
}
|
|
704
806
|
|
|
705
807
|
async function hover(_registry: MutableRegistry, input: OperationInputs["workspace.hover"]): Promise<OperationOutputs["workspace.hover"]> {
|
|
706
808
|
const { index } = await requireCodeIntelligence(input);
|
|
707
809
|
const hover = await hoverAt(index, { path: input.path, line: input.line, character: input.character });
|
|
708
|
-
return { hover };
|
|
810
|
+
return { hover, provenance: index.provenance };
|
|
709
811
|
}
|
|
710
812
|
|
|
711
813
|
async function documentSymbolsHandler(
|
|
@@ -714,7 +816,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
714
816
|
): Promise<OperationOutputs["workspace.documentSymbols"]> {
|
|
715
817
|
const { index } = await requireCodeIntelligence(input);
|
|
716
818
|
const symbols = await documentSymbolsQuery(index, input.path);
|
|
717
|
-
return { symbols };
|
|
819
|
+
return { symbols, provenance: index.provenance };
|
|
718
820
|
}
|
|
719
821
|
|
|
720
822
|
async function diagnosticsHandler(
|
|
@@ -723,7 +825,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
723
825
|
): Promise<OperationOutputs["workspace.diagnostics"]> {
|
|
724
826
|
const { index } = await requireCodeIntelligence(input);
|
|
725
827
|
const diagnostics = await diagnosticsQuery(index, input.path);
|
|
726
|
-
return { diagnostics };
|
|
828
|
+
return { diagnostics, provenance: index.provenance };
|
|
727
829
|
}
|
|
728
830
|
|
|
729
831
|
async function prepareCallHierarchyHandler(
|
|
@@ -732,7 +834,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
732
834
|
): Promise<OperationOutputs["workspace.prepareCallHierarchy"]> {
|
|
733
835
|
const { index } = await requireCodeIntelligence(input);
|
|
734
836
|
const items = await prepareCallHierarchyQuery(index, { path: input.path, line: input.line, character: input.character });
|
|
735
|
-
return { items };
|
|
837
|
+
return { items, provenance: index.provenance };
|
|
736
838
|
}
|
|
737
839
|
|
|
738
840
|
async function incomingCallsHandler(
|
|
@@ -741,7 +843,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
741
843
|
): Promise<OperationOutputs["workspace.incomingCalls"]> {
|
|
742
844
|
const { index } = await requireCodeIntelligence(input);
|
|
743
845
|
const calls = await incomingCallsQuery(index, { path: input.path, line: input.line, character: input.character });
|
|
744
|
-
return { calls };
|
|
846
|
+
return { calls, provenance: index.provenance };
|
|
745
847
|
}
|
|
746
848
|
|
|
747
849
|
async function outgoingCallsHandler(
|
|
@@ -750,27 +852,31 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
750
852
|
): Promise<OperationOutputs["workspace.outgoingCalls"]> {
|
|
751
853
|
const { index } = await requireCodeIntelligence(input);
|
|
752
854
|
const calls = await outgoingCallsQuery(index, { path: input.path, line: input.line, character: input.character });
|
|
753
|
-
return { calls };
|
|
855
|
+
return { calls, provenance: index.provenance };
|
|
754
856
|
}
|
|
755
857
|
|
|
756
858
|
async function populateSymbolGraphHandler(
|
|
757
859
|
_registry: MutableRegistry,
|
|
758
860
|
input: OperationInputs["workspace.populateSymbolGraph"],
|
|
759
861
|
): Promise<OperationOutputs["workspace.populateSymbolGraph"]> {
|
|
760
|
-
const
|
|
862
|
+
const workspaceIndex = ensureWorkspaceIndex(input.workspaceId);
|
|
863
|
+
if (!supportsCodeIntelligence(workspaceIndex.index)) throw new CodeIntelligenceUnavailable(input.workspaceId);
|
|
761
864
|
const entry = registry.get(input.workspaceId);
|
|
762
865
|
if (!entry?.rootPath) throw new SymbolQueryUnavailable(input.workspaceId);
|
|
763
866
|
const rootPath = entry.rootPath;
|
|
764
|
-
const
|
|
867
|
+
const extensions = workspaceSourceExtensions(workspaceIndex.descriptors);
|
|
868
|
+
const before = await deriveSourceManifest(rootPath, extensions, input.maxFiles, MAX_SOURCE_MANIFEST_BYTES);
|
|
765
869
|
const graph = ensureSymbolGraph(input.workspaceId);
|
|
766
|
-
const result = await populateSymbolGraphQuery(index, graph, before.absoluteFiles, input.maxSymbolsPerFile);
|
|
767
|
-
const after = await deriveSourceManifest(rootPath,
|
|
870
|
+
const result = await populateSymbolGraphQuery(workspaceIndex.index, graph, before.absoluteFiles, input.maxSymbolsPerFile);
|
|
871
|
+
const after = await deriveSourceManifest(rootPath, extensions, input.maxFiles, MAX_SOURCE_MANIFEST_BYTES);
|
|
768
872
|
if (after.fingerprint !== before.fingerprint) throw new WorkspaceChangedDuringPopulation(input.workspaceId);
|
|
769
873
|
await graph.setGeneration({
|
|
770
874
|
sourceFingerprint: after.fingerprint,
|
|
771
875
|
maxFiles: input.maxFiles,
|
|
772
876
|
maxSymbolsPerFile: input.maxSymbolsPerFile,
|
|
773
877
|
completedAt: Date.now(),
|
|
878
|
+
provenance: workspaceIndex.index.provenance,
|
|
879
|
+
sources: workspaceIndex.sources,
|
|
774
880
|
result,
|
|
775
881
|
});
|
|
776
882
|
return result;
|
|
@@ -795,10 +901,12 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
795
901
|
if (generation.maxFiles !== input.maxFiles || generation.maxSymbolsPerFile !== input.maxSymbolsPerFile) {
|
|
796
902
|
return { status: "not-cached", reason: "bounds-changed" };
|
|
797
903
|
}
|
|
798
|
-
const
|
|
904
|
+
const discovered = discoverWorkspaceDescriptors(entry.rootPath, LANGUAGE_SERVER_DESCRIPTORS);
|
|
905
|
+
if (discovered.length === 0) return { status: "not-cached", reason: "source-changed" };
|
|
906
|
+
const extensions = workspaceSourceExtensions(discovered.map(({ descriptor }) => descriptor));
|
|
799
907
|
let currentFingerprint: string;
|
|
800
908
|
try {
|
|
801
|
-
currentFingerprint = (await deriveSourceManifest(entry.rootPath,
|
|
909
|
+
currentFingerprint = (await deriveSourceManifest(entry.rootPath, extensions, input.maxFiles, MAX_SOURCE_MANIFEST_BYTES)).fingerprint;
|
|
802
910
|
} catch {
|
|
803
911
|
return { status: "not-cached", reason: "source-changed" };
|
|
804
912
|
}
|
|
@@ -920,6 +1028,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
920
1028
|
"workspace.gitLog": gitLogHandler,
|
|
921
1029
|
"workspace.gitDiff": gitDiffHandler,
|
|
922
1030
|
"repo.fetch": repoFetchHandler,
|
|
1031
|
+
"package.resolveSource": packageSourceHandler,
|
|
923
1032
|
"workspace.searchText": searchTextHandler,
|
|
924
1033
|
"search.symbols": crossFindSymbols,
|
|
925
1034
|
"search.text": crossSearchText,
|