@danypops/lector 0.1.6 → 0.1.8

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.
Files changed (35) hide show
  1. package/README.md +9 -7
  2. package/package.json +5 -1
  3. package/src/adapters/fallback-code-intelligence-index.ts +73 -0
  4. package/src/adapters/find-source-files.ts +1 -1
  5. package/src/adapters/git-repo-fetcher.ts +154 -37
  6. package/src/adapters/lsp/json-rpc-stream.ts +52 -2
  7. package/src/adapters/lsp/language-server-process.ts +54 -10
  8. package/src/adapters/lsp/lsp-symbol-index.ts +133 -32
  9. package/src/adapters/normalize-npm-repository.ts +77 -0
  10. package/src/adapters/npm-lockfile-version-resolver.ts +519 -0
  11. package/src/adapters/npm-package-source-resolver.ts +322 -0
  12. package/src/adapters/npm-registry-client.ts +304 -0
  13. package/src/adapters/sqlite-symbol-graph.ts +47 -3
  14. package/src/adapters/tree-sitter/typescript-tree-sitter-symbol-index.ts +61 -11
  15. package/src/adapters/typescript-compiler-symbol-index.ts +129 -0
  16. package/src/cli.ts +87 -23
  17. package/src/daemon.ts +4 -0
  18. package/src/domain/find-workspace-symbols.ts +4 -3
  19. package/src/domain/installed-package-version.ts +66 -0
  20. package/src/domain/intelligence-provenance.ts +19 -0
  21. package/src/domain/language-server-descriptor.ts +18 -0
  22. package/src/domain/npm-package-metadata.ts +26 -0
  23. package/src/domain/package-source.ts +143 -0
  24. package/src/domain/repo-fetch-result.ts +33 -1
  25. package/src/domain/resolve-package-source.ts +204 -0
  26. package/src/domain/symbol-graph-generation.ts +3 -0
  27. package/src/domain/symbol-query.ts +16 -0
  28. package/src/domain/workspace-symbol.ts +8 -0
  29. package/src/index.ts +61 -4
  30. package/src/ports/installed-package-version-resolver-port.ts +5 -0
  31. package/src/ports/npm-registry-port.ts +5 -0
  32. package/src/ports/package-source-resolver-port.ts +5 -0
  33. package/src/ports/repo-fetcher-port.ts +2 -2
  34. package/src/ports/symbol-index-port.ts +4 -2
  35. package/src/service.ts +89 -25
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { type ClosableIntelligenceIndex, FallbackCodeIntelligenceIndex } from "./adapters/fallback-code-intelligence-index.ts";
1
2
  export { GitRepoFetcher, type GitRepoFetcherOptions } from "./adapters/git-repo-fetcher.ts";
2
3
  export { InMemoryContentCache } from "./adapters/in-memory-content-cache.ts";
3
4
  export { InMemorySearchCache, type InMemorySearchCacheOptions } from "./adapters/in-memory-search-cache.ts";
@@ -6,11 +7,25 @@ export { InMemoryWorkspace } from "./adapters/in-memory-workspace.ts";
6
7
  export { LocalFilesystemWorkspace, PathEscapesWorkspaceRoot } from "./adapters/local-filesystem-workspace.ts";
7
8
  export { LocalGit } from "./adapters/local-git.ts";
8
9
  export {
10
+ LanguageServerCapacityExceeded,
9
11
  LanguageServerProcess,
10
12
  LanguageServerProcessExited,
11
13
  LanguageServerRequestTimedOut,
12
14
  } from "./adapters/lsp/language-server-process.ts";
13
- export { LspSymbolIndex } from "./adapters/lsp/lsp-symbol-index.ts";
15
+ export { LanguageFileLimitExceeded, LspSymbolIndex, type LspSymbolIndexOptions } from "./adapters/lsp/lsp-symbol-index.ts";
16
+ export { InvalidInstalledPackageVersionRequest, NpmLockfileVersionResolver } from "./adapters/npm-lockfile-version-resolver.ts";
17
+ export { NpmPackageSourceResolver, type NpmPackageSourceResolverOptions } from "./adapters/npm-package-source-resolver.ts";
18
+ export {
19
+ DEFAULT_NPM_REGISTRY,
20
+ InvalidNpmRegistryRequest,
21
+ NpmPackageNotFound,
22
+ NpmRegistryAuthenticationRequired,
23
+ NpmRegistryClient,
24
+ type NpmRegistryClientOptions,
25
+ NpmRegistryRequestFailed,
26
+ NpmRegistryResponseLimitExceeded,
27
+ NpmVersionNotFound,
28
+ } from "./adapters/npm-registry-client.ts";
14
29
  export { ReadOnlyWorkspace, WorkspaceIsReadOnly } from "./adapters/read-only-workspace.ts";
