@bacnh85/pi-serena 0.1.3 → 0.2.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/README.md +26 -0
- package/index.ts +67 -12
- package/package.json +1 -1
- package/worker.ts +16 -0
package/README.md
CHANGED
|
@@ -69,6 +69,32 @@ Use the Pi-facing `pattern` field with `serena_search_for_pattern`:
|
|
|
69
69
|
|
|
70
70
|
The extension maps `pattern` to Serena's backend `substring_pattern` parameter internally, so users do not need to call the Serena implementation detail directly.
|
|
71
71
|
|
|
72
|
+
### Content replacement
|
|
73
|
+
|
|
74
|
+
Use Serena's current `replace_content` API through Pi-facing fields:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"relative_path": "src/example.py",
|
|
79
|
+
"needle": "old text",
|
|
80
|
+
"repl": "new text",
|
|
81
|
+
"mode": "literal"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For regex replacement, set `mode` to `regex` and provide a Python regular expression in `needle`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"relative_path": "src/example.py",
|
|
90
|
+
"needle": "beginning.*?end",
|
|
91
|
+
"repl": "replacement",
|
|
92
|
+
"mode": "regex"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The Pi bridge also implements `serena_get_current_config` and `serena_restart_language_server` directly, so they work even when Serena's same-named native tools are inactive in single-project or default contexts.
|
|
97
|
+
|
|
72
98
|
## Commands
|
|
73
99
|
|
|
74
100
|
- `/serena-status [project]`
|
package/index.ts
CHANGED
|
@@ -54,7 +54,8 @@ const referencingSchema = Type.Object({
|
|
|
54
54
|
...controlSchema,
|
|
55
55
|
name_path: Type.String({ description: "Exact symbol name path from find_symbol." }),
|
|
56
56
|
relative_path: Type.String({ description: "File containing the symbol, relative to project root." }),
|
|
57
|
-
|
|
57
|
+
include_kinds: Type.Optional(Type.Array(Type.Number({ description: "LSP symbol kind integers to include." }))),
|
|
58
|
+
exclude_kinds: Type.Optional(Type.Array(Type.Number({ description: "LSP symbol kind integers to exclude." }))),
|
|
58
59
|
max_answer_chars: MAX_CHARS_PARAM,
|
|
59
60
|
});
|
|
60
61
|
|
|
@@ -114,10 +115,13 @@ const searchPatternSchema = Type.Object({
|
|
|
114
115
|
const replaceContentSchema = Type.Object({
|
|
115
116
|
...controlSchema,
|
|
116
117
|
relative_path: Type.String({ description: "File path relative to the Serena project root." }),
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
needle: Type.Optional(Type.String({ description: "Exact text or regex pattern to replace." })),
|
|
119
|
+
repl: Type.Optional(Type.String({ description: "Replacement text." })),
|
|
120
|
+
mode: Type.Optional(Type.Union([Type.Literal("literal"), Type.Literal("regex")], { description: "Interpret needle as exact text or as a Python regex." })),
|
|
121
|
+
old_string: Type.Optional(Type.String({ description: "Deprecated alias for literal needle." })),
|
|
122
|
+
new_string: Type.Optional(Type.String({ description: "Deprecated alias for repl with old_string." })),
|
|
123
|
+
content: Type.Optional(Type.String({ description: "Deprecated alias for repl with regex." })),
|
|
124
|
+
regex: Type.Optional(Type.String({ description: "Deprecated alias for regex needle." })),
|
|
121
125
|
allow_multiple_occurrences: Type.Optional(Type.Boolean({ description: "Allow replacing multiple matches when supported." })),
|
|
122
126
|
});
|
|
123
127
|
|
|
@@ -148,6 +152,41 @@ export function normalizeSearchPatternParams(params: Record<string, unknown>): R
|
|
|
148
152
|
return normalized;
|
|
149
153
|
}
|
|
150
154
|
|
|
155
|
+
export function normalizeFindReferencesParams(params: Record<string, unknown>): Record<string, unknown> {
|
|
156
|
+
const normalized = { ...params };
|
|
157
|
+
delete normalized.include_body;
|
|
158
|
+
return normalized;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function normalizeReplaceContentParams(params: Record<string, unknown>): Record<string, unknown> {
|
|
162
|
+
const normalized = { ...params };
|
|
163
|
+
|
|
164
|
+
if (normalized.needle === undefined) {
|
|
165
|
+
normalized.needle = normalized.old_string ?? normalized.regex;
|
|
166
|
+
}
|
|
167
|
+
if (normalized.repl === undefined) {
|
|
168
|
+
normalized.repl = normalized.new_string ?? normalized.content;
|
|
169
|
+
}
|
|
170
|
+
if (normalized.mode === undefined) {
|
|
171
|
+
if (normalized.regex !== undefined) normalized.mode = "regex";
|
|
172
|
+
else if (normalized.old_string !== undefined) normalized.mode = "literal";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
delete normalized.old_string;
|
|
176
|
+
delete normalized.new_string;
|
|
177
|
+
delete normalized.content;
|
|
178
|
+
delete normalized.regex;
|
|
179
|
+
|
|
180
|
+
return normalized;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function validateReplaceContentParams(params: Record<string, unknown>): string | undefined {
|
|
184
|
+
if (typeof params.needle !== "string") return "serena_replace_content requires string parameter 'needle' (or deprecated alias 'old_string'/'regex').";
|
|
185
|
+
if (typeof params.repl !== "string") return "serena_replace_content requires string parameter 'repl' (or deprecated alias 'new_string'/'content').";
|
|
186
|
+
if (params.mode !== "literal" && params.mode !== "regex") return "serena_replace_content requires mode to be 'literal' or 'regex'.";
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
151
190
|
function truncateText(text: string): string {
|
|
152
191
|
const lines = text.split("\n");
|
|
153
192
|
let output = lines.slice(0, OUTPUT_MAX_LINES).join("\n");
|
|
@@ -229,10 +268,10 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
229
268
|
return worker;
|
|
230
269
|
};
|
|
231
270
|
|
|
232
|
-
const
|
|
271
|
+
const callWorkerAction = async (ctx: any, action: string, rawParams: Record<string, unknown>, extraPayload: Record<string, unknown> = {}, lockPath?: string) => {
|
|
233
272
|
const { project, context, timeoutMs, params } = stripControlParams(rawParams);
|
|
234
273
|
const run = async () => {
|
|
235
|
-
const response = await getWorker(ctx).request({ action
|
|
274
|
+
const response = await getWorker(ctx).request({ action, project, context, params, ...extraPayload }, timeoutMs);
|
|
236
275
|
return {
|
|
237
276
|
content: [{ type: "text" as const, text: resultText(response) }],
|
|
238
277
|
details: response,
|
|
@@ -241,6 +280,10 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
241
280
|
return lockPath ? withFileMutationQueue(lockPath, run) : run();
|
|
242
281
|
};
|
|
243
282
|
|
|
283
|
+
const callSerena = async (ctx: any, tool: string, rawParams: Record<string, unknown>, lockPath?: string) => {
|
|
284
|
+
return callWorkerAction(ctx, "call", rawParams, { tool }, lockPath);
|
|
285
|
+
};
|
|
286
|
+
|
|
244
287
|
const lockPathForRelativeFile = (rawParams: Record<string, unknown>): string | undefined => {
|
|
245
288
|
const project = normalizeProject(rawParams.project);
|
|
246
289
|
return typeof rawParams.relative_path === "string" && rawParams.relative_path.trim()
|
|
@@ -314,7 +357,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
314
357
|
promptGuidelines: ["Use serena_find_referencing_symbols before changing public behavior or renaming a symbol."],
|
|
315
358
|
parameters: referencingSchema,
|
|
316
359
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
317
|
-
return callSerena(ctx, "find_referencing_symbols", params);
|
|
360
|
+
return callSerena(ctx, "find_referencing_symbols", normalizeFindReferencesParams(params));
|
|
318
361
|
},
|
|
319
362
|
});
|
|
320
363
|
|
|
@@ -398,7 +441,10 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
398
441
|
promptGuidelines: ["Prefer symbol-aware Serena edit tools for whole symbols; use serena_replace_content for non-symbol scoped replacements supported by Serena."],
|
|
399
442
|
parameters: replaceContentSchema,
|
|
400
443
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
401
|
-
|
|
444
|
+
const normalized = normalizeReplaceContentParams(params);
|
|
445
|
+
const error = validateReplaceContentParams(normalized);
|
|
446
|
+
if (error) return { content: [{ type: "text" as const, text: `Error: ${error}` }], details: { ok: false, error } };
|
|
447
|
+
return callSerena(ctx, "replace_content", normalized, lockPathForRelativeFile(params));
|
|
402
448
|
},
|
|
403
449
|
});
|
|
404
450
|
|
|
@@ -410,7 +456,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
410
456
|
promptGuidelines: ["Use serena_restart_language_server when Serena symbol retrieval appears stale or language-server diagnostics are stuck."],
|
|
411
457
|
parameters: emptyToolSchema,
|
|
412
458
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
413
|
-
return
|
|
459
|
+
return callWorkerAction(ctx, "restart_language_server", params);
|
|
414
460
|
},
|
|
415
461
|
});
|
|
416
462
|
|
|
@@ -422,7 +468,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
422
468
|
promptGuidelines: ["Use serena_get_current_config when project activation, contexts, modes, or tool availability are unclear."],
|
|
423
469
|
parameters: emptyToolSchema,
|
|
424
470
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
425
|
-
return
|
|
471
|
+
return callWorkerAction(ctx, "config", params);
|
|
426
472
|
},
|
|
427
473
|
});
|
|
428
474
|
|
|
@@ -531,7 +577,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
531
577
|
|
|
532
578
|
pi.on("before_agent_start", async (event) => {
|
|
533
579
|
const prompt = event.prompt.toLowerCase();
|
|
534
|
-
if (/\b(find references|rename|refactor|symbol|declaration|implementation|diagnostic|large repo|semantic|class|method|function)\b/.test(prompt)) {
|
|
580
|
+
if (/\b(find references|rename|refactor|symbol|declaration|implementation|diagnostic|large repo|semantic|class|method|function|investigate|understand|explore|search for|look at|find where|navigate|trace|examine|where is|what does)\b/.test(prompt)) {
|
|
535
581
|
return {
|
|
536
582
|
systemPrompt:
|
|
537
583
|
event.systemPrompt +
|
|
@@ -540,6 +586,15 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
540
586
|
}
|
|
541
587
|
});
|
|
542
588
|
|
|
589
|
+
// Unconditional Serena guidance on every session (like pi-subagent's agent-list hook)
|
|
590
|
+
pi.on("before_agent_start", async (event) => {
|
|
591
|
+
return {
|
|
592
|
+
systemPrompt:
|
|
593
|
+
event.systemPrompt +
|
|
594
|
+
"\n\nSerena semantic tools are available for code-symbol work: serena_find_symbol, serena_get_symbols_overview, serena_find_referencing_symbols, and more. Prefer these over repeated grep/read for exploring code structure, finding symbols, navigating references, and whole-symbol edits. Normal read/search tools remain appropriate for Markdown, JSON/YAML, docs, and exact text edits.",
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
|
|
543
598
|
pi.on("tool_call", async (event) => {
|
|
544
599
|
if (event.toolName.startsWith("serena_")) {
|
|
545
600
|
semanticMissCount = 0;
|
package/package.json
CHANGED
package/worker.ts
CHANGED
|
@@ -154,6 +154,22 @@ def handle(req: dict[str, Any]) -> dict[str, Any]:
|
|
|
154
154
|
opened = bool(req.get("open")) and agent.open_dashboard()
|
|
155
155
|
return {"id": req_id, "ok": True, "opened": opened, "dashboardUrl": agent.get_dashboard_url()}
|
|
156
156
|
|
|
157
|
+
if action == "config":
|
|
158
|
+
project = str(req.get("project") or os.getcwd())
|
|
159
|
+
context = str(req.get("context") or "ide")
|
|
160
|
+
agent = get_agent(project, context)
|
|
161
|
+
with contextlib.redirect_stdout(sys.stderr):
|
|
162
|
+
result = agent.get_current_config_overview()
|
|
163
|
+
return {"id": req_id, "ok": True, "tool": "get_current_config", "result": result}
|
|
164
|
+
|
|
165
|
+
if action == "restart_language_server":
|
|
166
|
+
project = str(req.get("project") or os.getcwd())
|
|
167
|
+
context = str(req.get("context") or "ide")
|
|
168
|
+
agent = get_agent(project, context)
|
|
169
|
+
with contextlib.redirect_stdout(sys.stderr):
|
|
170
|
+
agent.reset_language_server_manager()
|
|
171
|
+
return {"id": req_id, "ok": True, "tool": "restart_language_server", "result": "OK"}
|
|
172
|
+
|
|
157
173
|
if action == "call":
|
|
158
174
|
project = str(req.get("project") or os.getcwd())
|
|
159
175
|
context = str(req.get("context") or "ide")
|