@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/cli.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { InMemoryWorkspace } from "./adapters/in-memory-workspace.ts";
|
|
8
8
|
import { LocalFilesystemWorkspace } from "./adapters/local-filesystem-workspace.ts";
|
|
@@ -11,6 +11,8 @@ import { LECTOR_PATH_NAMES } from "./constants.ts";
|
|
|
11
11
|
import { serveMain } from "./daemon.ts";
|
|
12
12
|
import type { JobSnapshot } from "./domain/bounded-job-executor.ts";
|
|
13
13
|
import type { ContentHash } from "./domain/content-hash.ts";
|
|
14
|
+
import { DEFAULT_PACKAGE_SOURCE_BOUNDS, type PackageSourceOperationResult } from "./domain/package-source.ts";
|
|
15
|
+
import type { SymbolSearchResult } from "./domain/workspace-symbol.ts";
|
|
14
16
|
import type { WorkspacePort } from "./ports/workspace-port.ts";
|
|
15
17
|
import type { WorkspaceId } from "./service.ts";
|
|
16
18
|
|
|
@@ -51,6 +53,8 @@ const USAGE = `Usage:
|
|
|
51
53
|
lector workspace git-diff <workspace-id> [--ref <ref>] --max-bytes <n> [--json]
|
|
52
54
|
lector workspace repo-fetch <owner>/<repo>[@ref] [--host <host>] [--json]
|
|
53
55
|
shallow-clones an external repo into a disk-bounded cache and registers it read-only
|
|
56
|
+
lector package source <project-dir> <package-name> [--version <exact-version>] [--registry <url>] [--json]
|
|
57
|
+
resolves an installed npm package to verified exact repository source and registers it read-only
|
|
54
58
|
lector workspace search-text <workspace-id> <query> --max-matches <n> --max-bytes <n> [--json]
|
|
55
59
|
lector search symbols <query> [--workspace <id>]... [--timeout-ms <n>] [--json]
|
|
56
60
|
lector search text <query> --max-matches <n> --max-bytes <n> [--workspace <id>]... [--timeout-ms <n>] [--json]
|
|
@@ -127,15 +131,26 @@ async function runWorkspaceRegister(dir: string | undefined, flags: string[]): P
|
|
|
127
131
|
console.log(hasFlag(flags, "--json") ? JSON.stringify(result) : `${result.workspaceId} (${result.created ? "created" : "already registered"})`);
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
function formatSymbolSources(result: SymbolSearchResult): readonly string[] {
|
|
135
|
+
return (result.sources ?? []).map((source) => {
|
|
136
|
+
const identity = `${source.provenance.languageId}: ${source.status} via ${source.provenance.backend}`;
|
|
137
|
+
if (source.status === "failed") return source.error ? `${identity} [${source.error.code}] ${source.error.message}` : identity;
|
|
138
|
+
return `${identity} (${source.symbolCount} symbol${source.symbolCount === 1 ? "" : "s"}${source.truncated ? ", truncated" : ""})`;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
130
142
|
async function runWorkspaceSymbols(workspaceId: string | undefined, query: string | undefined, flags: string[]): Promise<void> {
|
|
131
143
|
if (!workspaceId || !query) fail(USAGE);
|
|
132
144
|
const seedFile = flagValue(flags, "--seed-file"); // omit to auto-discover one
|
|
133
145
|
const client = await connectLectorClient();
|
|
134
|
-
const
|
|
146
|
+
const result = await client.call("workspace.findSymbols", { workspaceId, query, seedFile });
|
|
135
147
|
if (hasFlag(flags, "--json")) {
|
|
136
|
-
console.log(JSON.stringify(
|
|
148
|
+
console.log(JSON.stringify(result));
|
|
137
149
|
return;
|
|
138
150
|
}
|
|
151
|
+
const { symbols, provenance, truncated } = result;
|
|
152
|
+
console.log(`${provenance.fidelity} via ${provenance.backend}${truncated ? " (truncated)" : ""}`);
|
|
153
|
+
for (const source of formatSymbolSources(result)) console.log(source);
|
|
139
154
|
if (symbols.length === 0) {
|
|
140
155
|
console.log(`no symbols matched "${query}"`);
|
|
141
156
|
return;
|
|
@@ -154,16 +169,22 @@ function parsePosition(line: string | undefined, character: string | undefined):
|
|
|
154
169
|
return { line: parsedLine, character: parsedCharacter };
|
|
155
170
|
}
|
|
156
171
|
|
|
172
|
+
function formatIntelligenceSource(provenance: { fidelity: string; backend: string }): string {
|
|
173
|
+
return `${provenance.fidelity} via ${provenance.backend}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
157
176
|
async function runWorkspaceDefinition(workspaceId: string | undefined, path: string | undefined, rest: string[]): Promise<void> {
|
|
158
177
|
if (!workspaceId || !path) fail(USAGE);
|
|
159
178
|
const [lineArg, characterArg, ...flags] = rest;
|
|
160
179
|
const { line, character } = parsePosition(lineArg, characterArg);
|
|
161
180
|
const client = await connectLectorClient();
|
|
162
|
-
const
|
|
181
|
+
const result = await client.call("workspace.goToDefinition", { workspaceId, path, line, character });
|
|
163
182
|
if (hasFlag(flags, "--json")) {
|
|
164
|
-
console.log(JSON.stringify(
|
|
183
|
+
console.log(JSON.stringify(result));
|
|
165
184
|
return;
|
|
166
185
|
}
|
|
186
|
+
const { locations, provenance } = result;
|
|
187
|
+
console.log(formatIntelligenceSource(provenance));
|
|
167
188
|
if (locations.length === 0) {
|
|
168
189
|
console.log("no definition found");
|
|
169
190
|
return;
|
|
@@ -176,11 +197,13 @@ async function runWorkspaceImplementation(workspaceId: string | undefined, path:
|
|
|
176
197
|
const [lineArg, characterArg, ...flags] = rest;
|
|
177
198
|
const { line, character } = parsePosition(lineArg, characterArg);
|
|
178
199
|
const client = await connectLectorClient();
|
|
179
|
-
const
|
|
200
|
+
const result = await client.call("workspace.goToImplementation", { workspaceId, path, line, character });
|
|
180
201
|
if (hasFlag(flags, "--json")) {
|
|
181
|
-
console.log(JSON.stringify(
|
|
202
|
+
console.log(JSON.stringify(result));
|
|
182
203
|
return;
|
|
183
204
|
}
|
|
205
|
+
const { locations, provenance } = result;
|
|
206
|
+
console.log(formatIntelligenceSource(provenance));
|
|
184
207
|
if (locations.length === 0) {
|
|
185
208
|
console.log("no implementation found");
|
|
186
209
|
return;
|
|
@@ -194,11 +217,13 @@ async function runWorkspaceReferences(workspaceId: string | undefined, path: str
|
|
|
194
217
|
const { line, character } = parsePosition(lineArg, characterArg);
|
|
195
218
|
const includeDeclaration = hasFlag(flags, "--include-declaration");
|
|
196
219
|
const client = await connectLectorClient();
|
|
197
|
-
const
|
|
220
|
+
const result = await client.call("workspace.findReferences", { workspaceId, path, line, character, includeDeclaration });
|
|
198
221
|
if (hasFlag(flags, "--json")) {
|
|
199
|
-
console.log(JSON.stringify(
|
|
222
|
+
console.log(JSON.stringify(result));
|
|
200
223
|
return;
|
|
201
224
|
}
|
|
225
|
+
const { locations, provenance } = result;
|
|
226
|
+
console.log(formatIntelligenceSource(provenance));
|
|
202
227
|
if (locations.length === 0) {
|
|
203
228
|
console.log("no references found");
|
|
204
229
|
return;
|
|
@@ -211,22 +236,25 @@ async function runWorkspaceHover(workspaceId: string | undefined, path: string |
|
|
|
211
236
|
const [lineArg, characterArg, ...flags] = rest;
|
|
212
237
|
const { line, character } = parsePosition(lineArg, characterArg);
|
|
213
238
|
const client = await connectLectorClient();
|
|
214
|
-
const
|
|
239
|
+
const result = await client.call("workspace.hover", { workspaceId, path, line, character });
|
|
215
240
|
if (hasFlag(flags, "--json")) {
|
|
216
|
-
console.log(JSON.stringify(
|
|
241
|
+
console.log(JSON.stringify(result));
|
|
217
242
|
return;
|
|
218
243
|
}
|
|
219
|
-
console.log(
|
|
244
|
+
console.log(formatIntelligenceSource(result.provenance));
|
|
245
|
+
console.log(result.hover ? result.hover.contents : "no hover information available");
|
|
220
246
|
}
|
|
221
247
|
|
|
222
248
|
async function runWorkspaceDocumentSymbols(workspaceId: string | undefined, path: string | undefined, flags: string[]): Promise<void> {
|
|
223
249
|
if (!workspaceId || !path) fail(USAGE);
|
|
224
250
|
const client = await connectLectorClient();
|
|
225
|
-
const
|
|
251
|
+
const result = await client.call("workspace.documentSymbols", { workspaceId, path });
|
|
226
252
|
if (hasFlag(flags, "--json")) {
|
|
227
|
-
console.log(JSON.stringify(
|
|
253
|
+
console.log(JSON.stringify(result));
|
|
228
254
|
return;
|
|
229
255
|
}
|
|
256
|
+
const { symbols, provenance } = result;
|
|
257
|
+
console.log(formatIntelligenceSource(provenance));
|
|
230
258
|
if (symbols.length === 0) {
|
|
231
259
|
console.log("no symbols found");
|
|
232
260
|
return;
|
|
@@ -241,11 +269,13 @@ async function runWorkspaceDocumentSymbols(workspaceId: string | undefined, path
|
|
|
241
269
|
async function runWorkspaceDiagnostics(workspaceId: string | undefined, path: string | undefined, flags: string[]): Promise<void> {
|
|
242
270
|
if (!workspaceId || !path) fail(USAGE);
|
|
243
271
|
const client = await connectLectorClient();
|
|
244
|
-
const
|
|
272
|
+
const result = await client.call("workspace.diagnostics", { workspaceId, path });
|
|
245
273
|
if (hasFlag(flags, "--json")) {
|
|
246
|
-
console.log(JSON.stringify(
|
|
274
|
+
console.log(JSON.stringify(result));
|
|
247
275
|
return;
|
|
248
276
|
}
|
|
277
|
+
const { diagnostics, provenance } = result;
|
|
278
|
+
console.log(formatIntelligenceSource(provenance));
|
|
249
279
|
if (diagnostics.length === 0) {
|
|
250
280
|
console.log("no diagnostics");
|
|
251
281
|
return;
|
|
@@ -273,11 +303,13 @@ async function runWorkspaceCallHierarchy(
|
|
|
273
303
|
const client = await connectLectorClient();
|
|
274
304
|
|
|
275
305
|
if (subcommand === "prepare") {
|
|
276
|
-
const
|
|
306
|
+
const result = await client.call("workspace.prepareCallHierarchy", { workspaceId, path, line, character });
|
|
277
307
|
if (hasFlag(flags, "--json")) {
|
|
278
|
-
console.log(JSON.stringify(
|
|
308
|
+
console.log(JSON.stringify(result));
|
|
279
309
|
return;
|
|
280
310
|
}
|
|
311
|
+
const { items, provenance } = result;
|
|
312
|
+
console.log(formatIntelligenceSource(provenance));
|
|
281
313
|
if (items.length === 0) {
|
|
282
314
|
console.log("no call-hierarchy root at this position");
|
|
283
315
|
return;
|
|
@@ -286,11 +318,13 @@ async function runWorkspaceCallHierarchy(
|
|
|
286
318
|
return;
|
|
287
319
|
}
|
|
288
320
|
if (subcommand === "incoming") {
|
|
289
|
-
const
|
|
321
|
+
const result = await client.call("workspace.incomingCalls", { workspaceId, path, line, character });
|
|
290
322
|
if (hasFlag(flags, "--json")) {
|
|
291
|
-
console.log(JSON.stringify(
|
|
323
|
+
console.log(JSON.stringify(result));
|
|
292
324
|
return;
|
|
293
325
|
}
|
|
326
|
+
const { calls, provenance } = result;
|
|
327
|
+
console.log(formatIntelligenceSource(provenance));
|
|
294
328
|
if (calls.length === 0) {
|
|
295
329
|
console.log("no incoming calls found");
|
|
296
330
|
return;
|
|
@@ -299,11 +333,13 @@ async function runWorkspaceCallHierarchy(
|
|
|
299
333
|
return;
|
|
300
334
|
}
|
|
301
335
|
if (subcommand === "outgoing") {
|
|
302
|
-
const
|
|
336
|
+
const result = await client.call("workspace.outgoingCalls", { workspaceId, path, line, character });
|
|
303
337
|
if (hasFlag(flags, "--json")) {
|
|
304
|
-
console.log(JSON.stringify(
|
|
338
|
+
console.log(JSON.stringify(result));
|
|
305
339
|
return;
|
|
306
340
|
}
|
|
341
|
+
const { calls, provenance } = result;
|
|
342
|
+
console.log(formatIntelligenceSource(provenance));
|
|
307
343
|
if (calls.length === 0) {
|
|
308
344
|
console.log("no outgoing calls found");
|
|
309
345
|
return;
|
|
@@ -454,6 +490,38 @@ async function runWorkspaceRepoFetch(spec: string | undefined, flags: string[]):
|
|
|
454
490
|
if (result.refFallbackOccurred) console.log(`note: requested ref not found, fell back to the default branch (resolved: ${result.resolvedRef})`);
|
|
455
491
|
}
|
|
456
492
|
|
|
493
|
+
function formatPackageSourceResult(result: PackageSourceOperationResult): string {
|
|
494
|
+
const { outcome } = result;
|
|
495
|
+
if (outcome.status === "verified") {
|
|
496
|
+
return `${result.workspaceId ?? "unregistered"} ${outcome.coordinate.name}@${outcome.coordinate.resolvedVersion} -- ${outcome.workspace.cachePath}\n${outcome.repository.url ?? "local source"}@${outcome.repository.resolvedRef ?? "local"} ${outcome.repository.commit ?? outcome.verification.integrity}`;
|
|
497
|
+
}
|
|
498
|
+
if (outcome.status === "ambiguous") {
|
|
499
|
+
return `ambiguous [${outcome.code}] -- ${outcome.candidates.map((candidate) => `${candidate.version} (${candidate.source})`).join(", ")}${outcome.truncated ? ", …" : ""}`;
|
|
500
|
+
}
|
|
501
|
+
if (outcome.status === "unauthenticated") return `unauthenticated [${outcome.code}] -- configure ${outcome.requiredCredentialNames.join(", ")}`;
|
|
502
|
+
if (outcome.status === "oversized") return `oversized [${outcome.code}] -- ${outcome.resource} exceeded ${outcome.limit}`;
|
|
503
|
+
if (outcome.status === "mismatched") return `mismatched [${outcome.code}] -- expected ${outcome.expected}, got ${outcome.actual}`;
|
|
504
|
+
return `unavailable [${outcome.code}]`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function runPackageSource(projectDir: string | undefined, packageName: string | undefined, flags: string[]): Promise<void> {
|
|
508
|
+
if (!projectDir || !packageName) fail(USAGE);
|
|
509
|
+
const client = await connectLectorClient();
|
|
510
|
+
const result = await client.call("package.resolveSource", {
|
|
511
|
+
request: {
|
|
512
|
+
projectRoot: resolve(projectDir),
|
|
513
|
+
coordinate: {
|
|
514
|
+
ecosystem: "npm",
|
|
515
|
+
registry: flagValue(flags, "--registry") ?? null,
|
|
516
|
+
name: packageName,
|
|
517
|
+
requestedVersion: flagValue(flags, "--version") ?? null,
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
bounds: DEFAULT_PACKAGE_SOURCE_BOUNDS,
|
|
521
|
+
});
|
|
522
|
+
console.log(hasFlag(flags, "--json") ? JSON.stringify(result) : formatPackageSourceResult(result));
|
|
523
|
+
}
|
|
524
|
+
|
|
457
525
|
async function runWorkspaceSearchText(workspaceId: string | undefined, query: string | undefined, flags: string[]): Promise<void> {
|
|
458
526
|
if (!workspaceId || !query) fail(USAGE);
|
|
459
527
|
const maxMatches = requiredIntFlag(flags, "--max-matches");
|
|
@@ -655,7 +723,7 @@ WantedBy=default.target
|
|
|
655
723
|
}
|
|
656
724
|
|
|
657
725
|
function unitPath(): string {
|
|
658
|
-
const configHome = process.env
|
|
726
|
+
const configHome = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
659
727
|
return join(configHome, "systemd", "user", LECTOR_PATH_NAMES.systemdUnitName);
|
|
660
728
|
}
|
|
661
729
|
|
|
@@ -713,6 +781,12 @@ async function main(): Promise<void> {
|
|
|
713
781
|
fail(USAGE);
|
|
714
782
|
}
|
|
715
783
|
|
|
784
|
+
if (command === "package") {
|
|
785
|
+
const [action, projectDir, packageName, ...packageFlags] = rest;
|
|
786
|
+
if (action === "source") return runPackageSource(projectDir, packageName, packageFlags);
|
|
787
|
+
fail(USAGE);
|
|
788
|
+
}
|
|
789
|
+
|
|
716
790
|
if (command === "workspace") {
|
|
717
791
|
const [action, ...actionArgs] = rest;
|
|
718
792
|
if (action === "register") {
|
package/src/daemon.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { SqliteSearchCache } from "./adapters/sqlite-search-cache.ts";
|
|
|
9
9
|
import { SqliteSymbolGraph } from "./adapters/sqlite-symbol-graph.ts";
|
|
10
10
|
import { TieredSearchCache } from "./adapters/tiered-search-cache.ts";
|
|
11
11
|
import { resolveLectorPaths } from "./constants.ts";
|
|
12
|
+
import type { PackageSourceResolverPort } from "./ports/package-source-resolver-port.ts";
|
|
12
13
|
import type { RepoFetcherPort } from "./ports/repo-fetcher-port.ts";
|
|
13
14
|
import type { WorkspacePort } from "./ports/workspace-port.ts";
|
|
14
15
|
import { createLectorService, type LectorService, type OperationName, type WorkspaceId } from "./service.ts";
|
|
@@ -84,6 +85,8 @@ export interface LectorDaemonOptions {
|
|
|
84
85
|
symbolIndexReapIntervalMs?: number;
|
|
85
86
|
/** Override the repo.fetch backend (tests inject a GitRepoFetcher pointed at a local fixture repo, avoiding live network). Defaults to a real GitRepoFetcher under this daemon's own data directory. */
|
|
86
87
|
createRepoFetcher?: () => RepoFetcherPort;
|
|
88
|
+
/** Override package source resolution while retaining the authenticated daemon/client seam. */
|
|
89
|
+
createPackageSourceResolver?: () => PackageSourceResolverPort;
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
function prepare(options: LectorDaemonOptions): {
|
|
@@ -110,6 +113,7 @@ function prepare(options: LectorDaemonOptions): {
|
|
|
110
113
|
allowDynamicOnly: options.allowDynamicOnly,
|
|
111
114
|
createSymbolGraph: (workspaceId) => new SqliteSymbolGraph(join(symbolGraphDirectory, `${workspaceId}.db`)),
|
|
112
115
|
createRepoFetcher: options.createRepoFetcher ?? (() => new GitRepoFetcher(reposDirectory)),
|
|
116
|
+
createPackageSourceResolver: options.createPackageSourceResolver,
|
|
113
117
|
// The real production shape the SearchCachePort design was for: an in-memory tier for
|
|
114
118
|
// speed plus a disk-backed tier so repeated searches survive a daemon restart -- a single
|
|
115
119
|
// SearchCachePort adapter can only be one or the other, service.ts's own safe default is
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { SymbolIndexPort } from "../ports/symbol-index-port.ts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SymbolSearchBounds } from "./intelligence-provenance.ts";
|
|
3
|
+
import type { SymbolSearchResult } from "./workspace-symbol.ts";
|
|
3
4
|
|
|
4
5
|
/** Find workspace symbols matching a fuzzy query string. */
|
|
5
|
-
export async function findWorkspaceSymbols(index: SymbolIndexPort, query: string): Promise<
|
|
6
|
-
return index.findSymbols(query);
|
|
6
|
+
export async function findWorkspaceSymbols(index: SymbolIndexPort, query: string, bounds?: SymbolSearchBounds): Promise<SymbolSearchResult> {
|
|
7
|
+
return index.findSymbols(query, bounds);
|
|
7
8
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type JavaScriptPackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
|
2
|
+
|
|
3
|
+
export interface InstalledPackageVersionRequest {
|
|
4
|
+
readonly projectRoot: string;
|
|
5
|
+
readonly packageName: string;
|
|
6
|
+
readonly requestedVersion: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface InstalledPackageVersionBounds {
|
|
10
|
+
readonly maxManifestBytes: number;
|
|
11
|
+
readonly maxManifestEntries: number;
|
|
12
|
+
readonly maxManifestNesting: number;
|
|
13
|
+
readonly maxWorkspaces: number;
|
|
14
|
+
readonly maxDiagnostics: number;
|
|
15
|
+
readonly maxCandidates: number;
|
|
16
|
+
readonly maxEvidencePerVersion: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface InstalledPackageEvidence {
|
|
20
|
+
readonly manager: JavaScriptPackageManager;
|
|
21
|
+
readonly lockfile: string;
|
|
22
|
+
readonly locator: string;
|
|
23
|
+
readonly integrity: string | null;
|
|
24
|
+
readonly workspace: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ResolvedInstalledPackageVersion {
|
|
28
|
+
readonly status: "resolved";
|
|
29
|
+
readonly packageName: string;
|
|
30
|
+
readonly requestedVersion: string | null;
|
|
31
|
+
readonly version: string;
|
|
32
|
+
readonly evidence: readonly InstalledPackageEvidence[];
|
|
33
|
+
readonly evidenceTruncated: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface InstalledPackageVersionCandidate {
|
|
37
|
+
readonly version: string;
|
|
38
|
+
readonly evidence: readonly InstalledPackageEvidence[];
|
|
39
|
+
readonly evidenceTruncated: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AmbiguousInstalledPackageVersion {
|
|
43
|
+
readonly status: "ambiguous";
|
|
44
|
+
readonly packageName: string;
|
|
45
|
+
readonly requestedVersion: null;
|
|
46
|
+
readonly candidates: readonly InstalledPackageVersionCandidate[];
|
|
47
|
+
readonly truncated: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UnavailableInstalledPackageVersion {
|
|
51
|
+
readonly status: "unavailable";
|
|
52
|
+
readonly code: "lockfile-not-found" | "package-not-found" | "version-not-found" | "unsupported-lockfile" | "corrupt-lockfile";
|
|
53
|
+
readonly lockfile?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface OversizedInstalledPackageVersion {
|
|
57
|
+
readonly status: "oversized";
|
|
58
|
+
readonly resource: "manifest-bytes" | "manifest-entries" | "manifest-nesting" | "workspaces" | "diagnostics";
|
|
59
|
+
readonly limit: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type InstalledPackageVersionOutcome =
|
|
63
|
+
| ResolvedInstalledPackageVersion
|
|
64
|
+
| AmbiguousInstalledPackageVersion
|
|
65
|
+
| UnavailableInstalledPackageVersion
|
|
66
|
+
| OversizedInstalledPackageVersion;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type IntelligenceFidelity = "semantic" | "structural";
|
|
2
|
+
|
|
3
|
+
export interface IntelligenceProvenance {
|
|
4
|
+
readonly fidelity: IntelligenceFidelity;
|
|
5
|
+
readonly backend: string;
|
|
6
|
+
readonly languageId: string;
|
|
7
|
+
readonly authority: "language-server" | "parser" | "compiler";
|
|
8
|
+
readonly freshness: "live-process" | "content-hash" | "filesystem-snapshot";
|
|
9
|
+
readonly limitations: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IntelligenceSourceOutcome {
|
|
13
|
+
readonly provenance: IntelligenceProvenance;
|
|
14
|
+
readonly status: "ready" | "failed";
|
|
15
|
+
readonly symbolCount: number;
|
|
16
|
+
readonly truncated?: boolean;
|
|
17
|
+
readonly error?: { readonly code: string; readonly message: string };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SymbolSearchBounds {
|
|
21
|
+
readonly maxResults: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ProvenancedResult<T> {
|
|
25
|
+
readonly result: T;
|
|
26
|
+
readonly provenance: IntelligenceProvenance;
|
|
27
|
+
}
|
|
@@ -13,13 +13,18 @@ export type LanguageServerLaunch = { readonly kind: "npm-module"; readonly entry
|
|
|
13
13
|
|
|
14
14
|
export interface LanguageServerDescriptor {
|
|
15
15
|
readonly languageId: string;
|
|
16
|
+
readonly backendId: string;
|
|
16
17
|
readonly extensions: readonly string[];
|
|
18
|
+
/** Per-extension document language ids when one server owns a language family. */
|
|
19
|
+
readonly documentLanguageIds?: Readonly<Record<string, string>>;
|
|
17
20
|
readonly launch: LanguageServerLaunch;
|
|
18
21
|
readonly args: readonly string[];
|
|
19
22
|
/** Checked nearest-first; closest match wins over a more distant one -- a monorepo subproject with its own root marker resolves to itself, not the outer repo. */
|
|
20
23
|
readonly rootMarkers: readonly string[];
|
|
21
24
|
/** Tried before the bounded directory scan when picking a file to warm the server with (e.g. a language's usual entry-point names). */
|
|
22
25
|
readonly commonSeedCandidates: readonly string[];
|
|
26
|
+
/** Excludes unsafe or unusually expensive servers from automatic workspace-wide fan-out while preserving explicit file operations. */
|
|
27
|
+
readonly workspaceDiscovery?: "enabled" | "explicit-only";
|
|
23
28
|
/** Extra textDocument/workspace capabilities this specific server gates real features behind (e.g. typescript-language-server withholds diagnostics/callHierarchy unless declared). */
|
|
24
29
|
readonly extraCapabilities?: Record<string, unknown>;
|
|
25
30
|
/** Milliseconds to wait after opening a file before trusting the server's answers -- no server signals "project loaded". Default 1000ms; rust-analyzer needs more (see RUST_DESCRIPTOR). */
|
|
@@ -30,7 +35,16 @@ export const DEFAULT_SETTLE_MS = 1000;
|
|
|
30
35
|
|
|
31
36
|
export const TYPESCRIPT_DESCRIPTOR: LanguageServerDescriptor = {
|
|
32
37
|
languageId: "typescript",
|
|
38
|
+
backendId: "typescript-language-server",
|
|
33
39
|
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
40
|
+
documentLanguageIds: {
|
|
41
|
+
".ts": "typescript",
|
|
42
|
+
".tsx": "typescriptreact",
|
|
43
|
+
".js": "javascript",
|
|
44
|
+
".jsx": "javascriptreact",
|
|
45
|
+
".mjs": "javascript",
|
|
46
|
+
".cjs": "javascript",
|
|
47
|
+
},
|
|
34
48
|
launch: { kind: "npm-module", entryModule: "typescript-language-server/lib/cli.mjs" },
|
|
35
49
|
args: ["--stdio"],
|
|
36
50
|
rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"],
|
|
@@ -43,6 +57,7 @@ export const TYPESCRIPT_DESCRIPTOR: LanguageServerDescriptor = {
|
|
|
43
57
|
|
|
44
58
|
export const PYTHON_DESCRIPTOR: LanguageServerDescriptor = {
|
|
45
59
|
languageId: "python",
|
|
60
|
+
backendId: "pyright",
|
|
46
61
|
extensions: [".py", ".pyi"],
|
|
47
62
|
launch: { kind: "npm-module", entryModule: "pyright/langserver.index.js" },
|
|
48
63
|
args: ["--stdio"],
|
|
@@ -52,6 +67,7 @@ export const PYTHON_DESCRIPTOR: LanguageServerDescriptor = {
|
|
|
52
67
|
|
|
53
68
|
export const GO_DESCRIPTOR: LanguageServerDescriptor = {
|
|
54
69
|
languageId: "go",
|
|
70
|
+
backendId: "gopls",
|
|
55
71
|
extensions: [".go"],
|
|
56
72
|
// gopls ships via `go install`, not npm.
|
|
57
73
|
launch: { kind: "system-binary", command: "gopls" },
|
|
@@ -62,6 +78,7 @@ export const GO_DESCRIPTOR: LanguageServerDescriptor = {
|
|
|
62
78
|
|
|
63
79
|
export const RUST_DESCRIPTOR: LanguageServerDescriptor = {
|
|
64
80
|
languageId: "rust",
|
|
81
|
+
backendId: "rust-analyzer",
|
|
65
82
|
extensions: [".rs"],
|
|
66
83
|
// rust-analyzer ships via rustup, not npm.
|
|
67
84
|
launch: { kind: "system-binary", command: "rust-analyzer" },
|
|
@@ -73,6 +90,7 @@ export const RUST_DESCRIPTOR: LanguageServerDescriptor = {
|
|
|
73
90
|
|
|
74
91
|
export const CPP_DESCRIPTOR: LanguageServerDescriptor = {
|
|
75
92
|
languageId: "cpp",
|
|
93
|
+
backendId: "clangd",
|
|
76
94
|
extensions: [".c", ".h", ".cc", ".cpp", ".cxx", ".hh", ".hpp", ".hxx"],
|
|
77
95
|
// clangd ships via LLVM's system packaging, not npm.
|
|
78
96
|
launch: { kind: "system-binary", command: "clangd" },
|
|
@@ -83,7 +101,9 @@ export const CPP_DESCRIPTOR: LanguageServerDescriptor = {
|
|
|
83
101
|
|
|
84
102
|
export const BASH_DESCRIPTOR: LanguageServerDescriptor = {
|
|
85
103
|
languageId: "shellscript",
|
|
104
|
+
backendId: "bash-language-server",
|
|
86
105
|
extensions: [".sh", ".bash"],
|
|
106
|
+
workspaceDiscovery: "explicit-only",
|
|
87
107
|
launch: { kind: "npm-module", entryModule: "bash-language-server/out/cli.js" },
|
|
88
108
|
args: ["start"],
|
|
89
109
|
rootMarkers: [],
|
|
@@ -92,7 +112,9 @@ export const BASH_DESCRIPTOR: LanguageServerDescriptor = {
|
|
|
92
112
|
|
|
93
113
|
export const YAML_DESCRIPTOR: LanguageServerDescriptor = {
|
|
94
114
|
languageId: "yaml",
|
|
115
|
+
backendId: "yaml-language-server",
|
|
95
116
|
extensions: [".yaml", ".yml"],
|
|
117
|
+
workspaceDiscovery: "explicit-only",
|
|
96
118
|
launch: { kind: "npm-module", entryModule: "yaml-language-server/bin/yaml-language-server" },
|
|
97
119
|
args: ["--stdio"],
|
|
98
120
|
rootMarkers: [],
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface NpmRegistryVersionRequest {
|
|
2
|
+
readonly registry: string;
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly version: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface NpmRegistryBounds {
|
|
8
|
+
readonly maxResponseBytes: number;
|
|
9
|
+
readonly maxRedirects: number;
|
|
10
|
+
readonly maxRetries: number;
|
|
11
|
+
readonly timeoutMs: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface NpmRepositoryMetadata {
|
|
15
|
+
readonly type: string | null;
|
|
16
|
+
readonly url: string;
|
|
17
|
+
readonly directory: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface NpmPackageVersionMetadata {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly version: string;
|
|
23
|
+
readonly repository: NpmRepositoryMetadata | null;
|
|
24
|
+
readonly gitHead: string | null;
|
|
25
|
+
readonly integrity: string | null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export type PackageEcosystem = "npm" | "pypi" | "cargo" | "go" | "maven" | "conan" | "vcpkg" | "nuget" | "swiftpm";
|
|
2
|
+
|
|
3
|
+
export interface PackageCoordinateRequest {
|
|
4
|
+
readonly ecosystem: PackageEcosystem;
|
|
5
|
+
readonly registry: string | null;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly requestedVersion: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PackageSourceRequest {
|
|
11
|
+
readonly projectRoot: string;
|
|
12
|
+
readonly coordinate: PackageCoordinateRequest;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PackageSourceBounds {
|
|
16
|
+
readonly maxManifestBytes: number;
|
|
17
|
+
readonly maxManifestEntries: number;
|
|
18
|
+
readonly maxManifestNesting: number;
|
|
19
|
+
readonly maxWorkspaces: number;
|
|
20
|
+
readonly maxDiagnostics: number;
|
|
21
|
+
readonly maxRegistryResponseBytes: number;
|
|
22
|
+
readonly maxRedirects: number;
|
|
23
|
+
readonly maxRetries: number;
|
|
24
|
+
readonly maxCloneBytes: number;
|
|
25
|
+
readonly maxCacheBytes: number;
|
|
26
|
+
readonly maxCandidates: number;
|
|
27
|
+
readonly timeoutMs: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ResolvedPackageCoordinate extends PackageCoordinateRequest {
|
|
31
|
+
readonly resolvedVersion: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PackageRepositoryIdentity {
|
|
35
|
+
readonly url: string | null;
|
|
36
|
+
readonly requestedRef: string | null;
|
|
37
|
+
readonly resolvedRef: string | null;
|
|
38
|
+
readonly commit: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PackageSourceWorkspace {
|
|
42
|
+
readonly cachePath: string;
|
|
43
|
+
readonly origin: "local" | "fetched";
|
|
44
|
+
readonly readOnly: true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type PackageSourceVerificationMethod = "lockfile-vcs-pin" | "registry-metadata-and-commit" | "source-artifact-checksum" | "local-content-digest";
|
|
48
|
+
|
|
49
|
+
export interface PackageSourceVerification {
|
|
50
|
+
readonly status: "verified";
|
|
51
|
+
readonly method: PackageSourceVerificationMethod;
|
|
52
|
+
readonly integrity: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface VerifiedPackageSource {
|
|
56
|
+
readonly status: "verified";
|
|
57
|
+
readonly coordinate: ResolvedPackageCoordinate;
|
|
58
|
+
readonly repository: PackageRepositoryIdentity;
|
|
59
|
+
readonly workspace: PackageSourceWorkspace;
|
|
60
|
+
readonly verification: PackageSourceVerification;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type PackageSourceUnavailableCode =
|
|
64
|
+
| "package-not-found"
|
|
65
|
+
| "version-not-found"
|
|
66
|
+
| "source-metadata-missing"
|
|
67
|
+
| "unsupported-ecosystem"
|
|
68
|
+
| "unsupported-manifest"
|
|
69
|
+
| "unverifiable-source";
|
|
70
|
+
|
|
71
|
+
export interface UnavailablePackageSource {
|
|
72
|
+
readonly status: "unavailable";
|
|
73
|
+
readonly code: PackageSourceUnavailableCode;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface PackageSourceCandidate {
|
|
77
|
+
readonly version: string;
|
|
78
|
+
readonly source: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface AmbiguousPackageSource {
|
|
82
|
+
readonly status: "ambiguous";
|
|
83
|
+
readonly code: "multiple-installed-versions" | "multiple-source-candidates";
|
|
84
|
+
readonly candidates: readonly PackageSourceCandidate[];
|
|
85
|
+
readonly truncated: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface UnauthenticatedPackageSource {
|
|
89
|
+
readonly status: "unauthenticated";
|
|
90
|
+
readonly code: "registry-authentication-required" | "repository-authentication-required";
|
|
91
|
+
readonly requiredCredentialNames: readonly string[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface OversizedPackageSource {
|
|
95
|
+
readonly status: "oversized";
|
|
96
|
+
readonly code: "manifest-limit-exceeded" | "registry-response-limit-exceeded" | "clone-limit-exceeded" | "cache-limit-exceeded";
|
|
97
|
+
readonly resource:
|
|
98
|
+
| "manifest-bytes"
|
|
99
|
+
| "manifest-entries"
|
|
100
|
+
| "manifest-nesting"
|
|
101
|
+
| "workspaces"
|
|
102
|
+
| "diagnostics"
|
|
103
|
+
| "registry-response-bytes"
|
|
104
|
+
| "clone-bytes"
|
|
105
|
+
| "cache-bytes";
|
|
106
|
+
readonly limit: number;
|
|
107
|
+
readonly observed: number | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface MismatchedPackageSource {
|
|
111
|
+
readonly status: "mismatched";
|
|
112
|
+
readonly code: "coordinate-mismatch" | "repository-ref-mismatch" | "repository-commit-mismatch" | "integrity-mismatch";
|
|
113
|
+
readonly expected: string;
|
|
114
|
+
readonly actual: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type PackageSourceOutcome =
|
|
118
|
+
| VerifiedPackageSource
|
|
119
|
+
| UnavailablePackageSource
|
|
120
|
+
| AmbiguousPackageSource
|
|
121
|
+
| UnauthenticatedPackageSource
|
|
122
|
+
| OversizedPackageSource
|
|
123
|
+
| MismatchedPackageSource;
|
|
124
|
+
|
|
125
|
+
export interface PackageSourceOperationResult {
|
|
126
|
+
readonly outcome: PackageSourceOutcome;
|
|
127
|
+
readonly workspaceId: string | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const DEFAULT_PACKAGE_SOURCE_BOUNDS: PackageSourceBounds = {
|
|
131
|
+
maxManifestBytes: 16 * 1024 * 1024,
|
|
132
|
+
maxManifestEntries: 100_000,
|
|
133
|
+
maxManifestNesting: 128,
|
|
134
|
+
maxWorkspaces: 10_000,
|
|
135
|
+
maxDiagnostics: 100,
|
|
136
|
+
maxRegistryResponseBytes: 8 * 1024 * 1024,
|
|
137
|
+
maxRedirects: 5,
|
|
138
|
+
maxRetries: 2,
|
|
139
|
+
maxCloneBytes: 512 * 1024 * 1024,
|
|
140
|
+
maxCacheBytes: 5 * 1024 * 1024 * 1024,
|
|
141
|
+
maxCandidates: 20,
|
|
142
|
+
timeoutMs: 60_000,
|
|
143
|
+
};
|