@trim21/personal-pi-extensions 0.1.684 → 0.1.685
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 +8 -6
- package/package.json +1 -1
- package/src/codemode/declarations.ts +3 -3
- package/src/codemode/tool.ts +13 -26
- package/src/gh/base.ts +20 -10
- package/src/gh/render.ts +259 -0
- package/src/gh/schemas.ts +213 -0
- package/src/gh/tools/list-issues.ts +62 -47
- package/src/gh/tools/list-prs.ts +62 -47
- package/src/gh/tools/list-releases.ts +41 -24
- package/src/gh/tools/list-workflow-runs.ts +52 -34
- package/src/gh/tools/read-pr-diff.ts +32 -21
- package/src/gh/tools/read-release.ts +52 -24
- package/src/gh/tools/read-repo.ts +59 -23
- package/src/gh/tools/watch-run.ts +55 -39
- package/src/lib/github.ts +75 -0
- package/src/lib/lsp/inspect-schemas.ts +44 -0
- package/src/lib/lsp/inspect-tool.ts +188 -137
|
@@ -18,8 +18,9 @@ import { Type } from "typebox";
|
|
|
18
18
|
import type { Hover } from "vscode-languageserver-types";
|
|
19
19
|
|
|
20
20
|
import { resolvePathArg } from "../path.js";
|
|
21
|
-
import type
|
|
21
|
+
import { defineStructuredTool, type ToolBus } from "../tool-bus.js";
|
|
22
22
|
import type { InspectLocation } from "./client.js";
|
|
23
|
+
import { lspHoverStructuredSchema, lspLocationsStructuredSchema } from "./inspect-schemas.js";
|
|
23
24
|
import type { LspService } from "./lsp.js";
|
|
24
25
|
import { type LspPosition, symbolCandidates } from "./rename.js";
|
|
25
26
|
|
|
@@ -29,12 +30,31 @@ const MAX_ENTRIES_PER_FILE = 10;
|
|
|
29
30
|
const MAX_FILES_LISTED = 30;
|
|
30
31
|
const MAX_SNIPPET_LENGTH = 200;
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
-
interface
|
|
33
|
+
/** 渲染产出:text 也是消歧分组键。 */
|
|
34
|
+
interface RenderedOutput {
|
|
34
35
|
text: string;
|
|
35
36
|
subtitle: string;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 一次探测的产出:渲染文本 + 结构化结果里除 `text` 之外的字段(选中哪一组,就取那一组的
|
|
41
|
+
* payload)。泛型让每个工具自己决定载荷。
|
|
42
|
+
*/
|
|
43
|
+
interface InspectOutput<TPayload> extends RenderedOutput {
|
|
44
|
+
payload: TPayload;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 位置载荷:服务层归一化后的绝对路径 + 1-based 行列号。 */
|
|
48
|
+
function toLocationPayload(
|
|
49
|
+
locations: readonly InspectLocation[],
|
|
50
|
+
): { path: string; line: number; character: number }[] {
|
|
51
|
+
return locations.map((location) => ({
|
|
52
|
+
path: location.path,
|
|
53
|
+
line: location.line + 1,
|
|
54
|
+
character: location.character + 1,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
38
58
|
function loadPrompt(fileName: string): string {
|
|
39
59
|
return readFileSync(fileURLToPath(new URL(fileName, import.meta.url)), "utf8").trim();
|
|
40
60
|
}
|
|
@@ -113,7 +133,7 @@ function toCoordinates(location: InspectLocation): string {
|
|
|
113
133
|
async function formatDefinitionLocations(
|
|
114
134
|
locations: InspectLocation[],
|
|
115
135
|
cache: Map<string, string[]>,
|
|
116
|
-
): Promise<
|
|
136
|
+
): Promise<RenderedOutput> {
|
|
117
137
|
if (locations.length === 0) {
|
|
118
138
|
return { text: "No definition found for this symbol.", subtitle: "0 definition(s)" };
|
|
119
139
|
}
|
|
@@ -131,7 +151,7 @@ async function formatDefinitionLocations(
|
|
|
131
151
|
async function formatReferenceLocations(
|
|
132
152
|
locations: InspectLocation[],
|
|
133
153
|
cache: Map<string, string[]>,
|
|
134
|
-
): Promise<
|
|
154
|
+
): Promise<RenderedOutput> {
|
|
135
155
|
if (locations.length === 0) {
|
|
136
156
|
return { text: "No references found for this symbol.", subtitle: "0 reference(s)" };
|
|
137
157
|
}
|
|
@@ -198,15 +218,15 @@ function formatHoverContents(contents: Hover["contents"]): string {
|
|
|
198
218
|
* 行内同名候选逐个探测:输出一致即同一符号的多次出现;不一致报歧义并列出
|
|
199
219
|
* 各候选的 1-based 列号(与 lsp-rename 的消歧行为一致)。
|
|
200
220
|
*/
|
|
201
|
-
async function probeSymbolCandidates(options: {
|
|
221
|
+
async function probeSymbolCandidates<TPayload>(options: {
|
|
202
222
|
content: string;
|
|
203
223
|
filePath: string;
|
|
204
224
|
line: number;
|
|
205
225
|
symbol: string;
|
|
206
226
|
character?: number;
|
|
207
227
|
signal?: AbortSignal;
|
|
208
|
-
probe: (position: LspPosition) => Promise<InspectOutput
|
|
209
|
-
}): Promise<InspectOutput
|
|
228
|
+
probe: (position: LspPosition) => Promise<InspectOutput<TPayload>>;
|
|
229
|
+
}): Promise<InspectOutput<TPayload>> {
|
|
210
230
|
const candidates = symbolCandidates(
|
|
211
231
|
options.content,
|
|
212
232
|
options.line - 1,
|
|
@@ -218,12 +238,12 @@ async function probeSymbolCandidates(options: {
|
|
|
218
238
|
`Symbol "${options.symbol}" not found on line ${options.line} of ${options.filePath}. Read the file again and locate the symbol.`,
|
|
219
239
|
);
|
|
220
240
|
}
|
|
221
|
-
const outputs: InspectOutput[] = [];
|
|
241
|
+
const outputs: InspectOutput<TPayload>[] = [];
|
|
222
242
|
for (const candidate of candidates) {
|
|
223
243
|
options.signal?.throwIfAborted();
|
|
224
244
|
outputs.push(await options.probe(candidate));
|
|
225
245
|
}
|
|
226
|
-
const groups = new Map<string, { output: InspectOutput
|
|
246
|
+
const groups = new Map<string, { output: InspectOutput<TPayload>; candidates: LspPosition[] }>();
|
|
227
247
|
for (const [index, candidate] of candidates.entries()) {
|
|
228
248
|
const output = outputs.at(index);
|
|
229
249
|
if (!output) {
|
|
@@ -256,137 +276,168 @@ async function probeSymbolCandidates(options: {
|
|
|
256
276
|
}
|
|
257
277
|
|
|
258
278
|
export function registerLspInspectTools(bus: ToolBus, service: LspService): void {
|
|
259
|
-
bus.register(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
279
|
+
bus.register(
|
|
280
|
+
defineStructuredTool({
|
|
281
|
+
name: "lsp-find-definition",
|
|
282
|
+
label: "Lsp Find Definition",
|
|
283
|
+
description:
|
|
284
|
+
"Find where a code symbol is defined via LSP. Returns 1-based path:line:col locations with source line snippets.",
|
|
285
|
+
promptSnippet: "Find symbol definitions via LSP",
|
|
286
|
+
promptGuidelines: [FIND_DEFINITION_PROMPT],
|
|
287
|
+
parameters: POSITION_SCHEMA,
|
|
288
|
+
structuredSchema: lspLocationsStructuredSchema,
|
|
289
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
290
|
+
signal?.throwIfAborted();
|
|
291
|
+
const { path: filePath, content } = await readSymbolFile(ctx.cwd, params.file_path);
|
|
292
|
+
const cache = new Map<string, string[]>();
|
|
293
|
+
const output = await probeSymbolCandidates({
|
|
294
|
+
content,
|
|
295
|
+
filePath,
|
|
296
|
+
line: params.line,
|
|
297
|
+
symbol: params.symbol,
|
|
298
|
+
character: params.character,
|
|
299
|
+
signal,
|
|
300
|
+
probe: async (position) => {
|
|
301
|
+
const result = await service.inspect({
|
|
302
|
+
file: filePath,
|
|
303
|
+
cwd: ctx.cwd,
|
|
304
|
+
line: position.line,
|
|
305
|
+
character: position.character,
|
|
306
|
+
query: "definition",
|
|
307
|
+
options: { signal },
|
|
308
|
+
});
|
|
309
|
+
const rendered = await formatDefinitionLocations(result.locations, cache);
|
|
310
|
+
return {
|
|
311
|
+
...rendered,
|
|
312
|
+
payload: {
|
|
313
|
+
serverID: result.serverID,
|
|
314
|
+
locations: toLocationPayload(result.locations),
|
|
315
|
+
},
|
|
316
|
+
};
|
|
296
317
|
},
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
318
|
+
});
|
|
319
|
+
return {
|
|
320
|
+
content: [{ type: "text" as const, text: output.text }],
|
|
321
|
+
details: {
|
|
322
|
+
pendant: {
|
|
323
|
+
title: "lsp-find-definition",
|
|
324
|
+
subtitle: `${params.symbol} · ${output.subtitle}`,
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
structuredResult: { ok: true as const, value: { text: output.text, ...output.payload } },
|
|
328
|
+
};
|
|
329
|
+
},
|
|
330
|
+
}),
|
|
331
|
+
);
|
|
301
332
|
|
|
302
|
-
bus.register(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
333
|
+
bus.register(
|
|
334
|
+
defineStructuredTool({
|
|
335
|
+
name: "lsp-find-reference",
|
|
336
|
+
label: "Lsp Find Reference",
|
|
337
|
+
description:
|
|
338
|
+
"Find all references to a code symbol across the workspace via LSP (includes the declaration). Grouped by file with 1-based line:col and source snippets.",
|
|
339
|
+
promptSnippet: "Find symbol references via LSP",
|
|
340
|
+
promptGuidelines: [FIND_REFERENCE_PROMPT],
|
|
341
|
+
parameters: POSITION_SCHEMA,
|
|
342
|
+
structuredSchema: lspLocationsStructuredSchema,
|
|
343
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
344
|
+
signal?.throwIfAborted();
|
|
345
|
+
const { path: filePath, content } = await readSymbolFile(ctx.cwd, params.file_path);
|
|
346
|
+
const cache = new Map<string, string[]>();
|
|
347
|
+
const output = await probeSymbolCandidates({
|
|
348
|
+
content,
|
|
349
|
+
filePath,
|
|
350
|
+
line: params.line,
|
|
351
|
+
symbol: params.symbol,
|
|
352
|
+
character: params.character,
|
|
353
|
+
signal,
|
|
354
|
+
probe: async (position) => {
|
|
355
|
+
const result = await service.inspect({
|
|
356
|
+
file: filePath,
|
|
357
|
+
cwd: ctx.cwd,
|
|
358
|
+
line: position.line,
|
|
359
|
+
character: position.character,
|
|
360
|
+
query: "references",
|
|
361
|
+
options: { signal },
|
|
362
|
+
});
|
|
363
|
+
const rendered = await formatReferenceLocations(result.locations, cache);
|
|
364
|
+
return {
|
|
365
|
+
...rendered,
|
|
366
|
+
// 文本为上下文做了截断(每文件 10 条、最多 30 个文件),载荷给全部位置
|
|
367
|
+
payload: {
|
|
368
|
+
serverID: result.serverID,
|
|
369
|
+
locations: toLocationPayload(result.locations),
|
|
370
|
+
},
|
|
371
|
+
};
|
|
339
372
|
},
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
373
|
+
});
|
|
374
|
+
return {
|
|
375
|
+
content: [{ type: "text" as const, text: output.text }],
|
|
376
|
+
details: {
|
|
377
|
+
pendant: {
|
|
378
|
+
title: "lsp-find-reference",
|
|
379
|
+
subtitle: `${params.symbol} · ${output.subtitle}`,
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
structuredResult: { ok: true as const, value: { text: output.text, ...output.payload } },
|
|
383
|
+
};
|
|
384
|
+
},
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
344
387
|
|
|
345
|
-
bus.register(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
content,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
388
|
+
bus.register(
|
|
389
|
+
defineStructuredTool({
|
|
390
|
+
name: "lsp-inspect",
|
|
391
|
+
label: "Lsp Inspect",
|
|
392
|
+
description:
|
|
393
|
+
"Get hover information (type signature, documentation) for a code symbol via LSP. Content is passed through from the language server.",
|
|
394
|
+
promptSnippet: "Get hover info for a symbol via LSP",
|
|
395
|
+
promptGuidelines: [INSPECT_PROMPT],
|
|
396
|
+
parameters: POSITION_SCHEMA,
|
|
397
|
+
structuredSchema: lspHoverStructuredSchema,
|
|
398
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
399
|
+
signal?.throwIfAborted();
|
|
400
|
+
const { path: filePath, content } = await readSymbolFile(ctx.cwd, params.file_path);
|
|
401
|
+
const output = await probeSymbolCandidates({
|
|
402
|
+
content,
|
|
403
|
+
filePath,
|
|
404
|
+
line: params.line,
|
|
405
|
+
symbol: params.symbol,
|
|
406
|
+
character: params.character,
|
|
407
|
+
signal,
|
|
408
|
+
probe: async (position) => {
|
|
409
|
+
const result = await service.inspect({
|
|
410
|
+
file: filePath,
|
|
411
|
+
cwd: ctx.cwd,
|
|
412
|
+
line: position.line,
|
|
413
|
+
character: position.character,
|
|
414
|
+
query: "hover",
|
|
415
|
+
options: { signal },
|
|
416
|
+
});
|
|
417
|
+
// hover 的内容是服务器给的异构结构(string / MarkedString / MarkupContent),
|
|
418
|
+
// 渲染成文本给它;载荷里只另带是谁回答的
|
|
419
|
+
if (result.hover === null) {
|
|
420
|
+
return {
|
|
421
|
+
text: `No hover information for '${params.symbol}' at line ${params.line} of ${filePath}.`,
|
|
422
|
+
subtitle: "no hover info",
|
|
423
|
+
payload: { serverID: result.serverID },
|
|
424
|
+
};
|
|
425
|
+
}
|
|
373
426
|
return {
|
|
374
|
-
text:
|
|
375
|
-
subtitle: "
|
|
427
|
+
text: formatHoverContents(result.hover.contents),
|
|
428
|
+
subtitle: "hover",
|
|
429
|
+
payload: { serverID: result.serverID },
|
|
376
430
|
};
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
};
|
|
390
|
-
},
|
|
391
|
-
});
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
content: [{ type: "text" as const, text: output.text }],
|
|
435
|
+
details: {
|
|
436
|
+
pendant: { title: "lsp-inspect", subtitle: `${params.symbol} · ${output.subtitle}` },
|
|
437
|
+
},
|
|
438
|
+
structuredResult: { ok: true as const, value: { text: output.text, ...output.payload } },
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
392
443
|
}
|