@bacnh85/pi-serena 0.4.0 → 0.4.2
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 +6 -0
- package/index.ts +100 -2
- package/package.json +1 -1
- package/worker.ts +102 -0
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ After install or update, restart Pi or run `/reload` in an existing Pi session.
|
|
|
37
37
|
- `serena_get_symbols_overview`
|
|
38
38
|
- `serena_find_symbol`
|
|
39
39
|
- `serena_find_referencing_symbols`
|
|
40
|
+
- `serena_find_declaration`
|
|
41
|
+
- `serena_find_implementations`
|
|
40
42
|
- `serena_replace_symbol_body`
|
|
41
43
|
- `serena_insert_before_symbol`
|
|
42
44
|
- `serena_insert_after_symbol`
|
|
@@ -46,10 +48,14 @@ After install or update, restart Pi or run `/reload` in an existing Pi session.
|
|
|
46
48
|
- `serena_replace_content`
|
|
47
49
|
- `serena_restart_language_server`
|
|
48
50
|
- `serena_get_current_config`
|
|
51
|
+
- `serena_get_diagnostics_for_file`
|
|
52
|
+
- `serena_check_onboarding_performed`
|
|
49
53
|
- `serena_onboarding`
|
|
50
54
|
- `serena_list_memories`
|
|
51
55
|
- `serena_read_memory`
|
|
52
56
|
- `serena_write_memory`
|
|
57
|
+
- `serena_edit_memory`
|
|
58
|
+
- `serena_rename_memory`
|
|
53
59
|
- `serena_delete_memory`
|
|
54
60
|
|
|
55
61
|
All tool outputs are truncated to 50KB / 2000 lines to match Pi-friendly output limits. Most tools accept optional `timeout_ms`.
|
package/index.ts
CHANGED
|
@@ -100,6 +100,32 @@ const writeMemorySchema = Type.Object({
|
|
|
100
100
|
content: Type.String({ description: "Memory content to write." }),
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
const editMemorySchema = Type.Object({
|
|
104
|
+
...controlSchema,
|
|
105
|
+
memory_name: Type.String({ description: "The name of the memory to edit." }),
|
|
106
|
+
needle: Type.String({ description: "The string or regex pattern to search for." }),
|
|
107
|
+
repl: Type.String({ description: "The replacement string (verbatim)." }),
|
|
108
|
+
mode: Type.Union([Type.Literal("literal"), Type.Literal("regex")], { description: "Either 'literal' or 'regex', specifying how needle is interpreted." }),
|
|
109
|
+
allow_multiple_occurrences: Type.Optional(Type.Boolean({ description: "Whether to allow replacing multiple occurrences. If false and multiple matches are found, returns an error." })),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const renameMemorySchema = Type.Object({
|
|
113
|
+
...controlSchema,
|
|
114
|
+
old_name: Type.String({ description: "Current name of the memory to rename." }),
|
|
115
|
+
new_name: Type.String({ description: "New name for the memory. Use '/' to organize into topics." }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const symbolRefSchema = Type.Object({
|
|
119
|
+
...controlSchema,
|
|
120
|
+
name_path: Type.String({ description: "Exact symbol name path to look up." }),
|
|
121
|
+
relative_path: Type.String({ description: "File containing the symbol, relative to project root." }),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const diagnosticsSchema = Type.Object({
|
|
125
|
+
...controlSchema,
|
|
126
|
+
relative_path: Type.String({ description: "File path relative to the Serena project root to get diagnostics for." }),
|
|
127
|
+
});
|
|
128
|
+
|
|
103
129
|
const searchPatternSchema = Type.Object({
|
|
104
130
|
...controlSchema,
|
|
105
131
|
pattern: Type.String({ description: "Pattern to search for." }),
|
|
@@ -434,6 +460,18 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
434
460
|
},
|
|
435
461
|
});
|
|
436
462
|
|
|
463
|
+
pi.registerTool({
|
|
464
|
+
name: "serena_check_onboarding_performed",
|
|
465
|
+
label: "Serena Check Onboarding",
|
|
466
|
+
description: "Check whether Serena onboarding memories exist for this project.",
|
|
467
|
+
promptSnippet: "Check whether project onboarding was already performed",
|
|
468
|
+
promptGuidelines: ["Use serena_check_onboarding_performed before relying on Serena project memories."],
|
|
469
|
+
parameters: emptyToolSchema,
|
|
470
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
471
|
+
return callSerena(ctx, "check_onboarding_performed", params);
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
|
|
437
475
|
pi.registerTool({
|
|
438
476
|
name: "serena_onboarding",
|
|
439
477
|
label: "Serena Onboarding",
|
|
@@ -494,6 +532,66 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
494
532
|
},
|
|
495
533
|
});
|
|
496
534
|
|
|
535
|
+
pi.registerTool({
|
|
536
|
+
name: "serena_edit_memory",
|
|
537
|
+
label: "Serena Edit Memory",
|
|
538
|
+
description: "Replace content matching a pattern in a Serena project memory using literal or regex mode.",
|
|
539
|
+
promptSnippet: "Edit a Serena project memory by replacing content matching a pattern",
|
|
540
|
+
promptGuidelines: ["Use serena_edit_memory for targeted edits to existing memories without rewriting the whole file."],
|
|
541
|
+
parameters: editMemorySchema,
|
|
542
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
543
|
+
return callSerena(ctx, "edit_memory", params, lockPathForProject(params));
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
pi.registerTool({
|
|
548
|
+
name: "serena_rename_memory",
|
|
549
|
+
label: "Serena Rename Memory",
|
|
550
|
+
description: "Rename or move a Serena project memory. Supports moving between project and global scope (e.g., renaming 'global/foo' to 'bar').",
|
|
551
|
+
promptSnippet: "Rename or move a Serena project memory",
|
|
552
|
+
promptGuidelines: ["Use serena_rename_memory to rename or reorganize memories, using '/' in the name to organize into topics."],
|
|
553
|
+
parameters: renameMemorySchema,
|
|
554
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
555
|
+
return callSerena(ctx, "rename_memory", params, lockPathForProject(params));
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
pi.registerTool({
|
|
560
|
+
name: "serena_find_declaration",
|
|
561
|
+
label: "Serena Find Declaration",
|
|
562
|
+
description: "Find the declaration/definition of a symbol using the language server backend.",
|
|
563
|
+
promptSnippet: "Find the declaration/definition of a symbol",
|
|
564
|
+
promptGuidelines: ["Use serena_find_declaration to navigate to the definition of a known symbol, e.g. to find where a function is defined."],
|
|
565
|
+
parameters: symbolRefSchema,
|
|
566
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
567
|
+
return callWorkerAction(ctx, "find_declaration", params);
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
pi.registerTool({
|
|
572
|
+
name: "serena_find_implementations",
|
|
573
|
+
label: "Serena Find Implementations",
|
|
574
|
+
description: "Find implementations of a symbol using the language server backend.",
|
|
575
|
+
promptSnippet: "Find symbols that implement the given symbol",
|
|
576
|
+
promptGuidelines: ["Use serena_find_implementations to locate implementations of interfaces, abstract methods, or base classes."],
|
|
577
|
+
parameters: symbolRefSchema,
|
|
578
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
579
|
+
return callWorkerAction(ctx, "find_implementations", params);
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
pi.registerTool({
|
|
584
|
+
name: "serena_get_diagnostics_for_file",
|
|
585
|
+
label: "Serena File Diagnostics",
|
|
586
|
+
description: "Get LSP diagnostics (errors, warnings, hints) for a file using the language server backend.",
|
|
587
|
+
promptSnippet: "Get diagnostics for a file from the language server",
|
|
588
|
+
promptGuidelines: ["Use serena_get_diagnostics_for_file to inspect compiler/IDE diagnostics for a specific file."],
|
|
589
|
+
parameters: diagnosticsSchema,
|
|
590
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
591
|
+
return callWorkerAction(ctx, "get_diagnostics_for_file", params);
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
|
|
497
595
|
pi.registerCommand("serena-status", {
|
|
498
596
|
description: "Show Serena worker status",
|
|
499
597
|
handler: async (args, ctx) => {
|
|
@@ -530,7 +628,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
530
628
|
return {
|
|
531
629
|
systemPrompt:
|
|
532
630
|
event.systemPrompt +
|
|
533
|
-
"\n\nSerena semantic tools are available for code-symbol work. For finding symbols, references, declarations, implementations, and refactoring targets, prefer serena_find_symbol / serena_get_symbols_overview / serena_find_referencing_symbols over grep/read. Use grep/read for exact text searches, docs, configs, and non-code files.",
|
|
631
|
+
"\n\nSerena semantic tools are available for code-symbol work. For finding symbols, references, declarations, implementations, and refactoring targets, prefer serena_find_symbol / serena_get_symbols_overview / serena_find_referencing_symbols / serena_find_declaration / serena_find_implementations over grep/read. Use grep/read for exact text searches, docs, configs, and non-code files.",
|
|
534
632
|
};
|
|
535
633
|
});
|
|
536
634
|
|
|
@@ -557,7 +655,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
557
655
|
{
|
|
558
656
|
customType: "serena-reminder",
|
|
559
657
|
content:
|
|
560
|
-
"Reminder: you are reading code files instead of using Serena semantic tools. For symbols, references, declarations, implementations, and refactoring, use serena_find_symbol / serena_get_symbols_overview / serena_find_referencing_symbols instead of more grep/read calls. Keep using read/grep for docs, configs, non-code files, and exact text searches.",
|
|
658
|
+
"Reminder: you are reading code files instead of using Serena semantic tools. For symbols, references, declarations, implementations, and refactoring, use serena_find_symbol / serena_get_symbols_overview / serena_find_referencing_symbols / serena_find_declaration / serena_find_implementations instead of more grep/read calls. Keep using read/grep for docs, configs, non-code files, and exact text searches.",
|
|
561
659
|
display: true,
|
|
562
660
|
},
|
|
563
661
|
{ deliverAs: "steer", triggerTurn: true },
|
package/package.json
CHANGED
package/worker.ts
CHANGED
|
@@ -187,9 +187,111 @@ def handle(req: dict[str, Any]) -> dict[str, Any]:
|
|
|
187
187
|
return {"id": req_id, "ok": False, "tool": tool_name, "errorType": classify_error(result), "error": result, "result": result}
|
|
188
188
|
return {"id": req_id, "ok": True, "tool": tool_name, "result": result}
|
|
189
189
|
|
|
190
|
+
if action in ("find_declaration", "find_implementations"):
|
|
191
|
+
return _handle_find_symbol_action(req_id, action, req)
|
|
192
|
+
|
|
193
|
+
if action == "get_diagnostics_for_file":
|
|
194
|
+
return _handle_diagnostics(req_id, req)
|
|
195
|
+
|
|
190
196
|
raise ValueError(f"Unknown action: {action}")
|
|
191
197
|
|
|
192
198
|
|
|
199
|
+
def _get_symbol_retriever(agent: SerenaAgent):
|
|
200
|
+
from serena.symbol import LanguageServerSymbolRetriever
|
|
201
|
+
project = agent.get_active_project_or_raise()
|
|
202
|
+
return project, LanguageServerSymbolRetriever(project)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _find_symbol_or_raise(retriever, name_path: str, relative_path: str):
|
|
206
|
+
return retriever.find_unique(
|
|
207
|
+
name_path, substring_matching=False, within_relative_path=relative_path
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _format_locations(locations) -> str:
|
|
212
|
+
import json
|
|
213
|
+
result = []
|
|
214
|
+
for loc in locations:
|
|
215
|
+
uri = loc.get("uri", "")
|
|
216
|
+
range_data = loc.get("range", {})
|
|
217
|
+
start = range_data.get("start", {})
|
|
218
|
+
end = range_data.get("end", {})
|
|
219
|
+
result.append({
|
|
220
|
+
"uri": uri,
|
|
221
|
+
"range": {
|
|
222
|
+
"start": {"line": start.get("line"), "character": start.get("character")},
|
|
223
|
+
"end": {"line": end.get("line"), "character": end.get("character")},
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
return json.dumps(result, indent=2)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _handle_find_symbol_action(req_id, action: str, req: dict[str, Any]) -> dict[str, Any]:
|
|
230
|
+
project = str(req.get("project") or os.getcwd())
|
|
231
|
+
context = str(req.get("context") or "ide")
|
|
232
|
+
params = req.get("params") or {}
|
|
233
|
+
name_path = params.get("name_path")
|
|
234
|
+
relative_path = params.get("relative_path")
|
|
235
|
+
if not isinstance(name_path, str) or not name_path:
|
|
236
|
+
return {"id": req_id, "ok": False, "tool": action, "error": f"{action} requires string parameter 'name_path'"}
|
|
237
|
+
if not isinstance(relative_path, str) or not relative_path:
|
|
238
|
+
return {"id": req_id, "ok": False, "tool": action, "error": f"{action} requires string parameter 'relative_path'"}
|
|
239
|
+
|
|
240
|
+
agent = get_agent(project, context)
|
|
241
|
+
project_obj, retriever = _get_symbol_retriever(agent)
|
|
242
|
+
symbol = _find_symbol_or_raise(retriever, name_path, relative_path)
|
|
243
|
+
|
|
244
|
+
sym_rel_path = symbol.relative_path
|
|
245
|
+
sym_line = symbol.line
|
|
246
|
+
sym_col = symbol.column
|
|
247
|
+
if sym_rel_path is None or sym_line is None or sym_col is None:
|
|
248
|
+
return {"id": req_id, "ok": False, "tool": action, "error": f"Symbol {name_path} has no position info"}
|
|
249
|
+
|
|
250
|
+
ls_manager = project_obj.get_language_server_manager_or_raise()
|
|
251
|
+
lang_server = ls_manager.get_language_server(sym_rel_path)
|
|
252
|
+
|
|
253
|
+
if action == "find_declaration":
|
|
254
|
+
locations = lang_server.request_definition(sym_rel_path, sym_line, sym_col)
|
|
255
|
+
else:
|
|
256
|
+
locations = lang_server.request_implementation(sym_rel_path, sym_line, sym_col)
|
|
257
|
+
|
|
258
|
+
if not locations:
|
|
259
|
+
return {"id": req_id, "ok": True, "tool": action, "result": "No declarations found."}
|
|
260
|
+
|
|
261
|
+
result = _format_locations(locations)
|
|
262
|
+
return {"id": req_id, "ok": True, "tool": action, "result": result}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _handle_diagnostics(req_id, req: dict[str, Any]) -> dict[str, Any]:
|
|
266
|
+
project = str(req.get("project") or os.getcwd())
|
|
267
|
+
context = str(req.get("context") or "ide")
|
|
268
|
+
params = req.get("params") or {}
|
|
269
|
+
relative_path = params.get("relative_path")
|
|
270
|
+
if not isinstance(relative_path, str) or not relative_path:
|
|
271
|
+
return {"id": req_id, "ok": False, "tool": "get_diagnostics_for_file", "error": "get_diagnostics_for_file requires string parameter 'relative_path'"}
|
|
272
|
+
|
|
273
|
+
import json as _json
|
|
274
|
+
import pathlib
|
|
275
|
+
from pathlib import PurePath
|
|
276
|
+
|
|
277
|
+
agent = get_agent(project, context)
|
|
278
|
+
project_obj = agent.get_active_project_or_raise()
|
|
279
|
+
ls_manager = project_obj.get_language_server_manager_or_raise()
|
|
280
|
+
lang_server = ls_manager.get_language_server(relative_path)
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
uri = pathlib.Path(str(PurePath(lang_server.repository_root_path, relative_path))).as_uri()
|
|
284
|
+
result = lang_server.server.send.text_document_diagnostic({
|
|
285
|
+
"textDocument": {"uri": uri},
|
|
286
|
+
})
|
|
287
|
+
return {"id": req_id, "ok": True, "tool": "get_diagnostics_for_file", "result": _json.dumps(result, indent=2, default=str)}
|
|
288
|
+
except AttributeError:
|
|
289
|
+
return {"id": req_id, "ok": True, "tool": "get_diagnostics_for_file", "result": _json.dumps({"note": "Language server does not support textDocument/diagnostic"})}
|
|
290
|
+
except Exception as exc:
|
|
291
|
+
return {"id": req_id, "ok": True, "tool": "get_diagnostics_for_file", "result": _json.dumps({"note": "Diagnostics request completed with no issues or language server does not support textDocument/diagnostic", "detail": str(exc)})}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
193
295
|
def main() -> int:
|
|
194
296
|
try:
|
|
195
297
|
for line in sys.stdin:
|