@danypops/lector 0.1.9 → 0.1.10
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 +1 -1
- package/package.json +1 -1
- package/src/adapters/polyglot-code-intelligence-index.ts +4 -0
- package/src/adapters/sqlite-symbol-graph.ts +87 -8
- package/src/cli.ts +13 -7
- package/src/domain/populate-symbol-graph.ts +78 -3
- package/src/domain/symbol-graph-generation.ts +1 -0
- package/src/index.ts +1 -1
- package/src/ports/code-intelligence-port.ts +3 -0
- package/src/service.ts +2 -2
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ Three independent `SymbolIndexPort` implementations use one result DTO with expl
|
|
|
64
64
|
- **TypeScript compiler** extracts bounded structural declarations from cold, malformed, or partially configured projects. It does not claim cross-file identity.
|
|
65
65
|
- **tree-sitter** extracts bounded structural declarations and caches them by content hash. It does not claim type or language-server identity.
|
|
66
66
|
|
|
67
|
-
File-position operations dispatch by extension and seed a cold server with the requested file. Workspace symbol search and graph population detect every enabled language, merge results deterministically, and retain per-symbol and per-backend provenance
|
|
67
|
+
File-position operations dispatch by extension and seed a cold server with the requested file. Workspace symbol search and graph population detect every enabled language, merge results deterministically, and retain per-symbol and per-backend provenance. Backend and file-level semantic failures produce bounded partial results without discarding successful languages or files. Bash remains explicit-file-only until its vulnerable upstream dependency chain is removed; generic YAML is explicit-file-only because lockfiles and data files do not establish a workspace language.
|
|
68
68
|
|
|
69
69
|
Symbol results report `fidelity`, `backend`, `authority`, `freshness`, `limitations`, `truncated`, and composite `sources`/`completeness` when applicable. Language-server messages, pending requests, opened files, file bytes, settling, parser files, parser nodes, parser bytes, language indexes, and returned symbols are bounded.
|
|
70
70
|
|
package/package.json
CHANGED
|
@@ -97,6 +97,10 @@ export class PolyglotCodeIntelligenceIndex implements SymbolIndexPort, CodeIntel
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
provenanceForPath(path: string): IntelligenceProvenance {
|
|
101
|
+
return this.indexForPath(path).provenance;
|
|
102
|
+
}
|
|
103
|
+
|
|
100
104
|
goToDefinition(at: WorkspaceLocation) {
|
|
101
105
|
return this.indexForPath(at.path).goToDefinition(at);
|
|
102
106
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
2
|
import { type Migration, openSqliteWithPragmas } from "@danypops/daemon-kit/storage";
|
|
3
3
|
import type { IntelligenceProvenance } from "../domain/intelligence-provenance.ts";
|
|
4
|
+
import type { PopulateSymbolGraphResult, SymbolGraphPopulationFailure } from "../domain/populate-symbol-graph.ts";
|
|
4
5
|
import type { SymbolGraphGeneration } from "../domain/symbol-graph-generation.ts";
|
|
5
6
|
import type { SymbolNodeId } from "../domain/symbol-node-id.ts";
|
|
6
7
|
import type { SymbolEdgeKind, SymbolGraphPort, SymbolNode } from "../ports/symbol-graph-port.ts";
|
|
@@ -32,6 +33,10 @@ const MIGRATIONS: Migration[] = [
|
|
|
32
33
|
version: 4,
|
|
33
34
|
up: (db) => db.exec("ALTER TABLE symbol_graph_generation ADD COLUMN sources_json TEXT"),
|
|
34
35
|
},
|
|
36
|
+
{
|
|
37
|
+
version: 5,
|
|
38
|
+
up: (db) => db.exec("ALTER TABLE symbol_graph_generation ADD COLUMN result_json TEXT"),
|
|
39
|
+
},
|
|
35
40
|
];
|
|
36
41
|
|
|
37
42
|
interface NodeRow {
|
|
@@ -53,6 +58,7 @@ interface GenerationRow {
|
|
|
53
58
|
edges_added: number;
|
|
54
59
|
provenance_json: string | null;
|
|
55
60
|
sources_json: string | null;
|
|
61
|
+
result_json: string | null;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -103,6 +109,69 @@ function parseSources(json: string | null): readonly IntelligenceProvenance[] |
|
|
|
103
109
|
return sources.every((source) => source !== undefined) ? sources : undefined;
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
function isNonNegativeInteger(value: unknown): value is number {
|
|
113
|
+
return Number.isSafeInteger(value) && typeof value === "number" && value >= 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function asPopulationFailure(value: unknown): SymbolGraphPopulationFailure | undefined {
|
|
117
|
+
if (!isRecord(value)) return undefined;
|
|
118
|
+
const provenance = asProvenance(value.provenance);
|
|
119
|
+
if (
|
|
120
|
+
typeof value.path !== "string" ||
|
|
121
|
+
value.path.length > 4096 ||
|
|
122
|
+
(value.operation !== "document-symbols" && value.operation !== "outgoing-calls") ||
|
|
123
|
+
typeof value.code !== "string" ||
|
|
124
|
+
value.code.length > 100 ||
|
|
125
|
+
typeof value.message !== "string" ||
|
|
126
|
+
value.message.length > 500 ||
|
|
127
|
+
!provenance
|
|
128
|
+
) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return { path: value.path, operation: value.operation, code: value.code, message: value.message, provenance };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parsePopulationResult(json: string | null): PopulateSymbolGraphResult | undefined {
|
|
135
|
+
const value = parseJson(json);
|
|
136
|
+
if (!isRecord(value) || (value.completeness !== "complete" && value.completeness !== "partial") || !Array.isArray(value.failures)) return undefined;
|
|
137
|
+
if (
|
|
138
|
+
!isNonNegativeInteger(value.filesAttempted) ||
|
|
139
|
+
!isNonNegativeInteger(value.filesProcessed) ||
|
|
140
|
+
!isNonNegativeInteger(value.filesFailed) ||
|
|
141
|
+
!isNonNegativeInteger(value.symbolsProcessed) ||
|
|
142
|
+
!isNonNegativeInteger(value.nodesAdded) ||
|
|
143
|
+
!isNonNegativeInteger(value.edgesAdded) ||
|
|
144
|
+
!isNonNegativeInteger(value.failureCount) ||
|
|
145
|
+
typeof value.failuresTruncated !== "boolean" ||
|
|
146
|
+
value.failures.length > 100
|
|
147
|
+
) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const failures = value.failures.map(asPopulationFailure);
|
|
151
|
+
if (
|
|
152
|
+
!failures.every((failure) => failure !== undefined) ||
|
|
153
|
+
value.filesProcessed > value.filesAttempted ||
|
|
154
|
+
value.filesFailed > value.filesAttempted ||
|
|
155
|
+
failures.length > value.failureCount ||
|
|
156
|
+
(value.completeness === "complete") !== (value.failureCount === 0) ||
|
|
157
|
+
value.failuresTruncated !== value.failureCount > failures.length
|
|
158
|
+
) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
completeness: value.completeness,
|
|
163
|
+
filesAttempted: value.filesAttempted,
|
|
164
|
+
filesProcessed: value.filesProcessed,
|
|
165
|
+
filesFailed: value.filesFailed,
|
|
166
|
+
symbolsProcessed: value.symbolsProcessed,
|
|
167
|
+
nodesAdded: value.nodesAdded,
|
|
168
|
+
edgesAdded: value.edgesAdded,
|
|
169
|
+
failureCount: value.failureCount,
|
|
170
|
+
failures,
|
|
171
|
+
failuresTruncated: value.failuresTruncated,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
106
175
|
/**
|
|
107
176
|
* SQLite-backed SymbolGraphPort; survives a daemon restart pointed at the
|
|
108
177
|
* same database file. reachableFrom uses `WITH RECURSIVE` rather than an
|
|
@@ -172,12 +241,26 @@ export class SqliteSymbolGraph implements SymbolGraphPort {
|
|
|
172
241
|
async getGeneration(): Promise<SymbolGraphGeneration | undefined> {
|
|
173
242
|
const row = this.db
|
|
174
243
|
.query(
|
|
175
|
-
"SELECT source_fingerprint, max_files, max_symbols_per_file, completed_at, files_processed, symbols_processed, nodes_added, edges_added, provenance_json, sources_json FROM symbol_graph_generation WHERE singleton = 1",
|
|
244
|
+
"SELECT source_fingerprint, max_files, max_symbols_per_file, completed_at, files_processed, symbols_processed, nodes_added, edges_added, provenance_json, sources_json, result_json FROM symbol_graph_generation WHERE singleton = 1",
|
|
176
245
|
)
|
|
177
246
|
.get() as GenerationRow | null;
|
|
178
247
|
if (!row) return undefined;
|
|
179
248
|
const provenance = parseProvenance(row.provenance_json);
|
|
180
249
|
const sources = parseSources(row.sources_json);
|
|
250
|
+
const result =
|
|
251
|
+
parsePopulationResult(row.result_json) ??
|
|
252
|
+
({
|
|
253
|
+
completeness: "complete",
|
|
254
|
+
filesAttempted: row.files_processed,
|
|
255
|
+
filesProcessed: row.files_processed,
|
|
256
|
+
filesFailed: 0,
|
|
257
|
+
symbolsProcessed: row.symbols_processed,
|
|
258
|
+
nodesAdded: row.nodes_added,
|
|
259
|
+
edgesAdded: row.edges_added,
|
|
260
|
+
failureCount: 0,
|
|
261
|
+
failures: [],
|
|
262
|
+
failuresTruncated: false,
|
|
263
|
+
} satisfies PopulateSymbolGraphResult);
|
|
181
264
|
return {
|
|
182
265
|
sourceFingerprint: row.source_fingerprint,
|
|
183
266
|
maxFiles: row.max_files,
|
|
@@ -185,19 +268,14 @@ export class SqliteSymbolGraph implements SymbolGraphPort {
|
|
|
185
268
|
completedAt: row.completed_at,
|
|
186
269
|
...(provenance ? { provenance } : {}),
|
|
187
270
|
...(sources ? { sources } : {}),
|
|
188
|
-
result
|
|
189
|
-
filesProcessed: row.files_processed,
|
|
190
|
-
symbolsProcessed: row.symbols_processed,
|
|
191
|
-
nodesAdded: row.nodes_added,
|
|
192
|
-
edgesAdded: row.edges_added,
|
|
193
|
-
},
|
|
271
|
+
result,
|
|
194
272
|
};
|
|
195
273
|
}
|
|
196
274
|
|
|
197
275
|
async setGeneration(generation: SymbolGraphGeneration): Promise<void> {
|
|
198
276
|
this.db
|
|
199
277
|
.query(
|
|
200
|
-
"INSERT INTO symbol_graph_generation (singleton, source_fingerprint, max_files, max_symbols_per_file, completed_at, files_processed, symbols_processed, nodes_added, edges_added, provenance_json, sources_json) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(singleton) DO UPDATE SET source_fingerprint = excluded.source_fingerprint, max_files = excluded.max_files, max_symbols_per_file = excluded.max_symbols_per_file, completed_at = excluded.completed_at, files_processed = excluded.files_processed, symbols_processed = excluded.symbols_processed, nodes_added = excluded.nodes_added, edges_added = excluded.edges_added, provenance_json = excluded.provenance_json, sources_json = excluded.sources_json",
|
|
278
|
+
"INSERT INTO symbol_graph_generation (singleton, source_fingerprint, max_files, max_symbols_per_file, completed_at, files_processed, symbols_processed, nodes_added, edges_added, provenance_json, sources_json, result_json) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(singleton) DO UPDATE SET source_fingerprint = excluded.source_fingerprint, max_files = excluded.max_files, max_symbols_per_file = excluded.max_symbols_per_file, completed_at = excluded.completed_at, files_processed = excluded.files_processed, symbols_processed = excluded.symbols_processed, nodes_added = excluded.nodes_added, edges_added = excluded.edges_added, provenance_json = excluded.provenance_json, sources_json = excluded.sources_json, result_json = excluded.result_json",
|
|
201
279
|
)
|
|
202
280
|
.run(
|
|
203
281
|
generation.sourceFingerprint,
|
|
@@ -210,6 +288,7 @@ export class SqliteSymbolGraph implements SymbolGraphPort {
|
|
|
210
288
|
generation.result.edgesAdded,
|
|
211
289
|
generation.provenance ? JSON.stringify(generation.provenance) : null,
|
|
212
290
|
generation.sources ? JSON.stringify(generation.sources) : null,
|
|
291
|
+
JSON.stringify(generation.result),
|
|
213
292
|
);
|
|
214
293
|
}
|
|
215
294
|
|
package/src/cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ 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
14
|
import { DEFAULT_PACKAGE_SOURCE_BOUNDS, type PackageSourceOperationResult } from "./domain/package-source.ts";
|
|
15
|
+
import type { PopulateSymbolGraphResult } from "./domain/populate-symbol-graph.ts";
|
|
15
16
|
import type { SymbolSearchResult } from "./domain/workspace-symbol.ts";
|
|
16
17
|
import type { WorkspacePort } from "./ports/workspace-port.ts";
|
|
17
18
|
import type { WorkspaceId } from "./service.ts";
|
|
@@ -361,11 +362,19 @@ function requiredIntFlag(flags: string[], flag: string): number {
|
|
|
361
362
|
return parsed;
|
|
362
363
|
}
|
|
363
364
|
|
|
364
|
-
function
|
|
365
|
+
function formatPopulationResult(result: PopulateSymbolGraphResult): string {
|
|
366
|
+
const counts = `${result.filesProcessed}/${result.filesAttempted} files, ${result.symbolsProcessed} symbols, ${result.nodesAdded} nodes, ${result.edgesAdded} edges`;
|
|
367
|
+
if (result.completeness === "complete") return counts;
|
|
368
|
+
const first = result.failures[0];
|
|
369
|
+
const failure = first ? `; first failure: ${first.path} [${first.code} via ${first.provenance.backend}] ${first.message}` : "";
|
|
370
|
+
return `partial -- ${counts}, ${result.filesFailed} failed files (${result.failureCount} failed operations)${failure}`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function formatJobSnapshot(job: JobSnapshot<PopulateSymbolGraphResult>): string {
|
|
365
374
|
if (job.status === "queued") return `${job.id}: queued (${job.operation}); poll with: lector job status ${job.id}`;
|
|
366
375
|
if (job.status === "running") return `${job.id}: still running (${job.operation}); poll with: lector job status ${job.id}`;
|
|
367
376
|
if (job.status === "failed") return `${job.id}: failed [${job.error.code}] -- ${job.error.message}`;
|
|
368
|
-
return `${job.id}: succeeded -- ${job.result
|
|
377
|
+
return `${job.id}: succeeded -- ${formatPopulationResult(job.result)}`;
|
|
369
378
|
}
|
|
370
379
|
|
|
371
380
|
async function runWorkspacePopulateSymbolGraph(workspaceId: string | undefined, flags: string[]): Promise<void> {
|
|
@@ -385,11 +394,7 @@ async function runWorkspacePopulateSymbolGraph(workspaceId: string | undefined,
|
|
|
385
394
|
return;
|
|
386
395
|
}
|
|
387
396
|
const result = await client.call("workspace.populateSymbolGraph", { workspaceId, maxFiles, maxSymbolsPerFile });
|
|
388
|
-
console.log(
|
|
389
|
-
hasFlag(flags, "--json")
|
|
390
|
-
? JSON.stringify(result)
|
|
391
|
-
: `${result.filesProcessed} files, ${result.symbolsProcessed} symbols, ${result.nodesAdded} nodes, ${result.edgesAdded} edges`,
|
|
392
|
-
);
|
|
397
|
+
console.log(hasFlag(flags, "--json") ? JSON.stringify(result) : formatPopulationResult(result));
|
|
393
398
|
}
|
|
394
399
|
|
|
395
400
|
async function runJobStatus(jobId: string | undefined, flags: string[]): Promise<void> {
|
|
@@ -411,6 +416,7 @@ async function runWorkspaceCacheStatus(workspaceId: string | undefined, flags: s
|
|
|
411
416
|
}
|
|
412
417
|
if (status.status === "not-cached") console.log(`not cached -- ${status.reason}`);
|
|
413
418
|
else if (status.status === "caching") console.log(`caching -- job ${status.jobId}`);
|
|
419
|
+
else if (status.status === "partial") console.log(`partially cached -- ${formatPopulationResult(status.generation.result)}`);
|
|
414
420
|
else console.log(`cached -- completed ${new Date(status.generation.completedAt).toISOString()}`);
|
|
415
421
|
}
|
|
416
422
|
|
|
@@ -1,17 +1,60 @@
|
|
|
1
1
|
import type { CodeIntelligencePort } from "../ports/code-intelligence-port.ts";
|
|
2
2
|
import type { SymbolGraphPort, SymbolNode } from "../ports/symbol-graph-port.ts";
|
|
3
|
+
import type { OutgoingCall } from "./call-hierarchy.ts";
|
|
3
4
|
import type { DocumentSymbolEntry } from "./document-symbol.ts";
|
|
5
|
+
import type { IntelligenceProvenance } from "./intelligence-provenance.ts";
|
|
4
6
|
import { deriveSymbolNodeId } from "./symbol-node-id.ts";
|
|
5
7
|
import type { WorkspaceLocation } from "./workspace-symbol.ts";
|
|
6
8
|
|
|
7
9
|
const CALLABLE_KINDS = new Set(["function", "method", "constructor"]);
|
|
10
|
+
const MAX_RECORDED_FAILURES = 100;
|
|
11
|
+
const MAX_FAILURE_MESSAGE_LENGTH = 500;
|
|
12
|
+
|
|
13
|
+
export interface SymbolGraphPopulationFailure {
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly operation: "document-symbols" | "outgoing-calls";
|
|
16
|
+
readonly code: string;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
readonly provenance: IntelligenceProvenance;
|
|
19
|
+
}
|
|
8
20
|
|
|
9
21
|
export interface PopulateSymbolGraphResult {
|
|
22
|
+
readonly completeness: "complete" | "partial";
|
|
23
|
+
readonly filesAttempted: number;
|
|
10
24
|
readonly filesProcessed: number;
|
|
25
|
+
readonly filesFailed: number;
|
|
11
26
|
readonly symbolsProcessed: number;
|
|
12
27
|
/** addNode calls made, not necessarily new nodes -- a symbol reached from multiple edges is upserted once per encounter within a run, deduped in-memory. */
|
|
13
28
|
readonly nodesAdded: number;
|
|
14
29
|
readonly edgesAdded: number;
|
|
30
|
+
readonly failureCount: number;
|
|
31
|
+
readonly failures: readonly SymbolGraphPopulationFailure[];
|
|
32
|
+
readonly failuresTruncated: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const UNKNOWN_PROVENANCE: IntelligenceProvenance = {
|
|
36
|
+
fidelity: "semantic",
|
|
37
|
+
backend: "unavailable",
|
|
38
|
+
languageId: "unknown",
|
|
39
|
+
authority: "language-server",
|
|
40
|
+
freshness: "live-process",
|
|
41
|
+
limitations: ["source provenance was unavailable"],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function boundedFailure(
|
|
45
|
+
index: CodeIntelligencePort,
|
|
46
|
+
path: string,
|
|
47
|
+
operation: SymbolGraphPopulationFailure["operation"],
|
|
48
|
+
error: unknown,
|
|
49
|
+
): SymbolGraphPopulationFailure {
|
|
50
|
+
const errorName = error instanceof Error ? error.name : undefined;
|
|
51
|
+
return {
|
|
52
|
+
path,
|
|
53
|
+
operation,
|
|
54
|
+
code: errorName && errorName !== "Error" ? errorName : "CodeIntelligenceFileError",
|
|
55
|
+
message: (error instanceof Error ? error.message : String(error)).slice(0, MAX_FAILURE_MESSAGE_LENGTH),
|
|
56
|
+
provenance: index.provenanceForPath?.(path) ?? index.provenance ?? UNKNOWN_PROVENANCE,
|
|
57
|
+
};
|
|
15
58
|
}
|
|
16
59
|
|
|
17
60
|
function toLocation(entry: DocumentSymbolEntry): WorkspaceLocation {
|
|
@@ -50,7 +93,16 @@ export async function populateSymbolGraph(
|
|
|
50
93
|
let symbolsProcessed = 0;
|
|
51
94
|
let nodesAdded = 0;
|
|
52
95
|
let edgesAdded = 0;
|
|
96
|
+
let failureCount = 0;
|
|
53
97
|
const addedNodeIds = new Set<string>();
|
|
98
|
+
const failedFiles = new Set<string>();
|
|
99
|
+
const failures: SymbolGraphPopulationFailure[] = [];
|
|
100
|
+
|
|
101
|
+
function recordFailure(file: string, operation: SymbolGraphPopulationFailure["operation"], error: unknown): void {
|
|
102
|
+
failureCount++;
|
|
103
|
+
failedFiles.add(file);
|
|
104
|
+
if (failures.length < MAX_RECORDED_FAILURES) failures.push(boundedFailure(index, file, operation, error));
|
|
105
|
+
}
|
|
54
106
|
|
|
55
107
|
async function ensureNode(node: SymbolNode): Promise<void> {
|
|
56
108
|
if (addedNodeIds.has(node.id)) return;
|
|
@@ -60,7 +112,13 @@ export async function populateSymbolGraph(
|
|
|
60
112
|
}
|
|
61
113
|
|
|
62
114
|
for (const file of files) {
|
|
63
|
-
|
|
115
|
+
let topLevel: DocumentSymbolEntry[];
|
|
116
|
+
try {
|
|
117
|
+
topLevel = await index.documentSymbols(file);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
recordFailure(file, "document-symbols", error);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
64
122
|
const flattened = flattenDocumentSymbols(topLevel).slice(0, maxSymbolsPerFile);
|
|
65
123
|
filesProcessed++;
|
|
66
124
|
|
|
@@ -76,7 +134,13 @@ export async function populateSymbolGraph(
|
|
|
76
134
|
}
|
|
77
135
|
|
|
78
136
|
if (CALLABLE_KINDS.has(entry.kind)) {
|
|
79
|
-
|
|
137
|
+
let callees: OutgoingCall[];
|
|
138
|
+
try {
|
|
139
|
+
callees = await index.outgoingCalls(location);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
recordFailure(file, "outgoing-calls", error);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
80
144
|
for (const call of callees) {
|
|
81
145
|
const calleeNode: SymbolNode = { id: deriveSymbolNodeId(call.to.location), name: call.to.name, kind: call.to.kind, location: call.to.location };
|
|
82
146
|
await ensureNode(calleeNode);
|
|
@@ -87,5 +151,16 @@ export async function populateSymbolGraph(
|
|
|
87
151
|
}
|
|
88
152
|
}
|
|
89
153
|
|
|
90
|
-
return {
|
|
154
|
+
return {
|
|
155
|
+
completeness: failureCount === 0 ? "complete" : "partial",
|
|
156
|
+
filesAttempted: files.length,
|
|
157
|
+
filesProcessed,
|
|
158
|
+
filesFailed: failedFiles.size,
|
|
159
|
+
symbolsProcessed,
|
|
160
|
+
nodesAdded,
|
|
161
|
+
edgesAdded,
|
|
162
|
+
failureCount,
|
|
163
|
+
failures,
|
|
164
|
+
failuresTruncated: failureCount > failures.length,
|
|
165
|
+
};
|
|
91
166
|
}
|
|
@@ -16,4 +16,5 @@ export interface SymbolGraphGeneration {
|
|
|
16
16
|
export type WorkspaceCacheStatus =
|
|
17
17
|
| { readonly status: "not-cached"; readonly reason: "no-completed-generation" | "bounds-changed" | "source-changed" }
|
|
18
18
|
| { readonly status: "caching"; readonly jobId: string }
|
|
19
|
+
| { readonly status: "partial"; readonly generation: SymbolGraphGeneration }
|
|
19
20
|
| { readonly status: "cached"; readonly generation: SymbolGraphGeneration };
|
package/src/index.ts
CHANGED
|
@@ -130,7 +130,7 @@ export type {
|
|
|
130
130
|
VerifiedPackageSource,
|
|
131
131
|
} from "./domain/package-source.ts";
|
|
132
132
|
export { DEFAULT_PACKAGE_SOURCE_BOUNDS } from "./domain/package-source.ts";
|
|
133
|
-
export { type PopulateSymbolGraphResult, populateSymbolGraph } from "./domain/populate-symbol-graph.ts";
|
|
133
|
+
export { type PopulateSymbolGraphResult, populateSymbolGraph, type SymbolGraphPopulationFailure } from "./domain/populate-symbol-graph.ts";
|
|
134
134
|
export { prepareCallHierarchy } from "./domain/prepare-call-hierarchy.ts";
|
|
135
135
|
export { raceWorkspaceQuery } from "./domain/race-workspace-query.ts";
|
|
136
136
|
export { type RawRead, rawRead, WorkspaceEntryNotFound } from "./domain/raw-read.ts";
|
|
@@ -2,6 +2,7 @@ import type { CallHierarchyEntry, IncomingCall, OutgoingCall } from "../domain/c
|
|
|
2
2
|
import type { Diagnostic } from "../domain/diagnostic.ts";
|
|
3
3
|
import type { DocumentSymbolEntry } from "../domain/document-symbol.ts";
|
|
4
4
|
import type { Hover } from "../domain/hover.ts";
|
|
5
|
+
import type { IntelligenceProvenance } from "../domain/intelligence-provenance.ts";
|
|
5
6
|
import type { WorkspaceLocation } from "../domain/workspace-symbol.ts";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -17,6 +18,8 @@ import type { WorkspaceLocation } from "../domain/workspace-symbol.ts";
|
|
|
17
18
|
* process findSymbols already keeps alive per workspace -- not a second one.
|
|
18
19
|
*/
|
|
19
20
|
export interface CodeIntelligencePort {
|
|
21
|
+
readonly provenance?: IntelligenceProvenance;
|
|
22
|
+
provenanceForPath?(path: string): IntelligenceProvenance | undefined;
|
|
20
23
|
/** Where the symbol at `at` is actually declared -- may cross files, may return more than one candidate. */
|
|
21
24
|
goToDefinition(at: WorkspaceLocation): Promise<WorkspaceLocation[]>;
|
|
22
25
|
/** Every concrete implementation of the interface/abstract member at `at` -- unlike goToDefinition, crosses a port/interface boundary into its real adapters. */
|
package/src/service.ts
CHANGED
|
@@ -313,7 +313,7 @@ export interface OperationOutputs {
|
|
|
313
313
|
"workspace.prepareCallHierarchy": Provenanced<{ items: readonly CallHierarchyEntry[] }>;
|
|
314
314
|
"workspace.incomingCalls": Provenanced<{ calls: readonly IncomingCall[] }>;
|
|
315
315
|
"workspace.outgoingCalls": Provenanced<{ calls: readonly OutgoingCall[] }>;
|
|
316
|
-
"workspace.populateSymbolGraph":
|
|
316
|
+
"workspace.populateSymbolGraph": PopulateSymbolGraphResult;
|
|
317
317
|
"workspace.reachableFrom": { symbols: readonly SymbolNode[] };
|
|
318
318
|
"workspace.symbolEdgesFrom": { symbols: readonly SymbolNode[] };
|
|
319
319
|
"workspace.symbolEdgesTo": { symbols: readonly SymbolNode[] };
|
|
@@ -911,7 +911,7 @@ export function createLectorService(workspaces: ReadonlyMap<WorkspaceId, Workspa
|
|
|
911
911
|
return { status: "not-cached", reason: "source-changed" };
|
|
912
912
|
}
|
|
913
913
|
if (currentFingerprint !== generation.sourceFingerprint) return { status: "not-cached", reason: "source-changed" };
|
|
914
|
-
return { status: "cached", generation };
|
|
914
|
+
return generation.result.completeness === "partial" ? { status: "partial", generation } : { status: "cached", generation };
|
|
915
915
|
}
|
|
916
916
|
|
|
917
917
|
function isRecord(value: unknown): value is Record<string, unknown> {
|