15
30
  export { RipgrepTextSearch } from "./adapters/ripgrep-text-search.ts";
16
31
  export { deriveSourceManifest, type SourceManifest, SourceManifestLimitExceeded } from "./adapters/source-manifest.ts";
@@ -18,7 +33,8 @@ export { SqliteContentCache } from "./adapters/sqlite-content-cache.ts";
18
33
  export { SqliteSearchCache, type SqliteSearchCacheOptions } from "./adapters/sqlite-search-cache.ts";
19
34
  export { SqliteSymbolGraph } from "./adapters/sqlite-symbol-graph.ts";
20
35
  export { TieredSearchCache } from "./adapters/tiered-search-cache.ts";
21
- export { TreeSitterSymbolIndex } from "./adapters/tree-sitter/typescript-tree-sitter-symbol-index.ts";
36
+ export { TreeSitterSymbolIndex, type TreeSitterSymbolIndexOptions } from "./adapters/tree-sitter/typescript-tree-sitter-symbol-index.ts";
37
+ export { TypeScriptCompilerSymbolIndex, type TypeScriptCompilerSymbolIndexOptions } from "./adapters/typescript-compiler-symbol-index.ts";
22
38
  export {
23
39
  type ConnectLectorClientOptions,
24
40
  connectLectorClient,
@@ -64,6 +80,19 @@ export { goToImplementation } from "./domain/go-to-implementation.ts";
64
80
  export type { Hover } from "./domain/hover.ts";
65
81
  export { hoverAt } from "./domain/hover-at.ts";
66
82
  export { incomingCalls } from "./domain/incoming-calls.ts";
83
+ export type {
84
+ AmbiguousInstalledPackageVersion,
85
+ InstalledPackageEvidence,
86
+ InstalledPackageVersionBounds,
87
+ InstalledPackageVersionCandidate,
88
+ InstalledPackageVersionOutcome,
89
+ InstalledPackageVersionRequest,
90
+ JavaScriptPackageManager,
91
+ OversizedInstalledPackageVersion,
92
+ ResolvedInstalledPackageVersion,
93
+ UnavailableInstalledPackageVersion,
94
+ } from "./domain/installed-package-version.ts";
95
+ export type { IntelligenceFidelity, IntelligenceProvenance, ProvenancedResult, SymbolSearchBounds } from "./domain/intelligence-provenance.ts";
67
96
  export {
68
97
  descriptorForExtension,
69
98
  LANGUAGE_SERVER_DESCRIPTORS,
@@ -71,26 +100,53 @@ export {
71
100
  PYTHON_DESCRIPTOR,
72
101
  TYPESCRIPT_DESCRIPTOR,
73
102
  } from "./domain/language-server-descriptor.ts";
103
+ export type { NpmPackageVersionMetadata, NpmRegistryBounds, NpmRegistryVersionRequest, NpmRepositoryMetadata } from "./domain/npm-package-metadata.ts";
74
104
  export { outgoingCalls } from "./domain/outgoing-calls.ts";
105
+ export type {
106
+ AmbiguousPackageSource,
107
+ MismatchedPackageSource,
108
+ OversizedPackageSource,
109
+ PackageCoordinateRequest,
110
+ PackageEcosystem,
111
+ PackageRepositoryIdentity,
112
+ PackageSourceBounds,
113
+ PackageSourceCandidate,
114
+ PackageSourceOperationResult,
115
+ PackageSourceOutcome,
116
+ PackageSourceRequest,
117
+ PackageSourceVerification,
118
+ PackageSourceVerificationMethod,
119
+ PackageSourceWorkspace,
120
+ ResolvedPackageCoordinate,
121
+ UnauthenticatedPackageSource,
122
+ UnavailablePackageSource,
123
+ VerifiedPackageSource,
124
+ } from "./domain/package-source.ts";
125
+ export { DEFAULT_PACKAGE_SOURCE_BOUNDS } from "./domain/package-source.ts";
75
126
  export { type PopulateSymbolGraphResult, populateSymbolGraph } from "./domain/populate-symbol-graph.ts";
76
127
  export { prepareCallHierarchy } from "./domain/prepare-call-hierarchy.ts";
77
128
  export { raceWorkspaceQuery } from "./domain/race-workspace-query.ts";
78
129
  export { type RawRead, rawRead, WorkspaceEntryNotFound } from "./domain/raw-read.ts";
79
130
  export { reachableSymbolsFrom } from "./domain/reachable-symbols-from.ts";
80
- export { RepoFetchFailed, type RepoFetchResult } from "./domain/repo-fetch-result.ts";
131
+ export { RepoFetchCapacityExceeded, RepoFetchFailed, RepoFetchLimitExceeded, type RepoFetchPolicy, type RepoFetchResult } from "./domain/repo-fetch-result.ts";
81
132
  export type { RepoReference } from "./domain/repo-reference.ts";
133
+ export { InvalidPackageSourceContract, resolvePackageSource } from "./domain/resolve-package-source.ts";
82
134
  export { deriveSearchCacheKey, type SearchCacheKey } from "./domain/search-cache-key.ts";
83
135
  export { searchText } from "./domain/search-text.ts";
84
136
  export { symbolEdgesFrom } from "./domain/symbol-edges-from.ts";
85
137
  export { symbolEdgesTo } from "./domain/symbol-edges-to.ts";
86
138
  export type { SymbolGraphGeneration, WorkspaceCacheStatus } from "./domain/symbol-graph-generation.ts";
87
139
  export { deriveSymbolNodeId, type SymbolNodeId } from "./domain/symbol-node-id.ts";
140
+ export { assertBoundedSymbolQuery, InvalidSymbolQuery, MAX_SYMBOL_QUERY_BYTES } from "./domain/symbol-query.ts";
88
141
  export type { TextSearchMatch, TextSearchResult } from "./domain/text-search-result.ts";
89
142
  export type { WorkspaceQueryOutcome, WorkspaceQueryStatus } from "./domain/workspace-query-outcome.ts";
90
- export type { WorkspaceLocation, WorkspaceSymbol } from "./domain/workspace-symbol.ts";
143
+ export type { SymbolSearchResult, WorkspaceLocation, WorkspaceSymbol } from "./domain/workspace-symbol.ts";
91
144
  export type { CodeIntelligencePort } from "./ports/code-intelligence-port.ts";
92
145
  export type { ContentCacheEntry, ContentCachePort, ContentSymbol } from "./ports/content-cache-port.ts";
93
146
  export type { GitPort } from "./ports/git-port.ts";
147
+ export type { InstalledPackageVersionResolverPort } from "./ports/installed-package-version-resolver-port.ts";
148
+ export type { NpmRegistryPort } from "./ports/npm-registry-port.ts";
149
+ export type { PackageSourceResolverPort } from "./ports/package-source-resolver-port.ts";
94
150
  export type { RepoFetcherPort } from "./ports/repo-fetcher-port.ts";
95
151
  export type { SearchCachePort } from "./ports/search-cache-port.ts";
96
152
  export type { SymbolEdgeKind, SymbolGraphPort, SymbolNode } from "./ports/symbol-graph-port.ts";
@@ -111,6 +167,7 @@ export {
111
167
  type OperationInputs,
112
168
  type OperationName,
113
169
  type OperationOutputs,
170
+ PackageSourceResolverNotConfigured,
114
171
  RepoFetcherNotConfigured,
115
172
  SymbolQueryUnavailable,
116
173
  UnknownWorkspace,
@@ -0,0 +1,5 @@
1
+ import type { InstalledPackageVersionBounds, InstalledPackageVersionOutcome, InstalledPackageVersionRequest } from "../domain/installed-package-version.ts";
2
+
3
+ export interface InstalledPackageVersionResolverPort {
4
+ resolve(request: InstalledPackageVersionRequest, bounds: InstalledPackageVersionBounds): Promise<InstalledPackageVersionOutcome>;
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { NpmPackageVersionMetadata, NpmRegistryBounds, NpmRegistryVersionRequest } from "../domain/npm-package-metadata.ts";
2
+
3
+ export interface NpmRegistryPort {
4
+ fetchVersion(request: NpmRegistryVersionRequest, bounds: NpmRegistryBounds): Promise<NpmPackageVersionMetadata>;
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { PackageSourceBounds, PackageSourceOutcome, PackageSourceRequest } from "../domain/package-source.ts";
2
+
3
+ export interface PackageSourceResolverPort {
4
+ resolve(request: PackageSourceRequest, bounds: PackageSourceBounds): Promise<PackageSourceOutcome>;
5
+ }
@@ -1,4 +1,4 @@
1
- import type { RepoFetchResult } from "../domain/repo-fetch-result.ts";
1
+ import type { RepoFetchPolicy, RepoFetchResult } from "../domain/repo-fetch-result.ts";
2
2
  import type { RepoReference } from "../domain/repo-reference.ts";
3
3
 
4
4
  /**
@@ -7,5 +7,5 @@ import type { RepoReference } from "../domain/repo-reference.ts";
7
7
  * directory to read and analyze, not one the caller owns.
8
8
  */
9
9
  export interface RepoFetcherPort {
10
- fetch(reference: RepoReference): Promise<RepoFetchResult>;
10
+ fetch(reference: RepoReference, policy?: RepoFetchPolicy): Promise<RepoFetchResult>;
11
11
  }
@@ -1,4 +1,5 @@
1
- import type { WorkspaceSymbol } from "../domain/workspace-symbol.ts";
1
+ import type { IntelligenceProvenance, SymbolSearchBounds } from "../domain/intelligence-provenance.ts";
2
+ import type { SymbolSearchResult } from "../domain/workspace-symbol.ts";
2
3
 
3
4
  /**
4
5
  * SymbolIndexPort -- the role a driven adapter plays for symbol queries:
@@ -7,5 +8,6 @@ import type { WorkspaceSymbol } from "../domain/workspace-symbol.ts";
7
8
  * this same interface.
8
9
  */
9
10
  export interface SymbolIndexPort {
10
- findSymbols(query: string): Promise<WorkspaceSymbol[]>;
11
+ readonly provenance: IntelligenceProvenance;
12
+ findSymbols(query: string, bounds?: SymbolSearchBounds): Promise<SymbolSearchResult>;
11
13
  }
package/src/service.ts CHANGED
@@ -1,15 +1,21 @@
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
9
  import { discoverWorkspaceDescriptor } 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";
10
14
  import { ReadOnlyWorkspace } from "./adapters/read-only-workspace.ts";
11
15
  import { RipgrepTextSearch } from "./adapters/ripgrep-text-search.ts";
12
16
  import { deriveSourceManifest } from "./adapters/source-manifest.ts";
17
+ import { TreeSitterSymbolIndex } from "./adapters/tree-sitter/typescript-tree-sitter-symbol-index.ts";
18
+ import { TypeScriptCompilerSymbolIndex } from "./adapters/typescript-compiler-symbol-index.ts";
13
19
  import { BoundedJobExecutor, type JobSnapshot } from "./domain/bounded-job-executor.ts";
14
20
  import type { CallHierarchyEntry, IncomingCall, OutgoingCall } from "./domain/call-hierarchy.ts";
15
21
  import type { Diagnostic } from "./domain/diagnostic.ts";
@@ -27,8 +33,10 @@ import { goToImplementation as goToImplementationQuery } from "./domain/go-to-im
27
33
  import type { Hover } from "./domain/hover.ts";
28
34
  import { hoverAt } from "./domain/hover-at.ts";
29
35
  import { incomingCalls as incomingCallsQuery } from "./domain/incoming-calls.ts";
36
+ import type { IntelligenceProvenance } from "./domain/intelligence-provenance.ts";
30
37
  import { descriptorForPath, LANGUAGE_SERVER_DESCRIPTORS, type LanguageServerDescriptor } from "./domain/language-server-descriptor.ts";
31
38
  import { outgoingCalls as outgoingCallsQuery } from "./domain/outgoing-calls.ts";
39
+ import type { PackageSourceBounds, PackageSourceOperationResult, PackageSourceRequest } from "./domain/package-source.ts";
32
40
  import { type PopulateSymbolGraphResult, populateSymbolGraph as populateSymbolGraphQuery } from "./domain/populate-symbol-graph.ts";
33
41
  import { prepareCallHierarchy as prepareCallHierarchyQuery } from "./domain/prepare-call-hierarchy.ts";
34
42
  import { raceWorkspaceQuery } from "./domain/race-workspace-query.ts";
@@ -36,16 +44,19 @@ import { type RawRead, rawRead, WorkspaceEntryNotFound } from "./domain/raw-read
36
44
  import { reachableSymbolsFrom } from "./domain/reachable-symbols-from.ts";
37
45
  import type { RepoFetchResult } from "./domain/repo-fetch-result.ts";
38
46
  import type { RepoReference } from "./domain/repo-reference.ts";
47
+ import { resolvePackageSource } from "./domain/resolve-package-source.ts";
39
48
  import { searchText as searchTextQuery } from "./domain/search-text.ts";
40
49
  import { symbolEdgesFrom } from "./domain/symbol-edges-from.ts";
41
50
  import { symbolEdgesTo } from "./domain/symbol-edges-to.ts";
42
51
  import type { WorkspaceCacheStatus } from "./domain/symbol-graph-generation.ts";
43
52
  import { deriveSymbolNodeId } from "./domain/symbol-node-id.ts";
53
+ import { assertBoundedSymbolQuery } from "./domain/symbol-query.ts";
44
54
  import type { TextSearchResult } from "./domain/text-search-result.ts";
45
55
  import type { WorkspaceQueryOutcome } from "./domain/workspace-query-outcome.ts";
46
- import type { WorkspaceLocation, WorkspaceSymbol } from "./domain/workspace-symbol.ts";
56
+ import type { SymbolSearchResult, WorkspaceLocation } from "./domain/workspace-symbol.ts";
47
57
  import type { CodeIntelligencePort } from "./ports/code-intelligence-port.ts";
48
58
  import type { GitPort } from "./ports/git-port.ts";
59
+ import type { PackageSourceResolverPort } from "./ports/package-source-resolver-port.ts";
49
60
  import type { RepoFetcherPort } from "./ports/repo-fetcher-port.ts";
50
61
  import type { SearchCachePort } from "./ports/search-cache-port.ts";
51
62
  import type { SymbolEdgeKind, SymbolGraphPort, SymbolNode } from "./ports/symbol-graph-port.ts";
@@ -128,6 +139,13 @@ export class RepoFetcherNotConfigured extends Error {
128
139
  }
129
140
  }
130
141
 
142
+ export class PackageSourceResolverNotConfigured extends Error {
143
+ constructor() {
144
+ super("package.resolveSource requires a service constructed with repository fetching");
145
+ this.name = "PackageSourceResolverNotConfigured";
146
+ }
147
+ }
148
+
131
149
  export class UnsupportedJobOperation extends Error {
132
150
  constructor(readonly operation: string) {
133
151
  super(`operation "${operation}" cannot run as a background job; supported operations: workspace.populateSymbolGraph`);
@@ -185,6 +203,7 @@ export type OperationName =
185
203
  | "workspace.gitLog"
186
204
  | "workspace.gitDiff"
187
205
  | "repo.fetch"
206
+ | "package.resolveSource"
188
207
  | "workspace.searchText"
189
208
  | "search.symbols"
190
209
  | "search.text"
@@ -215,6 +234,7 @@ export const OPERATION_NAMES: readonly OperationName[] = [
215
234
  "workspace.gitLog",
216
235
  "workspace.gitDiff",
217
236
  "repo.fetch",
237
+ "package.resolveSource",
218
238
  "workspace.searchText",
219
239
  "search.symbols",
220
240
  "search.text",
@@ -234,7 +254,7 @@ export interface OperationInputs {
234
254
  "workspace.rawRead": { workspaceId: WorkspaceId; path: string };
235
255
  "workspace.exactEdit": { workspaceId: WorkspaceId } & ExpectedHashEdit;
236
256
  "workspace.registerPath": { path: string };
237
- "workspace.findSymbols": { workspaceId: WorkspaceId; query: string; seedFile?: string };
257
+ "workspace.findSymbols": { workspaceId: WorkspaceId; query: string; seedFile?: string; maxResults?: number };
238
258
  "workspace.goToDefinition": WorkspacePosition;
239
259
  "workspace.goToImplementation": WorkspacePosition;
240
260
  "workspace.findReferences": WorkspacePosition & { includeDeclaration: boolean };
@@ -254,6 +274,7 @@ export interface OperationInputs {
254
274
  "workspace.gitLog": { workspaceId: WorkspaceId; maxCount: number };
255
275
  "workspace.gitDiff": { workspaceId: WorkspaceId; ref?: string; maxBytes: number };
256
276
  "repo.fetch": RepoReference;
277
+ "package.resolveSource": { request: PackageSourceRequest; bounds: PackageSourceBounds };
257
278
  "workspace.searchText": { workspaceId: WorkspaceId; query: string; maxMatches: number; maxBytes: number };
258
279
  /**
259
280
  * No single workspaceId -- fans out across several at once, unlike every other findSymbols-
@@ -275,20 +296,22 @@ export interface OperationInputs {
275
296
  "job.status": { jobId: string };
276
297
  }
277
298
 
299
+ type Provenanced<T> = T & { readonly provenance: IntelligenceProvenance };
300
+
278
301
  export interface OperationOutputs {
279
302
  "workspace.rawRead": RawRead;
280
303
  "workspace.exactEdit": EditOutcome;
281
304
  "workspace.registerPath": { workspaceId: WorkspaceId; created: boolean };
282
- "workspace.findSymbols": { symbols: readonly WorkspaceSymbol[] };
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[] };
305
+ "workspace.findSymbols": SymbolSearchResult;
306
+ "workspace.goToDefinition": Provenanced<{ locations: readonly WorkspaceLocation[] }>;
307
+ "workspace.goToImplementation": Provenanced<{ locations: readonly WorkspaceLocation[] }>;
308
+ "workspace.findReferences": Provenanced<{ locations: readonly WorkspaceLocation[] }>;
309
+ "workspace.hover": Provenanced<{ hover: Hover | undefined }>;
310
+ "workspace.documentSymbols": Provenanced<{ symbols: readonly DocumentSymbolEntry[] }>;
311
+ "workspace.diagnostics": Provenanced<{ diagnostics: readonly Diagnostic[] }>;
312
+ "workspace.prepareCallHierarchy": Provenanced<{ items: readonly CallHierarchyEntry[] }>;
313
+ "workspace.incomingCalls": Provenanced<{ calls: readonly IncomingCall[] }>;
314
+ "workspace.outgoingCalls": Provenanced<{ calls: readonly OutgoingCall[] }>;
292
315
  "workspace.populateSymbolGraph": { filesProcessed: number; symbolsProcessed: number; nodesAdded: number; edgesAdded: number };
293
316
  "workspace.reachableFrom": { symbols: readonly SymbolNode[] };
294
317
  "workspace.symbolEdgesFrom": { symbols: readonly SymbolNode[] };
@@ -299,8 +322,9 @@ export interface OperationOutputs {
299
322
  "workspace.gitLog": { entries: readonly GitLogEntry[] };
300
323
  "workspace.gitDiff": GitDiffResult;
301
324
  "repo.fetch": RepoFetchResult & { workspaceId: WorkspaceId };
325
+ "package.resolveSource": PackageSourceOperationResult;
302
326
  "workspace.searchText": TextSearchResult;
303
- "search.symbols": { results: readonly WorkspaceQueryOutcome<{ symbols: readonly WorkspaceSymbol[] }>[] };
327
+ "search.symbols": { results: readonly WorkspaceQueryOutcome<SymbolSearchResult>[] };
304
328
  "search.text": { results: readonly WorkspaceQueryOutcome<TextSearchResult>[] };
305
329
  "job.submit": { job: JobSnapshot<PopulateSymbolGraphResult> };
306
330
  "job.status": { job: JobSnapshot<PopulateSymbolGraphResult> };
@@ -366,6 +390,8 @@ export interface LectorServiceOptions {
366
390
  createGitPort?: (rootPath: string) => GitPort;
367
391
  /** 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
392
  createRepoFetcher?: () => RepoFetcherPort;
393
+ /** Override package-source resolution. With a repo fetcher configured, the default composes npm lockfiles, registry metadata, and exact Git fetching. */
394
+ createPackageSourceResolver?: () => PackageSourceResolverPort;
369
395
  /** 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
396
  createTextSearch?: () => TextSearchPort;
371
397
  /** 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. */
@@ -418,6 +444,7 @@ async function registerPath(registry: MutableRegistry, input: OperationInputs["w
418
444
  */
419
445
  const DEFAULT_CROSS_WORKSPACE_TIMEOUT_MS = 3000;
420
446
  const MAX_INITIAL_JOB_WAIT_MS = 30_000;
447
+ const MAX_SYMBOL_RESULTS = 5_000;
421
448
  const MAX_SOURCE_MANIFEST_BYTES = 50 * 1024 * 1024;
422
449
 
423
450
  /**
@@ -455,7 +482,11 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
455
482
  const symbolIndexes = new Map<string, { index: ClosableSymbolIndex; workspaceId: WorkspaceId; lastUsedAt: number }>();
456
483
  const createSymbolIndex =
457
484
  options.createSymbolIndex ??
458
- ((rootPath: string, descriptor: LanguageServerDescriptor, seedFile?: string) => new LspSymbolIndex(rootPath, descriptor, seedFile));
485
+ ((rootPath: string, descriptor: LanguageServerDescriptor, seedFile?: string) => {
486
+ const semantic = new LspSymbolIndex(rootPath, descriptor, seedFile);
487
+ if (descriptor.languageId !== "typescript") return semantic;
488
+ return new FallbackCodeIntelligenceIndex(semantic, [new TypeScriptCompilerSymbolIndex(rootPath), new TreeSitterSymbolIndex(rootPath)]);
489
+ });
459
490
  function symbolIndexKey(workspaceId: WorkspaceId, languageId: string): string {
460
491
  return `${workspaceId}:${languageId}`;
461
492
  }
@@ -481,6 +512,11 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
481
512
  // every time, wastefully, and would risk losing the in-memory LRU's recency ordering
482
513
  // between calls for no benefit (the index itself is what makes rehydration correct at all).
483
514
  const repoFetcher = options.createRepoFetcher?.();
515
+ const packageSourceResolver =
516
+ options.createPackageSourceResolver?.() ??
517
+ (repoFetcher
518
+ ? new NpmPackageSourceResolver({ versions: new NpmLockfileVersionResolver(), registry: new NpmRegistryClient(), repositories: repoFetcher })
519
+ : undefined);
484
520
  const textSearch = options.createTextSearch?.() ?? new RipgrepTextSearch();
485
521
  const searchCache = options.createSearchCache?.() ?? new InMemorySearchCache();
486
522
 
@@ -521,6 +557,28 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
521
557
  return { workspaceId, ...result };
522
558
  }
523
559
 
560
+ async function packageSourceHandler(
561
+ registry: MutableRegistry,
562
+ input: OperationInputs["package.resolveSource"],
563
+ ): Promise<OperationOutputs["package.resolveSource"]> {
564
+ if (!packageSourceResolver) throw new PackageSourceResolverNotConfigured();
565
+ const outcome = await resolvePackageSource(packageSourceResolver, input.request, input.bounds);
566
+ if (outcome.status !== "verified") return { outcome, workspaceId: null };
567
+ const absolutePath = resolve(outcome.workspace.cachePath);
568
+ let sourceStats: Awaited<ReturnType<typeof stat>>;
569
+ try {
570
+ sourceStats = await stat(absolutePath);
571
+ } catch {
572
+ throw new InvalidWorkspaceRoot(absolutePath, "verified package source does not exist or is not accessible");
573
+ }
574
+ if (!sourceStats.isDirectory()) throw new InvalidWorkspaceRoot(absolutePath, "verified package source is not a directory");
575
+ const workspaceId = deriveWorkspaceId(absolutePath);
576
+ if (!registry.has(workspaceId)) {
577
+ registry.set(workspaceId, { port: new ReadOnlyWorkspace(new LocalFilesystemWorkspace(absolutePath)), rootPath: absolutePath, origin: "remote" });
578
+ }
579
+ return { outcome, workspaceId };
580
+ }
581
+
524
582
  async function searchTextHandler(
525
583
  registry: MutableRegistry,
526
584
  input: OperationInputs["workspace.searchText"],
@@ -660,9 +718,13 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
660
718
  }
661
719
 
662
720
  async function findSymbols(_registry: MutableRegistry, input: OperationInputs["workspace.findSymbols"]): Promise<OperationOutputs["workspace.findSymbols"]> {
721
+ assertBoundedSymbolQuery(input.query);
722
+ const maxResults = input.maxResults ?? 1_000;
723
+ if (!Number.isSafeInteger(maxResults) || maxResults < 1 || maxResults > MAX_SYMBOL_RESULTS) {
724
+ throw new TypeError(`maxResults must be a positive safe integer no greater than ${MAX_SYMBOL_RESULTS}`);
725
+ }
663
726
  const { index } = await ensureWarmIndex(input);
664
- const symbols = await findWorkspaceSymbols(index, input.query);
665
- return { symbols };
727
+ return findWorkspaceSymbols(index, input.query, { maxResults });
666
728
  }
667
729
 
668
730
  async function requireCodeIntelligence(input: {
@@ -681,7 +743,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
681
743
  ): Promise<OperationOutputs["workspace.goToDefinition"]> {
682
744
  const { index } = await requireCodeIntelligence(input);
683
745
  const locations = await goToDefinitionQuery(index, { path: input.path, line: input.line, character: input.character });
684
- return { locations };
746
+ return { locations, provenance: index.provenance };
685
747
  }
686
748
 
687
749
  async function goToImplementation(
@@ -690,7 +752,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
690
752
  ): Promise<OperationOutputs["workspace.goToImplementation"]> {
691
753
  const { index } = await requireCodeIntelligence(input);
692
754
  const locations = await goToImplementationQuery(index, { path: input.path, line: input.line, character: input.character });
693
- return { locations };
755
+ return { locations, provenance: index.provenance };
694
756
  }
695
757
 
696
758
  async function findReferences(
@@ -699,13 +761,13 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
699
761
  ): Promise<OperationOutputs["workspace.findReferences"]> {
700
762
  const { index } = await requireCodeIntelligence(input);
701
763
  const locations = await findReferencesQuery(index, { path: input.path, line: input.line, character: input.character }, input.includeDeclaration);
702
- return { locations };
764
+ return { locations, provenance: index.provenance };
703
765
  }
704
766
 
705
767
  async function hover(_registry: MutableRegistry, input: OperationInputs["workspace.hover"]): Promise<OperationOutputs["workspace.hover"]> {
706
768
  const { index } = await requireCodeIntelligence(input);
707
769
  const hover = await hoverAt(index, { path: input.path, line: input.line, character: input.character });
708
- return { hover };
770
+ return { hover, provenance: index.provenance };
709
771
  }
710
772
 
711
773
  async function documentSymbolsHandler(
@@ -714,7 +776,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
714
776
  ): Promise<OperationOutputs["workspace.documentSymbols"]> {
715
777
  const { index } = await requireCodeIntelligence(input);
716
778
  const symbols = await documentSymbolsQuery(index, input.path);
717
- return { symbols };
779
+ return { symbols, provenance: index.provenance };
718
780
  }
719
781
 
720
782
  async function diagnosticsHandler(
@@ -723,7 +785,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
723
785
  ): Promise<OperationOutputs["workspace.diagnostics"]> {
724
786
  const { index } = await requireCodeIntelligence(input);
725
787
  const diagnostics = await diagnosticsQuery(index, input.path);
726
- return { diagnostics };
788
+ return { diagnostics, provenance: index.provenance };
727
789
  }
728
790
 
729
791
  async function prepareCallHierarchyHandler(
@@ -732,7 +794,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
732
794
  ): Promise<OperationOutputs["workspace.prepareCallHierarchy"]> {
733
795
  const { index } = await requireCodeIntelligence(input);
734
796
  const items = await prepareCallHierarchyQuery(index, { path: input.path, line: input.line, character: input.character });
735
- return { items };
797
+ return { items, provenance: index.provenance };
736
798
  }
737
799
 
738
800
  async function incomingCallsHandler(
@@ -741,7 +803,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
741
803
  ): Promise<OperationOutputs["workspace.incomingCalls"]> {
742
804
  const { index } = await requireCodeIntelligence(input);
743
805
  const calls = await incomingCallsQuery(index, { path: input.path, line: input.line, character: input.character });
744
- return { calls };
806
+ return { calls, provenance: index.provenance };
745
807
  }
746
808
 
747
809
  async function outgoingCallsHandler(
@@ -750,7 +812,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
750
812
  ): Promise<OperationOutputs["workspace.outgoingCalls"]> {
751
813
  const { index } = await requireCodeIntelligence(input);
752
814
  const calls = await outgoingCallsQuery(index, { path: input.path, line: input.line, character: input.character });
753
- return { calls };
815
+ return { calls, provenance: index.provenance };
754
816
  }
755
817
 
756
818
  async function populateSymbolGraphHandler(
@@ -771,6 +833,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
771
833
  maxFiles: input.maxFiles,
772
834
  maxSymbolsPerFile: input.maxSymbolsPerFile,
773
835
  completedAt: Date.now(),
836
+ provenance: index.provenance,
774
837
  result,
775
838
  });
776
839
  return result;
@@ -920,6 +983,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
920
983
  "workspace.gitLog": gitLogHandler,
921
984
  "workspace.gitDiff": gitDiffHandler,
922
985
  "repo.fetch": repoFetchHandler,
986
+ "package.resolveSource": packageSourceHandler,
923
987
  "workspace.searchText": searchTextHandler,
924
988
  "search.symbols": crossFindSymbols,
925
989
  "search.text": crossSearchText,