@danypops/pi-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 +4 -0
- package/extension/src/code-intelligence-operations.ts +19 -40
- package/extension/src/cross-workspace-search-operations.ts +2 -6
- package/extension/src/cross-workspace-search-rendering.ts +5 -2
- package/extension/src/find-symbols-operations.ts +3 -4
- package/extension/src/find-symbols-rendering.ts +8 -5
- package/extension/src/index.ts +127 -67
- package/extension/src/package-source-operations.ts +21 -0
- package/extension/src/package-source-rendering.ts +37 -0
- package/extension/src/workspace-cache-operations.ts +14 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,6 +8,10 @@ daemon-backed, hash-guarded filesystem. Requires a running Lector daemon
|
|
|
8
8
|
pi install npm:@danypops/pi-lector
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
`package_source` resolves an installed npm package through its lockfile and registry metadata, verifies an exact Git commit, and registers the package source read-only for `search_code`, `find_symbols`, and the semantic tools.
|
|
12
|
+
|
|
13
|
+
Symbol and semantic tool results identify their backend and fidelity. `typescript-language-server` results are semantic; compiler or parser fallback results are structural and list their limitations.
|
|
14
|
+
|
|
11
15
|
`populate_symbol_graph` submits bounded background work and waits briefly. If the
|
|
12
16
|
graph is still loading, it returns a job id immediately; `job_status` polls that id
|
|
13
17
|
later without forcing the agent into a blocking or blind polling loop.
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CallHierarchyEntry,
|
|
3
|
-
Diagnostic,
|
|
4
|
-
DocumentSymbolEntry,
|
|
5
|
-
Hover,
|
|
6
|
-
IncomingCall,
|
|
7
|
-
JobSnapshot,
|
|
8
|
-
OutgoingCall,
|
|
9
|
-
PopulateSymbolGraphResult,
|
|
10
|
-
SymbolEdgeKind,
|
|
11
|
-
SymbolNode,
|
|
12
|
-
WorkspaceLocation,
|
|
13
|
-
} from "@danypops/lector";
|
|
1
|
+
import type { JobSnapshot, OperationOutputs, PopulateSymbolGraphResult, SymbolEdgeKind, SymbolNode } from "@danypops/lector";
|
|
14
2
|
import { lectorClient, withWorkspace, workspaceForCodeIntelligencePath } from "./lector-client.ts";
|
|
15
3
|
|
|
16
4
|
/**
|
|
@@ -27,15 +15,15 @@ import { lectorClient, withWorkspace, workspaceForCodeIntelligencePath } from ".
|
|
|
27
15
|
* unlike read/write/edit) -- never a value captured once at session start.
|
|
28
16
|
*/
|
|
29
17
|
export interface CodeIntelligenceOperations {
|
|
30
|
-
goToDefinition(path: string, line: number, character: number): Promise<
|
|
31
|
-
goToImplementation(path: string, line: number, character: number): Promise<
|
|
32
|
-
findReferences(path: string, line: number, character: number, includeDeclaration: boolean): Promise<
|
|
33
|
-
hover(path: string, line: number, character: number): Promise<
|
|
34
|
-
documentSymbols(path: string): Promise<
|
|
35
|
-
diagnostics(path: string): Promise<
|
|
36
|
-
prepareCallHierarchy(path: string, line: number, character: number): Promise<
|
|
37
|
-
incomingCalls(path: string, line: number, character: number): Promise<
|
|
38
|
-
outgoingCalls(path: string, line: number, character: number): Promise<
|
|
18
|
+
goToDefinition(path: string, line: number, character: number): Promise<OperationOutputs["workspace.goToDefinition"]>;
|
|
19
|
+
goToImplementation(path: string, line: number, character: number): Promise<OperationOutputs["workspace.goToImplementation"]>;
|
|
20
|
+
findReferences(path: string, line: number, character: number, includeDeclaration: boolean): Promise<OperationOutputs["workspace.findReferences"]>;
|
|
21
|
+
hover(path: string, line: number, character: number): Promise<OperationOutputs["workspace.hover"]>;
|
|
22
|
+
documentSymbols(path: string): Promise<OperationOutputs["workspace.documentSymbols"]>;
|
|
23
|
+
diagnostics(path: string): Promise<OperationOutputs["workspace.diagnostics"]>;
|
|
24
|
+
prepareCallHierarchy(path: string, line: number, character: number): Promise<OperationOutputs["workspace.prepareCallHierarchy"]>;
|
|
25
|
+
incomingCalls(path: string, line: number, character: number): Promise<OperationOutputs["workspace.incomingCalls"]>;
|
|
26
|
+
outgoingCalls(path: string, line: number, character: number): Promise<OperationOutputs["workspace.outgoingCalls"]>;
|
|
39
27
|
populateSymbolGraph(path: string, maxFiles: number, maxSymbolsPerFile: number, waitMs?: number): Promise<JobSnapshot<PopulateSymbolGraphResult>>;
|
|
40
28
|
jobStatus(jobId: string): Promise<JobSnapshot<PopulateSymbolGraphResult>>;
|
|
41
29
|
reachableFrom(path: string, line: number, character: number, maxDepth: number, kind?: SymbolEdgeKind): Promise<readonly SymbolNode[]>;
|
|
@@ -50,8 +38,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
50
38
|
() => workspaceForCodeIntelligencePath(path),
|
|
51
39
|
async ({ workspaceId }) => {
|
|
52
40
|
const client = await lectorClient();
|
|
53
|
-
|
|
54
|
-
return locations;
|
|
41
|
+
return client.call("workspace.goToDefinition", { workspaceId, path, line, character });
|
|
55
42
|
},
|
|
56
43
|
);
|
|
57
44
|
},
|
|
@@ -60,8 +47,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
60
47
|
() => workspaceForCodeIntelligencePath(path),
|
|
61
48
|
async ({ workspaceId }) => {
|
|
62
49
|
const client = await lectorClient();
|
|
63
|
-
|
|
64
|
-
return locations;
|
|
50
|
+
return client.call("workspace.goToImplementation", { workspaceId, path, line, character });
|
|
65
51
|
},
|
|
66
52
|
);
|
|
67
53
|
},
|
|
@@ -70,8 +56,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
70
56
|
() => workspaceForCodeIntelligencePath(path),
|
|
71
57
|
async ({ workspaceId }) => {
|
|
72
58
|
const client = await lectorClient();
|
|
73
|
-
|
|
74
|
-
return locations;
|
|
59
|
+
return client.call("workspace.findReferences", { workspaceId, path, line, character, includeDeclaration });
|
|
75
60
|
},
|
|
76
61
|
);
|
|
77
62
|
},
|
|
@@ -80,8 +65,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
80
65
|
() => workspaceForCodeIntelligencePath(path),
|
|
81
66
|
async ({ workspaceId }) => {
|
|
82
67
|
const client = await lectorClient();
|
|
83
|
-
|
|
84
|
-
return hover;
|
|
68
|
+
return client.call("workspace.hover", { workspaceId, path, line, character });
|
|
85
69
|
},
|
|
86
70
|
);
|
|
87
71
|
},
|
|
@@ -90,8 +74,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
90
74
|
() => workspaceForCodeIntelligencePath(path),
|
|
91
75
|
async ({ workspaceId }) => {
|
|
92
76
|
const client = await lectorClient();
|
|
93
|
-
|
|
94
|
-
return symbols;
|
|
77
|
+
return client.call("workspace.documentSymbols", { workspaceId, path });
|
|
95
78
|
},
|
|
96
79
|
);
|
|
97
80
|
},
|
|
@@ -100,8 +83,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
100
83
|
() => workspaceForCodeIntelligencePath(path),
|
|
101
84
|
async ({ workspaceId }) => {
|
|
102
85
|
const client = await lectorClient();
|
|
103
|
-
|
|
104
|
-
return diagnostics;
|
|
86
|
+
return client.call("workspace.diagnostics", { workspaceId, path });
|
|
105
87
|
},
|
|
106
88
|
);
|
|
107
89
|
},
|
|
@@ -110,8 +92,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
110
92
|
() => workspaceForCodeIntelligencePath(path),
|
|
111
93
|
async ({ workspaceId }) => {
|
|
112
94
|
const client = await lectorClient();
|
|
113
|
-
|
|
114
|
-
return items;
|
|
95
|
+
return client.call("workspace.prepareCallHierarchy", { workspaceId, path, line, character });
|
|
115
96
|
},
|
|
116
97
|
);
|
|
117
98
|
},
|
|
@@ -120,8 +101,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
120
101
|
() => workspaceForCodeIntelligencePath(path),
|
|
121
102
|
async ({ workspaceId }) => {
|
|
122
103
|
const client = await lectorClient();
|
|
123
|
-
|
|
124
|
-
return calls;
|
|
104
|
+
return client.call("workspace.incomingCalls", { workspaceId, path, line, character });
|
|
125
105
|
},
|
|
126
106
|
);
|
|
127
107
|
},
|
|
@@ -130,8 +110,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
130
110
|
() => workspaceForCodeIntelligencePath(path),
|
|
131
111
|
async ({ workspaceId }) => {
|
|
132
112
|
const client = await lectorClient();
|
|
133
|
-
|
|
134
|
-
return calls;
|
|
113
|
+
return client.call("workspace.outgoingCalls", { workspaceId, path, line, character });
|
|
135
114
|
},
|
|
136
115
|
);
|
|
137
116
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TextSearchResult, WorkspaceQueryOutcome
|
|
1
|
+
import type { SymbolSearchResult, TextSearchResult, WorkspaceQueryOutcome } from "@danypops/lector";
|
|
2
2
|
import { lectorClient, workspaceForDirectory } from "./lector-client.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -10,11 +10,7 @@ import { lectorClient, workspaceForDirectory } from "./lector-client.ts";
|
|
|
10
10
|
* required, same "no implicit fallback" convention as find_symbols/search_code.
|
|
11
11
|
*/
|
|
12
12
|
export interface CrossWorkspaceSearchOperations {
|
|
13
|
-
findSymbols(
|
|
14
|
-
query: string,
|
|
15
|
-
directories: readonly string[],
|
|
16
|
-
timeoutMs?: number,
|
|
17
|
-
): Promise<readonly WorkspaceQueryOutcome<{ symbols: readonly WorkspaceSymbol[] }>[]>;
|
|
13
|
+
findSymbols(query: string, directories: readonly string[], timeoutMs?: number): Promise<readonly WorkspaceQueryOutcome<SymbolSearchResult>[]>;
|
|
18
14
|
searchText(
|
|
19
15
|
query: string,
|
|
20
16
|
directories: readonly string[],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TextSearchResult, WorkspaceQueryOutcome
|
|
1
|
+
import type { SymbolSearchResult, TextSearchResult, WorkspaceQueryOutcome } from "@danypops/lector";
|
|
2
2
|
import { keyHint } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import type { LectorTheme } from "./lector-tui-theme.ts";
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ function formatOutcomeHeader(outcome: WorkspaceQueryOutcome<unknown>, theme: Lec
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function formatFindSymbolsAcrossProjectsResult(
|
|
20
|
-
results: readonly WorkspaceQueryOutcome<
|
|
20
|
+
results: readonly WorkspaceQueryOutcome<SymbolSearchResult>[] | undefined,
|
|
21
21
|
expanded: boolean,
|
|
22
22
|
theme: LectorTheme,
|
|
23
23
|
): string {
|
|
@@ -26,6 +26,9 @@ export function formatFindSymbolsAcrossProjectsResult(
|
|
|
26
26
|
for (const outcome of results) {
|
|
27
27
|
lines.push(formatOutcomeHeader(outcome, theme));
|
|
28
28
|
if (outcome.status !== "ready") continue;
|
|
29
|
+
lines.push(
|
|
30
|
+
theme.fg("muted", ` ${outcome.result.provenance.fidelity} via ${outcome.result.provenance.backend}${outcome.result.truncated ? " (truncated)" : ""}`),
|
|
31
|
+
);
|
|
29
32
|
if (outcome.result.symbols.length === 0) {
|
|
30
33
|
lines.push(theme.fg("dim", " no symbols matched"));
|
|
31
34
|
continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SymbolSearchResult } from "@danypops/lector";
|
|
2
2
|
import { lectorClient, withWorkspace, workspaceForDirectory } from "./lector-client.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -15,7 +15,7 @@ import { lectorClient, withWorkspace, workspaceForDirectory } from "./lector-cli
|
|
|
15
15
|
* is no implicit fallback anywhere in this module.
|
|
16
16
|
*/
|
|
17
17
|
export interface FindSymbolsOperations {
|
|
18
|
-
findSymbols(query: string, directory: string): Promise<
|
|
18
|
+
findSymbols(query: string, directory: string): Promise<SymbolSearchResult>;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function createLectorFindSymbolsOperations(): FindSymbolsOperations {
|
|
@@ -25,8 +25,7 @@ export function createLectorFindSymbolsOperations(): FindSymbolsOperations {
|
|
|
25
25
|
() => workspaceForDirectory(directory),
|
|
26
26
|
async ({ workspaceId }) => {
|
|
27
27
|
const client = await lectorClient();
|
|
28
|
-
|
|
29
|
-
return symbols;
|
|
28
|
+
return client.call("workspace.findSymbols", { workspaceId, query });
|
|
30
29
|
},
|
|
31
30
|
);
|
|
32
31
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WorkspaceSymbol } from "@danypops/lector";
|
|
1
|
+
import type { SymbolSearchResult, WorkspaceSymbol } from "@danypops/lector";
|
|
2
2
|
import { keyHint } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { colorForKind, formatLocation, type LectorTheme } from "./lector-tui-theme.ts";
|
|
4
4
|
|
|
@@ -32,14 +32,17 @@ function formatSymbolLine(symbol: WorkspaceSymbol, theme: FindSymbolsTheme, kind
|
|
|
32
32
|
return `${kind} ${name} ${location}`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function formatFindSymbolsResult(
|
|
36
|
-
if (!
|
|
37
|
-
|
|
35
|
+
export function formatFindSymbolsResult(result: SymbolSearchResult | undefined, query: string, expanded: boolean, theme: FindSymbolsTheme): string {
|
|
36
|
+
if (!result) return theme.fg("dim", `No symbols found matching "${query}".`);
|
|
37
|
+
const { symbols, provenance, truncated } = result;
|
|
38
|
+
const source = `${provenance.fidelity} via ${provenance.backend}${truncated ? " (truncated)" : ""}`;
|
|
39
|
+
if (symbols.length === 0) {
|
|
40
|
+
return `${theme.fg("muted", source)}\n${theme.fg("dim", `No symbols found matching "${query}".`)}`;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
const kindColumnWidth = Math.max(...symbols.map((symbol) => symbol.kind.length));
|
|
41
44
|
const displayCount = expanded ? symbols.length : Math.min(symbols.length, DEFAULT_VISIBLE_RESULTS);
|
|
42
|
-
const lines = [theme.fg("muted", `${symbols.length} symbol${symbols.length === 1 ? "" : "s"} matching "${query}":`)];
|
|
45
|
+
const lines = [theme.fg("muted", source), theme.fg("muted", `${symbols.length} symbol${symbols.length === 1 ? "" : "s"} matching "${query}":`)];
|
|
43
46
|
|
|
44
47
|
for (const symbol of symbols.slice(0, displayCount)) {
|
|
45
48
|
lines.push(formatSymbolLine(symbol, theme, kindColumnWidth));
|
package/extension/src/index.ts
CHANGED
|
@@ -8,15 +8,17 @@ import type {
|
|
|
8
8
|
GitStatusSummary,
|
|
9
9
|
Hover,
|
|
10
10
|
IncomingCall,
|
|
11
|
+
IntelligenceProvenance,
|
|
11
12
|
JobSnapshot,
|
|
12
13
|
OutgoingCall,
|
|
14
|
+
PackageSourceOperationResult,
|
|
13
15
|
PopulateSymbolGraphResult,
|
|
14
16
|
RepoFetchResult,
|
|
15
17
|
SymbolNode,
|
|
18
|
+
SymbolSearchResult,
|
|
16
19
|
TextSearchResult,
|
|
17
20
|
WorkspaceLocation,
|
|
18
21
|
WorkspaceQueryOutcome,
|
|
19
|
-
WorkspaceSymbol,
|
|
20
22
|
} from "@danypops/lector";
|
|
21
23
|
import { createEditToolDefinition, createReadToolDefinition, createWriteToolDefinition, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
22
24
|
import { Text } from "@earendil-works/pi-tui";
|
|
@@ -55,14 +57,30 @@ import { formatFindSymbolsCall, formatFindSymbolsResult } from "./find-symbols-r
|
|
|
55
57
|
import { createLectorGitOperations } from "./git-operations.ts";
|
|
56
58
|
import { formatGitDiffCall, formatGitDiffResult, formatGitLogCall, formatGitLogResult, formatGitStatusCall, formatGitStatusResult } from "./git-rendering.ts";
|
|
57
59
|
import { nearestGitRoot } from "./nearest-workspace-root.ts";
|
|
60
|
+
import { createLectorPackageSourceOperations } from "./package-source-operations.ts";
|
|
61
|
+
import { formatPackageSourceCall, formatPackageSourceResult } from "./package-source-rendering.ts";
|
|
58
62
|
import { createLectorReadOperations } from "./read-operations.ts";
|
|
59
63
|
import { createLectorRepoFetchOperations } from "./repo-fetch-operations.ts";
|
|
60
64
|
import { formatRepoFetchCall, formatRepoFetchResult } from "./repo-fetch-rendering.ts";
|
|
61
65
|
import { createLectorSearchOperations } from "./search-operations.ts";
|
|
62
66
|
import { formatSearchCall, formatSearchResult } from "./search-rendering.ts";
|
|
63
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
type CachePresentationState,
|
|
69
|
+
cacheContextMessage,
|
|
70
|
+
createWorkspaceCacheOperations,
|
|
71
|
+
describeCacheState,
|
|
72
|
+
monitorWorkspaceCache,
|
|
73
|
+
} from "./workspace-cache-operations.ts";
|
|
64
74
|
import { createLectorWriteOperations } from "./write-operations.ts";
|
|
65
75
|
|
|
76
|
+
function describeIntelligenceSource(provenance: IntelligenceProvenance): string {
|
|
77
|
+
return `${provenance.fidelity} via ${provenance.backend}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderIntelligenceSource(body: string, provenance: IntelligenceProvenance | undefined, theme: { fg(color: "muted", text: string): string }): string {
|
|
81
|
+
return provenance ? `${theme.fg("muted", describeIntelligenceSource(provenance))}\n${body}` : body;
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
/**
|
|
67
85
|
* pi-lector -- the thin Pi host adapter for Lector. Overrides the built-in
|
|
68
86
|
* read/write/edit tools by name with Lector-backed Operations, so built-in
|
|
@@ -87,13 +105,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
87
105
|
let cacheState: CachePresentationState | undefined;
|
|
88
106
|
let lastInjectedCacheState: string | undefined;
|
|
89
107
|
|
|
90
|
-
function describeCacheState(state: CachePresentationState): string {
|
|
91
|
-
if (state.status === "not-cached") return `not cached (${state.reason})`;
|
|
92
|
-
if (state.status === "caching") return `caching (job ${state.jobId})`;
|
|
93
|
-
if (state.status === "finished-caching") return `finished caching (job ${state.job.id})`;
|
|
94
|
-
return "cached";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
108
|
pi.on("before_agent_start", () => {
|
|
98
109
|
if (!cacheState) return;
|
|
99
110
|
const description = describeCacheState(cacheState);
|
|
@@ -102,7 +113,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
102
113
|
return {
|
|
103
114
|
message: {
|
|
104
115
|
customType: "lector-cache-status",
|
|
105
|
-
content:
|
|
116
|
+
content: cacheContextMessage(cacheState),
|
|
106
117
|
display: false,
|
|
107
118
|
},
|
|
108
119
|
};
|
|
@@ -160,7 +171,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
160
171
|
"Search a workspace for functions, classes, interfaces, types, enums, and methods by name " +
|
|
161
172
|
"(case-insensitive substring match). Returns each match's kind and file location. `directory` " +
|
|
162
173
|
"selects which project to search -- pass the current working directory to search it, or any " +
|
|
163
|
-
"other project's directory to get code intelligence there without needing to be in it.",
|
|
174
|
+
"other project's directory to get code intelligence there without needing to be in it. Results identify semantic language-server authority or structural compiler/parser fallback.",
|
|
164
175
|
promptSnippet: "Search a workspace for a symbol (function, class, etc.) by name",
|
|
165
176
|
promptGuidelines: [
|
|
166
177
|
"Use find_symbols to locate where a function, class, interface, type, enum, or method is declared by name, instead of grepping for it.",
|
|
@@ -172,14 +183,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
172
183
|
}),
|
|
173
184
|
async execute(_toolCallId, params) {
|
|
174
185
|
const directory = resolve(cwd, params.directory);
|
|
175
|
-
const
|
|
186
|
+
const result = await findSymbolsOperations.findSymbols(params.query, directory);
|
|
187
|
+
const { symbols, provenance, truncated } = result;
|
|
188
|
+
const source = `${provenance.fidelity} via ${provenance.backend}${truncated ? " (truncated)" : ""}`;
|
|
176
189
|
const text =
|
|
177
190
|
symbols.length === 0
|
|
178
|
-
?
|
|
179
|
-
: symbols
|
|
191
|
+
? `${source}\nNo symbols found matching "${params.query}".`
|
|
192
|
+
: `${source}\n${symbols
|
|
180
193
|
.map((symbol) => `${symbol.kind} ${symbol.name} -- ${symbol.location.path}:${symbol.location.line}:${symbol.location.character}`)
|
|
181
|
-
.join("\n")
|
|
182
|
-
return { content: [{ type: "text", text }], details:
|
|
194
|
+
.join("\n")}`;
|
|
195
|
+
return { content: [{ type: "text", text }], details: result };
|
|
183
196
|
},
|
|
184
197
|
renderCall(args, theme, context) {
|
|
185
198
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -197,10 +210,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
197
210
|
.join("\n");
|
|
198
211
|
return new Text(theme.fg("error", errorText || "find_symbols failed"), 0, 0);
|
|
199
212
|
}
|
|
200
|
-
const details = result.details as
|
|
213
|
+
const details = result.details as SymbolSearchResult | undefined;
|
|
201
214
|
const query = typeof context.args?.query === "string" ? context.args.query : "";
|
|
202
215
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
203
|
-
text.setText(formatFindSymbolsResult(details
|
|
216
|
+
text.setText(formatFindSymbolsResult(details, query, expanded, theme));
|
|
204
217
|
return text;
|
|
205
218
|
},
|
|
206
219
|
});
|
|
@@ -221,9 +234,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
221
234
|
parameters: Type.Object(positionParameters),
|
|
222
235
|
async execute(_toolCallId, params) {
|
|
223
236
|
const path = resolve(cwd, params.path);
|
|
224
|
-
const
|
|
225
|
-
const text = locations.length === 0 ? "No definition found." : locations.map((l) => `${l.path}:${l.line}:${l.character}`).join("\n");
|
|
226
|
-
return { content: [{ type: "text", text }], details
|
|
237
|
+
const details = await codeIntelligenceOperations.goToDefinition(path, params.line, params.character);
|
|
238
|
+
const text = details.locations.length === 0 ? "No definition found." : details.locations.map((l) => `${l.path}:${l.line}:${l.character}`).join("\n");
|
|
239
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${text}` }], details };
|
|
227
240
|
},
|
|
228
241
|
renderCall(args, theme, context) {
|
|
229
242
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -239,9 +252,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
239
252
|
.join("\n");
|
|
240
253
|
return new Text(theme.fg("error", errorText || "go_to_definition failed"), 0, 0);
|
|
241
254
|
}
|
|
242
|
-
const details = result.details as { locations?: readonly WorkspaceLocation[] } | undefined;
|
|
255
|
+
const details = result.details as { locations?: readonly WorkspaceLocation[]; provenance?: IntelligenceProvenance } | undefined;
|
|
243
256
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
244
|
-
text.setText(formatGoToDefinitionResult(details?.locations, expanded, theme));
|
|
257
|
+
text.setText(renderIntelligenceSource(formatGoToDefinitionResult(details?.locations, expanded, theme), details?.provenance, theme));
|
|
245
258
|
return text;
|
|
246
259
|
},
|
|
247
260
|
});
|
|
@@ -258,9 +271,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
258
271
|
parameters: Type.Object(positionParameters),
|
|
259
272
|
async execute(_toolCallId, params) {
|
|
260
273
|
const path = resolve(cwd, params.path);
|
|
261
|
-
const
|
|
262
|
-
const text =
|
|
263
|
-
|
|
274
|
+
const details = await codeIntelligenceOperations.goToImplementation(path, params.line, params.character);
|
|
275
|
+
const text =
|
|
276
|
+
details.locations.length === 0 ? "No implementation found." : details.locations.map((l) => `${l.path}:${l.line}:${l.character}`).join("\n");
|
|
277
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${text}` }], details };
|
|
264
278
|
},
|
|
265
279
|
renderCall(args, theme, context) {
|
|
266
280
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -276,9 +290,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
276
290
|
.join("\n");
|
|
277
291
|
return new Text(theme.fg("error", errorText || "go_to_implementation failed"), 0, 0);
|
|
278
292
|
}
|
|
279
|
-
const details = result.details as { locations?: readonly WorkspaceLocation[] } | undefined;
|
|
293
|
+
const details = result.details as { locations?: readonly WorkspaceLocation[]; provenance?: IntelligenceProvenance } | undefined;
|
|
280
294
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
281
|
-
text.setText(formatGoToImplementationResult(details?.locations, expanded, theme));
|
|
295
|
+
text.setText(renderIntelligenceSource(formatGoToImplementationResult(details?.locations, expanded, theme), details?.provenance, theme));
|
|
282
296
|
return text;
|
|
283
297
|
},
|
|
284
298
|
});
|
|
@@ -298,9 +312,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
298
312
|
}),
|
|
299
313
|
async execute(_toolCallId, params) {
|
|
300
314
|
const path = resolve(cwd, params.path);
|
|
301
|
-
const
|
|
302
|
-
const text = locations.length === 0 ? "No references found." : locations.map((l) => `${l.path}:${l.line}:${l.character}`).join("\n");
|
|
303
|
-
return { content: [{ type: "text", text }], details
|
|
315
|
+
const details = await codeIntelligenceOperations.findReferences(path, params.line, params.character, params.includeDeclaration);
|
|
316
|
+
const text = details.locations.length === 0 ? "No references found." : details.locations.map((l) => `${l.path}:${l.line}:${l.character}`).join("\n");
|
|
317
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${text}` }], details };
|
|
304
318
|
},
|
|
305
319
|
renderCall(args, theme, context) {
|
|
306
320
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -316,9 +330,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
316
330
|
.join("\n");
|
|
317
331
|
return new Text(theme.fg("error", errorText || "find_references failed"), 0, 0);
|
|
318
332
|
}
|
|
319
|
-
const details = result.details as { locations?: readonly WorkspaceLocation[] } | undefined;
|
|
333
|
+
const details = result.details as { locations?: readonly WorkspaceLocation[]; provenance?: IntelligenceProvenance } | undefined;
|
|
320
334
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
321
|
-
text.setText(formatFindReferencesResult(details?.locations, expanded, theme));
|
|
335
|
+
text.setText(renderIntelligenceSource(formatFindReferencesResult(details?.locations, expanded, theme), details?.provenance, theme));
|
|
322
336
|
return text;
|
|
323
337
|
},
|
|
324
338
|
});
|
|
@@ -334,8 +348,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
334
348
|
parameters: Type.Object(positionParameters),
|
|
335
349
|
async execute(_toolCallId, params) {
|
|
336
350
|
const path = resolve(cwd, params.path);
|
|
337
|
-
const
|
|
338
|
-
return {
|
|
351
|
+
const details = await codeIntelligenceOperations.hover(path, params.line, params.character);
|
|
352
|
+
return {
|
|
353
|
+
content: [
|
|
354
|
+
{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${details.hover?.contents ?? "No hover information available."}` },
|
|
355
|
+
],
|
|
356
|
+
details,
|
|
357
|
+
};
|
|
339
358
|
},
|
|
340
359
|
renderCall(args, theme, context) {
|
|
341
360
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -351,9 +370,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
351
370
|
.join("\n");
|
|
352
371
|
return new Text(theme.fg("error", errorText || "hover failed"), 0, 0);
|
|
353
372
|
}
|
|
354
|
-
const details = result.details as { hover?: Hover } | undefined;
|
|
373
|
+
const details = result.details as { hover?: Hover; provenance?: IntelligenceProvenance } | undefined;
|
|
355
374
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
356
|
-
text.setText(formatHoverResult(details?.hover, expanded, theme));
|
|
375
|
+
text.setText(renderIntelligenceSource(formatHoverResult(details?.hover, expanded, theme), details?.provenance, theme));
|
|
357
376
|
return text;
|
|
358
377
|
},
|
|
359
378
|
});
|
|
@@ -367,9 +386,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
367
386
|
parameters: Type.Object({ path: Type.String({ description: "Absolute or cwd-relative path to the file" }) }),
|
|
368
387
|
async execute(_toolCallId, params) {
|
|
369
388
|
const path = resolve(cwd, params.path);
|
|
370
|
-
const
|
|
371
|
-
const text = symbols.length === 0 ? "No symbols found." : symbols.map((s) => `${s.kind} ${s.name}`).join("\n");
|
|
372
|
-
return { content: [{ type: "text", text }], details
|
|
389
|
+
const details = await codeIntelligenceOperations.documentSymbols(path);
|
|
390
|
+
const text = details.symbols.length === 0 ? "No symbols found." : details.symbols.map((s) => `${s.kind} ${s.name}`).join("\n");
|
|
391
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${text}` }], details };
|
|
373
392
|
},
|
|
374
393
|
renderCall(args, theme, context) {
|
|
375
394
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -385,9 +404,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
385
404
|
.join("\n");
|
|
386
405
|
return new Text(theme.fg("error", errorText || "document_symbols failed"), 0, 0);
|
|
387
406
|
}
|
|
388
|
-
const details = result.details as { symbols?: readonly DocumentSymbolEntry[] } | undefined;
|
|
407
|
+
const details = result.details as { symbols?: readonly DocumentSymbolEntry[]; provenance?: IntelligenceProvenance } | undefined;
|
|
389
408
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
390
|
-
text.setText(formatDocumentSymbolsResult(details?.symbols, expanded, theme));
|
|
409
|
+
text.setText(renderIntelligenceSource(formatDocumentSymbolsResult(details?.symbols, expanded, theme), details?.provenance, theme));
|
|
391
410
|
return text;
|
|
392
411
|
},
|
|
393
412
|
});
|
|
@@ -401,12 +420,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
401
420
|
parameters: Type.Object({ path: Type.String({ description: "Absolute or cwd-relative path to the file" }) }),
|
|
402
421
|
async execute(_toolCallId, params) {
|
|
403
422
|
const path = resolve(cwd, params.path);
|
|
404
|
-
const
|
|
423
|
+
const details = await codeIntelligenceOperations.diagnostics(path);
|
|
405
424
|
const text =
|
|
406
|
-
diagnostics.length === 0
|
|
425
|
+
details.diagnostics.length === 0
|
|
407
426
|
? "No diagnostics."
|
|
408
|
-
: diagnostics.map((d) => `${d.severity} ${d.range.path}:${d.range.start.line}:${d.range.start.character} -- ${d.message}`).join("\n");
|
|
409
|
-
return { content: [{ type: "text", text }], details
|
|
427
|
+
: details.diagnostics.map((d) => `${d.severity} ${d.range.path}:${d.range.start.line}:${d.range.start.character} -- ${d.message}`).join("\n");
|
|
428
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${text}` }], details };
|
|
410
429
|
},
|
|
411
430
|
renderCall(args, theme, context) {
|
|
412
431
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -422,9 +441,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
422
441
|
.join("\n");
|
|
423
442
|
return new Text(theme.fg("error", errorText || "diagnostics failed"), 0, 0);
|
|
424
443
|
}
|
|
425
|
-
const details = result.details as { diagnostics?: readonly Diagnostic[] } | undefined;
|
|
444
|
+
const details = result.details as { diagnostics?: readonly Diagnostic[]; provenance?: IntelligenceProvenance } | undefined;
|
|
426
445
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
427
|
-
text.setText(formatDiagnosticsResult(details?.diagnostics, expanded, theme));
|
|
446
|
+
text.setText(renderIntelligenceSource(formatDiagnosticsResult(details?.diagnostics, expanded, theme), details?.provenance, theme));
|
|
428
447
|
return text;
|
|
429
448
|
},
|
|
430
449
|
});
|
|
@@ -440,12 +459,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
440
459
|
parameters: Type.Object(positionParameters),
|
|
441
460
|
async execute(_toolCallId, params) {
|
|
442
461
|
const path = resolve(cwd, params.path);
|
|
443
|
-
const
|
|
462
|
+
const details = await codeIntelligenceOperations.prepareCallHierarchy(path, params.line, params.character);
|
|
444
463
|
const text =
|
|
445
|
-
items.length === 0
|
|
464
|
+
details.items.length === 0
|
|
446
465
|
? "No call-hierarchy root at this position."
|
|
447
|
-
: items.map((i) => `${i.kind} ${i.name} -- ${i.location.path}:${i.location.line}:${i.location.character}`).join("\n");
|
|
448
|
-
return { content: [{ type: "text", text }], details
|
|
466
|
+
: details.items.map((i) => `${i.kind} ${i.name} -- ${i.location.path}:${i.location.line}:${i.location.character}`).join("\n");
|
|
467
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${text}` }], details };
|
|
449
468
|
},
|
|
450
469
|
renderCall(args, theme, context) {
|
|
451
470
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -461,9 +480,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
461
480
|
.join("\n");
|
|
462
481
|
return new Text(theme.fg("error", errorText || "prepare_call_hierarchy failed"), 0, 0);
|
|
463
482
|
}
|
|
464
|
-
const details = result.details as { items?: readonly CallHierarchyEntry[] } | undefined;
|
|
483
|
+
const details = result.details as { items?: readonly CallHierarchyEntry[]; provenance?: IntelligenceProvenance } | undefined;
|
|
465
484
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
466
|
-
text.setText(formatPrepareCallHierarchyResult(details?.items, theme));
|
|
485
|
+
text.setText(renderIntelligenceSource(formatPrepareCallHierarchyResult(details?.items, theme), details?.provenance, theme));
|
|
467
486
|
return text;
|
|
468
487
|
},
|
|
469
488
|
});
|
|
@@ -479,12 +498,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
479
498
|
parameters: Type.Object(positionParameters),
|
|
480
499
|
async execute(_toolCallId, params) {
|
|
481
500
|
const path = resolve(cwd, params.path);
|
|
482
|
-
const
|
|
501
|
+
const details = await codeIntelligenceOperations.incomingCalls(path, params.line, params.character);
|
|
483
502
|
const text =
|
|
484
|
-
calls.length === 0
|
|
503
|
+
details.calls.length === 0
|
|
485
504
|
? "No incoming calls found."
|
|
486
|
-
: calls
|
|
487
|
-
|
|
505
|
+
: details.calls
|
|
506
|
+
.map((c) => `${c.from.kind} ${c.from.name} -- ${c.from.location.path}:${c.from.location.line}:${c.from.location.character}`)
|
|
507
|
+
.join("\n");
|
|
508
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${text}` }], details };
|
|
488
509
|
},
|
|
489
510
|
renderCall(args, theme, context) {
|
|
490
511
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -500,9 +521,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
500
521
|
.join("\n");
|
|
501
522
|
return new Text(theme.fg("error", errorText || "incoming_calls failed"), 0, 0);
|
|
502
523
|
}
|
|
503
|
-
const details = result.details as { calls?: readonly IncomingCall[] } | undefined;
|
|
524
|
+
const details = result.details as { calls?: readonly IncomingCall[]; provenance?: IntelligenceProvenance } | undefined;
|
|
504
525
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
505
|
-
text.setText(formatIncomingCallsResult(details?.calls, expanded, theme));
|
|
526
|
+
text.setText(renderIntelligenceSource(formatIncomingCallsResult(details?.calls, expanded, theme), details?.provenance, theme));
|
|
506
527
|
return text;
|
|
507
528
|
},
|
|
508
529
|
});
|
|
@@ -516,12 +537,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
516
537
|
parameters: Type.Object(positionParameters),
|
|
517
538
|
async execute(_toolCallId, params) {
|
|
518
539
|
const path = resolve(cwd, params.path);
|
|
519
|
-
const
|
|
540
|
+
const details = await codeIntelligenceOperations.outgoingCalls(path, params.line, params.character);
|
|
520
541
|
const text =
|
|
521
|
-
calls.length === 0
|
|
542
|
+
details.calls.length === 0
|
|
522
543
|
? "No outgoing calls found."
|
|
523
|
-
: calls.map((c) => `${c.to.kind} ${c.to.name} -- ${c.to.location.path}:${c.to.location.line}:${c.to.location.character}`).join("\n");
|
|
524
|
-
return { content: [{ type: "text", text }], details
|
|
544
|
+
: details.calls.map((c) => `${c.to.kind} ${c.to.name} -- ${c.to.location.path}:${c.to.location.line}:${c.to.location.character}`).join("\n");
|
|
545
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(details.provenance)}\n${text}` }], details };
|
|
525
546
|
},
|
|
526
547
|
renderCall(args, theme, context) {
|
|
527
548
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -537,9 +558,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
537
558
|
.join("\n");
|
|
538
559
|
return new Text(theme.fg("error", errorText || "outgoing_calls failed"), 0, 0);
|
|
539
560
|
}
|
|
540
|
-
const details = result.details as { calls?: readonly OutgoingCall[] } | undefined;
|
|
561
|
+
const details = result.details as { calls?: readonly OutgoingCall[]; provenance?: IntelligenceProvenance } | undefined;
|
|
541
562
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
542
|
-
text.setText(formatOutgoingCallsResult(details?.calls, expanded, theme));
|
|
563
|
+
text.setText(renderIntelligenceSource(formatOutgoingCallsResult(details?.calls, expanded, theme), details?.provenance, theme));
|
|
543
564
|
return text;
|
|
544
565
|
},
|
|
545
566
|
});
|
|
@@ -825,6 +846,45 @@ export default function (pi: ExtensionAPI) {
|
|
|
825
846
|
},
|
|
826
847
|
});
|
|
827
848
|
|
|
849
|
+
const packageSourceOperations = createLectorPackageSourceOperations();
|
|
850
|
+
pi.registerTool({
|
|
851
|
+
name: "package_source",
|
|
852
|
+
label: "Package Source",
|
|
853
|
+
description:
|
|
854
|
+
"Resolve an installed npm package to verified exact repository source. Uses the project's lockfile, bounded registry metadata, and an exact Git ref/commit; registers verified source as a read-only workspace for the other Lector tools.",
|
|
855
|
+
promptSnippet: "Resolve an installed npm package to exact read-only source",
|
|
856
|
+
parameters: Type.Object({
|
|
857
|
+
directory: Type.String({ description: "Project directory containing the npm-family lockfile" }),
|
|
858
|
+
name: Type.String({ description: "Installed package name, including scope when present" }),
|
|
859
|
+
version: Type.Optional(Type.String({ description: "Exact installed version; required when the lockfile contains several versions" })),
|
|
860
|
+
registry: Type.Optional(Type.String({ description: "npm registry URL; defaults to the public npm registry" })),
|
|
861
|
+
}),
|
|
862
|
+
async execute(_toolCallId, params) {
|
|
863
|
+
const directory = resolve(cwd, params.directory);
|
|
864
|
+
const result = await packageSourceOperations.resolve(directory, params.name, params.version ?? null, params.registry ?? null);
|
|
865
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { result } };
|
|
866
|
+
},
|
|
867
|
+
renderCall(args, theme, context) {
|
|
868
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
869
|
+
text.setText(formatPackageSourceCall(args as { directory?: unknown; name?: unknown; version?: unknown }, theme));
|
|
870
|
+
return text;
|
|
871
|
+
},
|
|
872
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
873
|
+
if (isPartial) return new Text(theme.fg("warning", "Resolving package source..."), 0, 0);
|
|
874
|
+
if (context.isError) {
|
|
875
|
+
const errorText = result.content
|
|
876
|
+
.filter((block) => block.type === "text")
|
|
877
|
+
.map((block) => block.text)
|
|
878
|
+
.join("\n");
|
|
879
|
+
return new Text(theme.fg("error", errorText || "package_source failed"), 0, 0);
|
|
880
|
+
}
|
|
881
|
+
const details = result.details as { result?: PackageSourceOperationResult } | undefined;
|
|
882
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
883
|
+
text.setText(formatPackageSourceResult(details?.result, expanded, theme));
|
|
884
|
+
return text;
|
|
885
|
+
},
|
|
886
|
+
});
|
|
887
|
+
|
|
828
888
|
const repoFetchOperations = createLectorRepoFetchOperations();
|
|
829
889
|
pi.registerTool({
|
|
830
890
|
name: "repo_fetch",
|
|
@@ -894,7 +954,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
894
954
|
.join("\n");
|
|
895
955
|
return new Text(theme.fg("error", errorText || "find_symbols_across_projects failed"), 0, 0);
|
|
896
956
|
}
|
|
897
|
-
const details = result.details as { results?: readonly WorkspaceQueryOutcome<
|
|
957
|
+
const details = result.details as { results?: readonly WorkspaceQueryOutcome<SymbolSearchResult>[] } | undefined;
|
|
898
958
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
899
959
|
text.setText(formatFindSymbolsAcrossProjectsResult(details?.results, expanded, theme));
|
|
900
960
|
return text;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DEFAULT_PACKAGE_SOURCE_BOUNDS, type PackageSourceOperationResult } from "@danypops/lector";
|
|
2
|
+
import { lectorClient } from "./lector-client.ts";
|
|
3
|
+
|
|
4
|
+
export interface PackageSourceOperations {
|
|
5
|
+
resolve(directory: string, name: string, requestedVersion: string | null, registry: string | null): Promise<PackageSourceOperationResult>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createLectorPackageSourceOperations(): PackageSourceOperations {
|
|
9
|
+
return {
|
|
10
|
+
async resolve(directory, name, requestedVersion, registry) {
|
|
11
|
+
const client = await lectorClient();
|
|
12
|
+
return client.call("package.resolveSource", {
|
|
13
|
+
request: {
|
|
14
|
+
projectRoot: directory,
|
|
15
|
+
coordinate: { ecosystem: "npm", registry, name, requestedVersion },
|
|
16
|
+
},
|
|
17
|
+
bounds: DEFAULT_PACKAGE_SOURCE_BOUNDS,
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PackageSourceOperationResult } from "@danypops/lector";
|
|
2
|
+
import type { LectorTheme } from "./lector-tui-theme.ts";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_VISIBLE_CANDIDATES = 5;
|
|
5
|
+
|
|
6
|
+
export function formatPackageSourceCall(args: { directory?: unknown; name?: unknown; version?: unknown }, theme: LectorTheme): string {
|
|
7
|
+
const name = typeof args.name === "string" ? args.name : "";
|
|
8
|
+
const version = typeof args.version === "string" ? `@${args.version}` : "";
|
|
9
|
+
const directory = typeof args.directory === "string" ? args.directory : "";
|
|
10
|
+
return `${theme.fg("toolTitle", theme.bold("package_source"))} ${theme.fg("accent", `${name}${version}`)} ${theme.fg("dim", directory)}`.trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function formatPackageSourceResult(result: PackageSourceOperationResult | undefined, expanded: boolean, theme: LectorTheme): string {
|
|
14
|
+
if (!result) return theme.fg("dim", "No package-source result.");
|
|
15
|
+
const { outcome } = result;
|
|
16
|
+
if (outcome.status === "verified") {
|
|
17
|
+
return [
|
|
18
|
+
`${theme.fg("accent", result.workspaceId ?? "unregistered")} ${theme.fg("success", `${outcome.coordinate.name}@${outcome.coordinate.resolvedVersion}`)}`,
|
|
19
|
+
`${outcome.workspace.cachePath}`,
|
|
20
|
+
`${outcome.repository.url ?? "local source"}@${outcome.repository.resolvedRef ?? "local"} ${outcome.repository.commit ?? outcome.verification.integrity}`,
|
|
21
|
+
].join("\n");
|
|
22
|
+
}
|
|
23
|
+
if (outcome.status === "ambiguous") {
|
|
24
|
+
const visible = expanded ? outcome.candidates : outcome.candidates.slice(0, DEFAULT_VISIBLE_CANDIDATES);
|
|
25
|
+
const lines = [theme.fg("warning", `Ambiguous package source (${outcome.code})`)];
|
|
26
|
+
for (const candidate of visible) lines.push(`${candidate.version} -- ${candidate.source}`);
|
|
27
|
+
const hidden = outcome.candidates.length - visible.length;
|
|
28
|
+
if (hidden > 0 || outcome.truncated) lines.push(theme.fg("dim", outcome.truncated ? "More candidates were truncated by the daemon." : `… ${hidden} more`));
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
}
|
|
31
|
+
if (outcome.status === "unauthenticated") {
|
|
32
|
+
return theme.fg("warning", `Authentication required (${outcome.code}): configure ${outcome.requiredCredentialNames.join(", ")}`);
|
|
33
|
+
}
|
|
34
|
+
if (outcome.status === "oversized") return theme.fg("warning", `Source resolution exceeded ${outcome.resource} limit ${outcome.limit}.`);
|
|
35
|
+
if (outcome.status === "mismatched") return theme.fg("error", `Source mismatch (${outcome.code}): expected ${outcome.expected}, got ${outcome.actual}.`);
|
|
36
|
+
return theme.fg("warning", `Source unavailable (${outcome.code}).`);
|
|
37
|
+
}
|
|
@@ -46,6 +46,20 @@ export type CachePresentationState =
|
|
|
46
46
|
| { readonly status: "finished-caching"; readonly job: JobSnapshot<PopulateSymbolGraphResult> & { readonly status: "succeeded" } }
|
|
47
47
|
| { readonly status: "cached" };
|
|
48
48
|
|
|
49
|
+
export function describeCacheState(state: CachePresentationState): string {
|
|
50
|
+
if (state.status === "not-cached") return `not cached (${state.reason})`;
|
|
51
|
+
if (state.status === "caching") return `caching (job ${state.jobId})`;
|
|
52
|
+
if (state.status === "finished-caching") return `finished caching (job ${state.job.id})`;
|
|
53
|
+
return "cached";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function cacheContextMessage(state: CachePresentationState): string {
|
|
57
|
+
const prefix = `Lector workspace cache: ${describeCacheState(state)}.`;
|
|
58
|
+
if (state.status === "not-cached") return `${prefix} Live code-intelligence operations remain available.`;
|
|
59
|
+
if (state.status === "caching") return `${prefix} The cached graph is still building; use live code-intelligence operations until it is ready.`;
|
|
60
|
+
return `${prefix} The cached graph is ready.`;
|
|
61
|
+
}
|
|
62
|
+
|
|
49
63
|
export interface MonitorWorkspaceCacheOptions {
|
|
50
64
|
readonly directory: string;
|
|
51
65
|
readonly maxFiles: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-lector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Pi host adapter for Lector: overrides read/write/edit with a daemon-backed, hash-guarded filesystem",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"typebox": "*"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@danypops/lector": "^0.1.
|
|
21
|
+
"@danypops/lector": "^0.1.8"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@earendil-works/pi-ai": "^0.81.1",
|