decorated-pi 0.1.0
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/LICENSE +21 -0
- package/README.md +218 -0
- package/extensions/extend-model.ts +410 -0
- package/extensions/guidance.ts +21 -0
- package/extensions/index.ts +24 -0
- package/extensions/lsp/client.ts +525 -0
- package/extensions/lsp/env.ts +12 -0
- package/extensions/lsp/format.ts +349 -0
- package/extensions/lsp/index.ts +14 -0
- package/extensions/lsp/prompt.ts +39 -0
- package/extensions/lsp/server-manager.ts +303 -0
- package/extensions/lsp/servers.ts +229 -0
- package/extensions/lsp/tools.ts +530 -0
- package/extensions/lsp/trust.ts +39 -0
- package/extensions/safety.ts +370 -0
- package/extensions/session-title.ts +40 -0
- package/extensions/settings.ts +62 -0
- package/extensions/slash.ts +67 -0
- package/extensions/smart-at.ts +220 -0
- package/extensions/subdir-agents.ts +121 -0
- package/index.ts +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { list_supported_languages } from "./servers.js";
|
|
4
|
+
import {
|
|
5
|
+
filter_diagnostics,
|
|
6
|
+
find_symbol_matches,
|
|
7
|
+
format_diagnostics,
|
|
8
|
+
format_document_symbols,
|
|
9
|
+
format_hover,
|
|
10
|
+
format_locations,
|
|
11
|
+
format_symbol_matches,
|
|
12
|
+
format_tool_error,
|
|
13
|
+
SYMBOL_KIND_NAMES,
|
|
14
|
+
to_lsp_tool_error,
|
|
15
|
+
type SeverityFilter,
|
|
16
|
+
} from "./format.js";
|
|
17
|
+
import type { LspServerManager } from "./server-manager.js";
|
|
18
|
+
|
|
19
|
+
const SYMBOL_KIND_SCHEMA = Type.Union(
|
|
20
|
+
SYMBOL_KIND_NAMES.map((name) => Type.Literal(name))
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const DIAGNOSTICS_MANY_CONCURRENCY = 8;
|
|
24
|
+
|
|
25
|
+
function make_tool_result(
|
|
26
|
+
text: string,
|
|
27
|
+
details: Record<string, unknown> = {}
|
|
28
|
+
) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text" as const, text }],
|
|
31
|
+
details,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function make_tool_error(details: any) {
|
|
36
|
+
return make_tool_result(format_tool_error(details), {
|
|
37
|
+
ok: false,
|
|
38
|
+
error: details,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function map_with_concurrency<T, R>(
|
|
43
|
+
items: T[],
|
|
44
|
+
concurrency: number,
|
|
45
|
+
mapper: (item: T, index: number) => Promise<R>
|
|
46
|
+
): Promise<R[]> {
|
|
47
|
+
const results: R[] = [];
|
|
48
|
+
let next_index = 0;
|
|
49
|
+
const worker_count = Math.min(concurrency, items.length);
|
|
50
|
+
await Promise.all(
|
|
51
|
+
Array.from({ length: worker_count }, async () => {
|
|
52
|
+
while (true) {
|
|
53
|
+
const index = next_index;
|
|
54
|
+
next_index += 1;
|
|
55
|
+
if (index >= items.length) return;
|
|
56
|
+
results[index] = await mapper(items[index]!, index);
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
return results;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function with_file_state(
|
|
64
|
+
manager: LspServerManager,
|
|
65
|
+
file: string,
|
|
66
|
+
ctx: any,
|
|
67
|
+
run: (result: { abs: string; uri: string; state: any }) => Promise<string>
|
|
68
|
+
) {
|
|
69
|
+
const resolved = await manager.resolve_file_state(file, ctx);
|
|
70
|
+
if (!resolved.ok) {
|
|
71
|
+
return make_tool_error(resolved.error);
|
|
72
|
+
}
|
|
73
|
+
const { result } = resolved;
|
|
74
|
+
try {
|
|
75
|
+
const text = await run(result);
|
|
76
|
+
return make_tool_result(text, {
|
|
77
|
+
ok: true,
|
|
78
|
+
language: result.state.language,
|
|
79
|
+
command: result.state.command,
|
|
80
|
+
workspace_root: result.state.workspace_root,
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return make_tool_error(
|
|
84
|
+
to_lsp_tool_error(
|
|
85
|
+
result.abs,
|
|
86
|
+
result.state.language,
|
|
87
|
+
result.state.workspace_root,
|
|
88
|
+
result.state.command,
|
|
89
|
+
result.state.install_hint,
|
|
90
|
+
error
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function register_lsp_tools(pi: ExtensionAPI, manager: LspServerManager) {
|
|
97
|
+
pi.registerTool(
|
|
98
|
+
defineTool({
|
|
99
|
+
name: "lsp_diagnostics",
|
|
100
|
+
label: "LSP: diagnostics",
|
|
101
|
+
description:
|
|
102
|
+
"Get language server diagnostics for one or more files. Default filter: error. Supports optional severity filtering.",
|
|
103
|
+
promptSnippet: "Get language server diagnostics for one or more files",
|
|
104
|
+
promptGuidelines: [
|
|
105
|
+
"Use lsp_diagnostics to validate focused code changes after editing or writing before reporting completion.",
|
|
106
|
+
],
|
|
107
|
+
parameters: Type.Object({
|
|
108
|
+
files: Type.Array(Type.String(), {
|
|
109
|
+
minItems: 1,
|
|
110
|
+
maxItems: 100,
|
|
111
|
+
description: "Files to check. Single file or list (relative to cwd or absolute).",
|
|
112
|
+
}),
|
|
113
|
+
severity: Type.Optional(
|
|
114
|
+
Type.Array(
|
|
115
|
+
Type.Union([
|
|
116
|
+
Type.Literal("error"),
|
|
117
|
+
Type.Literal("warning"),
|
|
118
|
+
Type.Literal("info"),
|
|
119
|
+
Type.Literal("hint"),
|
|
120
|
+
]),
|
|
121
|
+
{
|
|
122
|
+
description:
|
|
123
|
+
"Filter to specific severity levels. Default: error. Values: error, warning, info, hint. Picking a level shows it and all more severe levels (e.g. warning → error + warning).",
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
),
|
|
127
|
+
wait_ms: Type.Optional(
|
|
128
|
+
Type.Number({
|
|
129
|
+
description:
|
|
130
|
+
"Max ms to wait for diagnostics after opening each file. Default 1500.",
|
|
131
|
+
})
|
|
132
|
+
),
|
|
133
|
+
}),
|
|
134
|
+
execute: async (_id, params, _signal, _on_update, ctx) => {
|
|
135
|
+
const wait_ms = params.wait_ms ?? 1500;
|
|
136
|
+
const severities: SeverityFilter[] = params.severity ?? ["error"];
|
|
137
|
+
|
|
138
|
+
const lines_with_stats = await map_with_concurrency(
|
|
139
|
+
params.files,
|
|
140
|
+
DIAGNOSTICS_MANY_CONCURRENCY,
|
|
141
|
+
async (file) => {
|
|
142
|
+
const resolved = await manager.resolve_file_state(file, ctx);
|
|
143
|
+
if (!resolved.ok) {
|
|
144
|
+
return {
|
|
145
|
+
line: format_tool_error(resolved.error),
|
|
146
|
+
diagnostics: 0,
|
|
147
|
+
error: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const diagnostics =
|
|
152
|
+
await resolved.result.state.client.wait_for_diagnostics(
|
|
153
|
+
resolved.result.uri,
|
|
154
|
+
wait_ms
|
|
155
|
+
);
|
|
156
|
+
const filtered = filter_diagnostics(diagnostics, severities);
|
|
157
|
+
let errors = 0, warnings = 0, infos = 0;
|
|
158
|
+
for (const d of filtered) {
|
|
159
|
+
if (d.severity === 1) errors++;
|
|
160
|
+
else if (d.severity === 2) warnings++;
|
|
161
|
+
else infos++;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
line: format_diagnostics(resolved.result.abs, diagnostics, severities),
|
|
165
|
+
diagnostics: filtered.length,
|
|
166
|
+
errors,
|
|
167
|
+
warnings,
|
|
168
|
+
error: false,
|
|
169
|
+
};
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return {
|
|
172
|
+
line: format_tool_error(
|
|
173
|
+
to_lsp_tool_error(
|
|
174
|
+
resolved.result.abs,
|
|
175
|
+
resolved.result.state.language,
|
|
176
|
+
resolved.result.state.workspace_root,
|
|
177
|
+
resolved.result.state.command,
|
|
178
|
+
resolved.result.state.install_hint,
|
|
179
|
+
error
|
|
180
|
+
)
|
|
181
|
+
),
|
|
182
|
+
diagnostics: 0,
|
|
183
|
+
error: true,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
let total_diag = 0;
|
|
190
|
+
let total_err = 0;
|
|
191
|
+
let total_warn = 0;
|
|
192
|
+
let clean_count = 0;
|
|
193
|
+
let fail_count = 0;
|
|
194
|
+
const lines: string[] = [];
|
|
195
|
+
for (const entry of lines_with_stats) {
|
|
196
|
+
lines.push(entry.line);
|
|
197
|
+
if (entry.error) {
|
|
198
|
+
fail_count += 1;
|
|
199
|
+
} else {
|
|
200
|
+
total_diag += entry.diagnostics;
|
|
201
|
+
total_err += (entry as any).errors ?? 0;
|
|
202
|
+
total_warn += (entry as any).warnings ?? 0;
|
|
203
|
+
if (entry.diagnostics === 0) clean_count += 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const summary = total_err > 0 || total_warn > 0
|
|
207
|
+
? `Checked ${params.files.length} file(s): ${total_err} error(s), ${total_warn} warning(s), ${total_diag - total_err - total_warn} info/hint(s), ${clean_count} clean, ${fail_count} failed to check`
|
|
208
|
+
: `Checked ${params.files.length} file(s): ${total_diag} diagnostic(s), ${clean_count} clean, ${fail_count} failed to check`;
|
|
209
|
+
return make_tool_result(
|
|
210
|
+
[summary, ...lines].join("\n\n"),
|
|
211
|
+
{
|
|
212
|
+
ok: fail_count === 0 && total_err === 0,
|
|
213
|
+
checked: params.files.length,
|
|
214
|
+
diagnostic_count: total_diag,
|
|
215
|
+
error_count: total_err,
|
|
216
|
+
warning_count: total_warn,
|
|
217
|
+
clean_count,
|
|
218
|
+
fail_count,
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
pi.registerTool(
|
|
226
|
+
defineTool({
|
|
227
|
+
name: "lsp_find_symbol",
|
|
228
|
+
label: "LSP: find symbol",
|
|
229
|
+
description:
|
|
230
|
+
"Find symbols in a file by name or detail text using document symbols. Supports exact matching, kind filters, and top-level-only mode.",
|
|
231
|
+
promptSnippet: "Find symbols in a file by name, kind, or match mode",
|
|
232
|
+
promptGuidelines: [
|
|
233
|
+
"Use lsp_find_symbol to locate named symbols in a file when symbol structure matters more than broad text search.",
|
|
234
|
+
],
|
|
235
|
+
parameters: Type.Object({
|
|
236
|
+
file: Type.String({
|
|
237
|
+
description: "Path to the file whose symbols should be searched.",
|
|
238
|
+
}),
|
|
239
|
+
query: Type.String({
|
|
240
|
+
description: "Substring to match against symbol names/details.",
|
|
241
|
+
}),
|
|
242
|
+
max_results: Type.Optional(
|
|
243
|
+
Type.Number({
|
|
244
|
+
description: "Max number of matches to return. Default 20.",
|
|
245
|
+
})
|
|
246
|
+
),
|
|
247
|
+
top_level_only: Type.Optional(
|
|
248
|
+
Type.Boolean({
|
|
249
|
+
description: "Only match top-level symbols. Default false.",
|
|
250
|
+
})
|
|
251
|
+
),
|
|
252
|
+
exact_match: Type.Optional(
|
|
253
|
+
Type.Boolean({
|
|
254
|
+
description:
|
|
255
|
+
"Match whole symbol names/details exactly instead of substring matching. Default false.",
|
|
256
|
+
})
|
|
257
|
+
),
|
|
258
|
+
kinds: Type.Optional(
|
|
259
|
+
Type.Array(SYMBOL_KIND_SCHEMA, {
|
|
260
|
+
minItems: 1,
|
|
261
|
+
maxItems: SYMBOL_KIND_NAMES.length,
|
|
262
|
+
description: "Restrict matches to these symbol kinds.",
|
|
263
|
+
})
|
|
264
|
+
),
|
|
265
|
+
}),
|
|
266
|
+
execute: async (
|
|
267
|
+
_id,
|
|
268
|
+
params,
|
|
269
|
+
_signal,
|
|
270
|
+
_on_update,
|
|
271
|
+
ctx
|
|
272
|
+
) =>
|
|
273
|
+
with_file_state(
|
|
274
|
+
manager,
|
|
275
|
+
params.file,
|
|
276
|
+
ctx,
|
|
277
|
+
async (result) => {
|
|
278
|
+
const symbols =
|
|
279
|
+
await result.state.client.document_symbols(result.uri);
|
|
280
|
+
return format_symbol_matches(
|
|
281
|
+
result.abs,
|
|
282
|
+
params.query,
|
|
283
|
+
find_symbol_matches(symbols, params.query, {
|
|
284
|
+
max_results: params.max_results ?? 20,
|
|
285
|
+
top_level_only: params.top_level_only ?? false,
|
|
286
|
+
exact_match: params.exact_match ?? false,
|
|
287
|
+
kinds: new Set(params.kinds ?? []),
|
|
288
|
+
language: result.state.language,
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
),
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
pi.registerTool(
|
|
297
|
+
defineTool({
|
|
298
|
+
name: "lsp_hover",
|
|
299
|
+
label: "LSP: hover",
|
|
300
|
+
description:
|
|
301
|
+
"Get hover info (types, docs) at a position in a file. Positions are zero-based.",
|
|
302
|
+
promptSnippet: "Get types and documentation at a symbol position",
|
|
303
|
+
promptGuidelines: [
|
|
304
|
+
"Use lsp_hover to inspect the type, signature, or documentation of the symbol at a specific zero-based position.",
|
|
305
|
+
],
|
|
306
|
+
parameters: Type.Object({
|
|
307
|
+
file: Type.String({
|
|
308
|
+
description: "Path to the file containing the symbol.",
|
|
309
|
+
}),
|
|
310
|
+
line: Type.Number({
|
|
311
|
+
description: "Zero-based line number of the symbol.",
|
|
312
|
+
}),
|
|
313
|
+
character: Type.Number({
|
|
314
|
+
description: "Zero-based character offset of the symbol.",
|
|
315
|
+
}),
|
|
316
|
+
}),
|
|
317
|
+
execute: async (
|
|
318
|
+
_id,
|
|
319
|
+
params,
|
|
320
|
+
_signal,
|
|
321
|
+
_on_update,
|
|
322
|
+
ctx
|
|
323
|
+
) =>
|
|
324
|
+
with_file_state(
|
|
325
|
+
manager,
|
|
326
|
+
params.file,
|
|
327
|
+
ctx,
|
|
328
|
+
async (result) => {
|
|
329
|
+
const hover = await result.state.client.hover(result.uri, {
|
|
330
|
+
line: params.line,
|
|
331
|
+
character: params.character,
|
|
332
|
+
});
|
|
333
|
+
return format_hover(hover);
|
|
334
|
+
}
|
|
335
|
+
),
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
pi.registerTool(
|
|
340
|
+
defineTool({
|
|
341
|
+
name: "lsp_definition",
|
|
342
|
+
label: "LSP: go to definition",
|
|
343
|
+
description:
|
|
344
|
+
"Find definition locations for the symbol at a position. Positions are zero-based.",
|
|
345
|
+
promptSnippet: "Find definition locations for a symbol at a position",
|
|
346
|
+
promptGuidelines: [
|
|
347
|
+
"Use lsp_definition to find the canonical definition location for the symbol at a specific zero-based position.",
|
|
348
|
+
],
|
|
349
|
+
parameters: Type.Object({
|
|
350
|
+
file: Type.String({
|
|
351
|
+
description: "Path to the file containing the symbol.",
|
|
352
|
+
}),
|
|
353
|
+
line: Type.Number({
|
|
354
|
+
description: "Zero-based line number of the symbol.",
|
|
355
|
+
}),
|
|
356
|
+
character: Type.Number({
|
|
357
|
+
description: "Zero-based character offset of the symbol.",
|
|
358
|
+
}),
|
|
359
|
+
}),
|
|
360
|
+
execute: async (
|
|
361
|
+
_id,
|
|
362
|
+
params,
|
|
363
|
+
_signal,
|
|
364
|
+
_on_update,
|
|
365
|
+
ctx
|
|
366
|
+
) =>
|
|
367
|
+
with_file_state(
|
|
368
|
+
manager,
|
|
369
|
+
params.file,
|
|
370
|
+
ctx,
|
|
371
|
+
async (result) => {
|
|
372
|
+
const locations = await result.state.client.definition(
|
|
373
|
+
result.uri,
|
|
374
|
+
{
|
|
375
|
+
line: params.line,
|
|
376
|
+
character: params.character,
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
return format_locations(locations, "No definition found.");
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
})
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
pi.registerTool(
|
|
386
|
+
defineTool({
|
|
387
|
+
name: "lsp_references",
|
|
388
|
+
label: "LSP: find references",
|
|
389
|
+
description:
|
|
390
|
+
"Find references to the symbol at a position. Positions are zero-based.",
|
|
391
|
+
promptSnippet: "Find references to a symbol at a position",
|
|
392
|
+
promptGuidelines: [
|
|
393
|
+
"Use lsp_references to find usages of a symbol more precisely than text search, optionally including the declaration site.",
|
|
394
|
+
],
|
|
395
|
+
parameters: Type.Object({
|
|
396
|
+
file: Type.String({
|
|
397
|
+
description: "Path to the file containing the symbol.",
|
|
398
|
+
}),
|
|
399
|
+
line: Type.Number({
|
|
400
|
+
description: "Zero-based line number of the symbol.",
|
|
401
|
+
}),
|
|
402
|
+
character: Type.Number({
|
|
403
|
+
description: "Zero-based character offset of the symbol.",
|
|
404
|
+
}),
|
|
405
|
+
include_declaration: Type.Optional(
|
|
406
|
+
Type.Boolean({
|
|
407
|
+
description:
|
|
408
|
+
"Whether to include the symbol declaration in reference results. Default true.",
|
|
409
|
+
})
|
|
410
|
+
),
|
|
411
|
+
}),
|
|
412
|
+
execute: async (
|
|
413
|
+
_id,
|
|
414
|
+
params,
|
|
415
|
+
_signal,
|
|
416
|
+
_on_update,
|
|
417
|
+
ctx
|
|
418
|
+
) =>
|
|
419
|
+
with_file_state(
|
|
420
|
+
manager,
|
|
421
|
+
params.file,
|
|
422
|
+
ctx,
|
|
423
|
+
async (result) => {
|
|
424
|
+
const locations = await result.state.client.references(
|
|
425
|
+
result.uri,
|
|
426
|
+
{ line: params.line, character: params.character },
|
|
427
|
+
params.include_declaration ?? true
|
|
428
|
+
);
|
|
429
|
+
return format_locations(locations, "No references found.");
|
|
430
|
+
}
|
|
431
|
+
),
|
|
432
|
+
})
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
pi.registerTool(
|
|
436
|
+
defineTool({
|
|
437
|
+
name: "lsp_document_symbols",
|
|
438
|
+
label: "LSP: document symbols",
|
|
439
|
+
description:
|
|
440
|
+
"List symbols in a file (functions, classes, variables) using the language server.",
|
|
441
|
+
promptSnippet: "List functions, classes, and variables in a file",
|
|
442
|
+
promptGuidelines: [
|
|
443
|
+
"Use lsp_document_symbols to inspect a file's structural outline before making focused edits or searching for symbols.",
|
|
444
|
+
],
|
|
445
|
+
parameters: Type.Object({
|
|
446
|
+
file: Type.String({
|
|
447
|
+
description: "Path to the file to inspect.",
|
|
448
|
+
}),
|
|
449
|
+
}),
|
|
450
|
+
execute: async (
|
|
451
|
+
_id,
|
|
452
|
+
params,
|
|
453
|
+
_signal,
|
|
454
|
+
_on_update,
|
|
455
|
+
ctx
|
|
456
|
+
) =>
|
|
457
|
+
with_file_state(
|
|
458
|
+
manager,
|
|
459
|
+
params.file,
|
|
460
|
+
ctx,
|
|
461
|
+
async (result) => {
|
|
462
|
+
const symbols =
|
|
463
|
+
await result.state.client.document_symbols(result.uri);
|
|
464
|
+
return format_document_symbols(result.abs, symbols);
|
|
465
|
+
}
|
|
466
|
+
),
|
|
467
|
+
})
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
pi.registerTool(
|
|
471
|
+
defineTool({
|
|
472
|
+
name: "lsp_rename",
|
|
473
|
+
label: "LSP: rename symbol",
|
|
474
|
+
description:
|
|
475
|
+
"Rename a symbol at a position. Returns all locations that need to be updated with the new name. Use the edit tool to apply the changes.",
|
|
476
|
+
promptSnippet: "Compute symbol rename updates across affected files",
|
|
477
|
+
promptGuidelines: [
|
|
478
|
+
"Use lsp_rename to compute coordinated symbol rename updates across affected files instead of manual search-and-replace.",
|
|
479
|
+
],
|
|
480
|
+
parameters: Type.Object({
|
|
481
|
+
file: Type.String({
|
|
482
|
+
description: "Path to the file containing the symbol.",
|
|
483
|
+
}),
|
|
484
|
+
line: Type.Number({
|
|
485
|
+
description: "Zero-based line number of the symbol.",
|
|
486
|
+
}),
|
|
487
|
+
character: Type.Number({
|
|
488
|
+
description: "Zero-based character offset of the symbol.",
|
|
489
|
+
}),
|
|
490
|
+
newName: Type.String({
|
|
491
|
+
description: "New name for the symbol.",
|
|
492
|
+
}),
|
|
493
|
+
}),
|
|
494
|
+
execute: async (
|
|
495
|
+
_id,
|
|
496
|
+
params,
|
|
497
|
+
_signal,
|
|
498
|
+
_on_update,
|
|
499
|
+
ctx
|
|
500
|
+
) =>
|
|
501
|
+
with_file_state(
|
|
502
|
+
manager,
|
|
503
|
+
params.file,
|
|
504
|
+
ctx,
|
|
505
|
+
async (result) => {
|
|
506
|
+
const edits = await result.state.client.rename(
|
|
507
|
+
result.uri,
|
|
508
|
+
{ line: params.line, character: params.character },
|
|
509
|
+
params.newName
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
// Format rename output as a clear list of files to edit
|
|
513
|
+
const locations = Object.keys(edits);
|
|
514
|
+
if (locations.length === 0) {
|
|
515
|
+
return `No rename locations found for "${params.newName}"`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let output = `Rename to "${params.newName}": ${locations.length} file(s) need update\n\n`;
|
|
519
|
+
for (const path of locations) {
|
|
520
|
+
const info = edits[path]!;
|
|
521
|
+
output += `${path}: change to "${info.newText}"\n`;
|
|
522
|
+
}
|
|
523
|
+
output += "\nUse the edit tool to apply these changes.";
|
|
524
|
+
|
|
525
|
+
return output;
|
|
526
|
+
}
|
|
527
|
+
),
|
|
528
|
+
})
|
|
529
|
+
);
|
|
530
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
is_project_subject_trusted,
|
|
4
|
+
read_project_trust_store,
|
|
5
|
+
trust_project_subject,
|
|
6
|
+
} from "@spences10/pi-project-trust";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
const LSP_PROJECT_BINARY_ENV = "MY_PI_LSP_PROJECT_BINARY";
|
|
10
|
+
|
|
11
|
+
export function default_lsp_trust_store_path(): string {
|
|
12
|
+
return join(getAgentDir(), "trusted-lsp-binaries.json");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function create_lsp_binary_trust_subject(binary_path: string) {
|
|
16
|
+
return {
|
|
17
|
+
kind: "lsp-binary" as const,
|
|
18
|
+
id: binary_path,
|
|
19
|
+
store_key: binary_path,
|
|
20
|
+
env_key: LSP_PROJECT_BINARY_ENV,
|
|
21
|
+
prompt_title: "Trust project-local LSP binary?",
|
|
22
|
+
fallback: "global" as const,
|
|
23
|
+
choices: {
|
|
24
|
+
allow_once: "Allow once for this session",
|
|
25
|
+
trust: "Trust this binary path",
|
|
26
|
+
skip: "Use global PATH binary instead",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function is_lsp_binary_trusted(
|
|
32
|
+
binary_path: string,
|
|
33
|
+
trust_store_path: string = default_lsp_trust_store_path()
|
|
34
|
+
): boolean {
|
|
35
|
+
const subject = create_lsp_binary_trust_subject(binary_path);
|
|
36
|
+
if (is_project_subject_trusted(subject, trust_store_path)) return true;
|
|
37
|
+
const entry = read_project_trust_store(trust_store_path)[binary_path];
|
|
38
|
+
return entry?.binary_path === binary_path;
|
|
39
|
+
}
|