@bacnh85/pi-serena 0.1.2 → 0.1.4
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 +40 -0
- package/index.ts +71 -12
- package/package.json +1 -1
- package/worker.ts +16 -0
package/README.md
CHANGED
|
@@ -55,6 +55,46 @@ After install or update, restart Pi or run `/reload` in an existing Pi session.
|
|
|
55
55
|
|
|
56
56
|
All tool outputs are truncated to 50KB / 2000 lines to match Pi-friendly output limits. Most tools accept optional `timeout_ms`.
|
|
57
57
|
|
|
58
|
+
### Pattern search
|
|
59
|
+
|
|
60
|
+
Use the Pi-facing `pattern` field with `serena_search_for_pattern`:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"pattern": "USB_HOST_DEVICE_OBJ|USB_HOST_DEVICE_STATE_ERROR_HOLDING",
|
|
65
|
+
"relative_path": "AmazonFreeRTOS",
|
|
66
|
+
"paths_include_glob": "**/*.h"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
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
|
+
|
|
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
|
+
|
|
58
98
|
## Commands
|
|
59
99
|
|
|
60
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
|
|
|
@@ -104,16 +105,23 @@ const searchPatternSchema = Type.Object({
|
|
|
104
105
|
relative_path: Type.Optional(Type.String({ description: "Optional file or directory restriction relative to the Serena project root." })),
|
|
105
106
|
paths_include_glob: Type.Optional(Type.String({ description: "Optional glob for included paths." })),
|
|
106
107
|
paths_exclude_glob: Type.Optional(Type.String({ description: "Optional glob for excluded paths." })),
|
|
108
|
+
context_lines_before: Type.Optional(Type.Number({ description: "Number of context lines before each match." })),
|
|
109
|
+
context_lines_after: Type.Optional(Type.Number({ description: "Number of context lines after each match." })),
|
|
110
|
+
restrict_search_to_code_files: Type.Optional(Type.Boolean({ description: "Restrict search to source/code files when supported by Serena." })),
|
|
111
|
+
multiline: Type.Optional(Type.Boolean({ description: "Treat the pattern as a multiline regular expression when supported by Serena." })),
|
|
107
112
|
max_answer_chars: MAX_CHARS_PARAM,
|
|
108
113
|
});
|
|
109
114
|
|
|
110
115
|
const replaceContentSchema = Type.Object({
|
|
111
116
|
...controlSchema,
|
|
112
117
|
relative_path: Type.String({ description: "File path relative to the Serena project root." }),
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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." })),
|
|
117
125
|
allow_multiple_occurrences: Type.Optional(Type.Boolean({ description: "Allow replacing multiple matches when supported." })),
|
|
118
126
|
});
|
|
119
127
|
|
|
@@ -135,6 +143,50 @@ function stripControlParams(params: Record<string, unknown>): { project: string;
|
|
|
135
143
|
return { project: normalizeProject(project), context: normalizeContext(context), timeoutMs: normalizeTimeoutMs(timeout_ms), params: toolParams };
|
|
136
144
|
}
|
|
137
145
|
|
|
146
|
+
export function normalizeSearchPatternParams(params: Record<string, unknown>): Record<string, unknown> {
|
|
147
|
+
const normalized = { ...params };
|
|
148
|
+
if (normalized.substring_pattern === undefined && normalized.pattern !== undefined) {
|
|
149
|
+
normalized.substring_pattern = normalized.pattern;
|
|
150
|
+
}
|
|
151
|
+
delete normalized.pattern;
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
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
|
+
|
|
138
190
|
function truncateText(text: string): string {
|
|
139
191
|
const lines = text.split("\n");
|
|
140
192
|
let output = lines.slice(0, OUTPUT_MAX_LINES).join("\n");
|
|
@@ -216,10 +268,10 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
216
268
|
return worker;
|
|
217
269
|
};
|
|
218
270
|
|
|
219
|
-
const
|
|
271
|
+
const callWorkerAction = async (ctx: any, action: string, rawParams: Record<string, unknown>, extraPayload: Record<string, unknown> = {}, lockPath?: string) => {
|
|
220
272
|
const { project, context, timeoutMs, params } = stripControlParams(rawParams);
|
|
221
273
|
const run = async () => {
|
|
222
|
-
const response = await getWorker(ctx).request({ action
|
|
274
|
+
const response = await getWorker(ctx).request({ action, project, context, params, ...extraPayload }, timeoutMs);
|
|
223
275
|
return {
|
|
224
276
|
content: [{ type: "text" as const, text: resultText(response) }],
|
|
225
277
|
details: response,
|
|
@@ -228,6 +280,10 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
228
280
|
return lockPath ? withFileMutationQueue(lockPath, run) : run();
|
|
229
281
|
};
|
|
230
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
|
+
|
|
231
287
|
const lockPathForRelativeFile = (rawParams: Record<string, unknown>): string | undefined => {
|
|
232
288
|
const project = normalizeProject(rawParams.project);
|
|
233
289
|
return typeof rawParams.relative_path === "string" && rawParams.relative_path.trim()
|
|
@@ -301,7 +357,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
301
357
|
promptGuidelines: ["Use serena_find_referencing_symbols before changing public behavior or renaming a symbol."],
|
|
302
358
|
parameters: referencingSchema,
|
|
303
359
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
304
|
-
return callSerena(ctx, "find_referencing_symbols", params);
|
|
360
|
+
return callSerena(ctx, "find_referencing_symbols", normalizeFindReferencesParams(params));
|
|
305
361
|
},
|
|
306
362
|
});
|
|
307
363
|
|
|
@@ -373,7 +429,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
373
429
|
promptGuidelines: ["Use serena_search_for_pattern for project-scoped code searches when symbol lookup is not enough."],
|
|
374
430
|
parameters: searchPatternSchema,
|
|
375
431
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
376
|
-
return callSerena(ctx, "search_for_pattern", params);
|
|
432
|
+
return callSerena(ctx, "search_for_pattern", normalizeSearchPatternParams(params));
|
|
377
433
|
},
|
|
378
434
|
});
|
|
379
435
|
|
|
@@ -385,7 +441,10 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
385
441
|
promptGuidelines: ["Prefer symbol-aware Serena edit tools for whole symbols; use serena_replace_content for non-symbol scoped replacements supported by Serena."],
|
|
386
442
|
parameters: replaceContentSchema,
|
|
387
443
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
388
|
-
|
|
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));
|
|
389
448
|
},
|
|
390
449
|
});
|
|
391
450
|
|
|
@@ -397,7 +456,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
397
456
|
promptGuidelines: ["Use serena_restart_language_server when Serena symbol retrieval appears stale or language-server diagnostics are stuck."],
|
|
398
457
|
parameters: emptyToolSchema,
|
|
399
458
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
400
|
-
return
|
|
459
|
+
return callWorkerAction(ctx, "restart_language_server", params);
|
|
401
460
|
},
|
|
402
461
|
});
|
|
403
462
|
|
|
@@ -409,7 +468,7 @@ export default function serenaToolsExtension(pi: ExtensionAPI) {
|
|
|
409
468
|
promptGuidelines: ["Use serena_get_current_config when project activation, contexts, modes, or tool availability are unclear."],
|
|
410
469
|
parameters: emptyToolSchema,
|
|
411
470
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
412
|
-
return
|
|
471
|
+
return callWorkerAction(ctx, "config", params);
|
|
413
472
|
},
|
|
414
473
|
});
|
|
415
474
|
|
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")
|