@oh-my-pi/pi-coding-agent 10.2.2 → 10.3.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/package.json +7 -7
  3. package/src/commit/agentic/prompts/analyze-file.md +7 -7
  4. package/src/commit/agentic/prompts/session-user.md +4 -4
  5. package/src/commit/agentic/prompts/system.md +14 -16
  6. package/src/commit/prompts/analysis-system.md +7 -9
  7. package/src/commit/prompts/analysis-user.md +0 -3
  8. package/src/commit/prompts/changelog-system.md +14 -19
  9. package/src/commit/prompts/file-observer-system.md +2 -2
  10. package/src/commit/prompts/reduce-system.md +13 -23
  11. package/src/commit/prompts/summary-system.md +7 -21
  12. package/src/config/settings-schema.ts +135 -56
  13. package/src/config/settings.ts +0 -6
  14. package/src/cursor.ts +2 -1
  15. package/src/extensibility/extensions/index.ts +0 -11
  16. package/src/extensibility/extensions/types.ts +1 -30
  17. package/src/extensibility/hooks/types.ts +1 -31
  18. package/src/index.ts +0 -11
  19. package/src/ipy/prelude.py +1 -113
  20. package/src/lsp/index.ts +66 -515
  21. package/src/lsp/render.ts +0 -11
  22. package/src/lsp/types.ts +3 -87
  23. package/src/modes/components/settings-defs.ts +3 -2
  24. package/src/modes/components/settings-selector.ts +14 -14
  25. package/src/modes/theme/theme.ts +45 -1
  26. package/src/prompts/agents/designer.md +23 -27
  27. package/src/prompts/agents/explore.md +28 -38
  28. package/src/prompts/agents/init.md +17 -17
  29. package/src/prompts/agents/plan.md +21 -27
  30. package/src/prompts/agents/reviewer.md +37 -37
  31. package/src/prompts/compaction/branch-summary.md +9 -9
  32. package/src/prompts/compaction/compaction-summary.md +8 -12
  33. package/src/prompts/compaction/compaction-update-summary.md +17 -19
  34. package/src/prompts/review-request.md +12 -13
  35. package/src/prompts/system/custom-system-prompt.md +6 -26
  36. package/src/prompts/system/plan-mode-active.md +23 -35
  37. package/src/prompts/system/plan-mode-subagent.md +7 -7
  38. package/src/prompts/system/subagent-system-prompt.md +7 -7
  39. package/src/prompts/system/system-prompt.md +134 -149
  40. package/src/prompts/system/web-search.md +10 -10
  41. package/src/prompts/tools/ask.md +12 -15
  42. package/src/prompts/tools/bash.md +7 -7
  43. package/src/prompts/tools/exit-plan-mode.md +6 -6
  44. package/src/prompts/tools/gemini-image.md +4 -4
  45. package/src/prompts/tools/grep.md +4 -4
  46. package/src/prompts/tools/lsp.md +12 -19
  47. package/src/prompts/tools/patch.md +26 -30
  48. package/src/prompts/tools/python.md +14 -57
  49. package/src/prompts/tools/read.md +4 -4
  50. package/src/prompts/tools/replace.md +8 -8
  51. package/src/prompts/tools/ssh.md +14 -27
  52. package/src/prompts/tools/task.md +23 -35
  53. package/src/prompts/tools/todo-write.md +29 -38
  54. package/src/prompts/tools/write.md +3 -3
  55. package/src/sdk.ts +0 -2
  56. package/src/session/agent-session.ts +27 -6
  57. package/src/system-prompt.ts +1 -219
  58. package/src/task/agents.ts +2 -1
  59. package/src/tools/bash-interceptor.ts +0 -24
  60. package/src/tools/bash.ts +1 -7
  61. package/src/tools/index.ts +8 -3
  62. package/src/tools/read.ts +74 -17
  63. package/src/tools/renderers.ts +0 -2
  64. package/src/lsp/rust-analyzer.ts +0 -184
  65. package/src/tools/ls.ts +0 -307
package/src/lsp/index.ts CHANGED
@@ -22,17 +22,11 @@ import {
22
22
  WARMUP_TIMEOUT_MS,
23
23
  } from "./client";
24
24
  import { getLinterClient } from "./clients";
25
- import { getServersForFile, hasCapability, type LspConfig, loadConfig } from "./config";
25
+ import { getServersForFile, type LspConfig, loadConfig } from "./config";
26
26
  import { applyTextEditsToString, applyWorkspaceEdit } from "./edits";
27
27
  import { detectLspmux } from "./lspmux";
28
28
  import { renderCall, renderResult } from "./render";
29
- import * as rustAnalyzer from "./rust-analyzer";
30
29
  import {
31
- type CallHierarchyIncomingCall,
32
- type CallHierarchyItem,
33
- type CallHierarchyOutgoingCall,
34
- type CodeAction,
35
- type Command,
36
30
  type Diagnostic,
37
31
  type DocumentSymbol,
38
32
  type Hover,
@@ -57,7 +51,6 @@ import {
57
51
  formatSymbolInformation,
58
52
  formatWorkspaceEdit,
59
53
  symbolKindToIcon,
60
- uriToFile,
61
54
  } from "./utils";
62
55
 
63
56
  export type { LspServerStatus } from "./client";
@@ -243,8 +236,6 @@ function getLspServerForFile(config: LspConfig, filePath: string): [string, Serv
243
236
  return servers.length > 0 ? servers[0] : null;
244
237
  }
245
238
 
246
- const FILE_SEARCH_MAX_DEPTH = 5;
247
- const IGNORED_DIRS = new Set(["node_modules", "target", "dist", "build", ".git"]);
248
239
  const DIAGNOSTIC_MESSAGE_LIMIT = 50;
249
240
 
250
241
  function limitDiagnosticMessages(messages: string[]): string[] {
@@ -254,84 +245,14 @@ function limitDiagnosticMessages(messages: string[]): string[] {
254
245
  return messages.slice(0, DIAGNOSTIC_MESSAGE_LIMIT);
255
246
  }
256
247
 
257
- function findFileByExtensions(baseDir: string, extensions: string[], maxDepth: number): string | null {
258
- const normalized = extensions.map(ext => ext.toLowerCase());
259
- const search = (dir: string, depth: number): string | null => {
260
- if (depth > maxDepth) return null;
261
- const entries: fs.Dirent[] = [];
262
- try {
263
- const names = Array.from(new Bun.Glob("*").scanSync({ cwd: dir, onlyFiles: false }));
264
- for (const name of names) {
265
- const fullPath = path.join(dir, name);
266
- let isDir = false;
267
- try {
268
- isDir = fs.statSync(fullPath).isDirectory();
269
- } catch {
270
- continue;
271
- }
272
- entries.push({ name, isFile: () => !isDir, isDirectory: () => isDir } as fs.Dirent);
273
- }
274
- } catch {
275
- return null;
276
- }
277
-
278
- for (const entry of entries) {
279
- if (entry.name.startsWith(".")) continue;
280
- if (entry.isDirectory() && IGNORED_DIRS.has(entry.name)) continue;
281
- const fullPath = path.join(dir, entry.name);
282
-
283
- if (entry.isFile()) {
284
- const lowerName = entry.name.toLowerCase();
285
- if (normalized.some(ext => lowerName.endsWith(ext))) {
286
- return fullPath;
287
- }
288
- } else if (entry.isDirectory()) {
289
- const found = search(fullPath, depth + 1);
290
- if (found) return found;
291
- }
292
- }
293
- return null;
294
- };
295
-
296
- return search(baseDir, 0);
297
- }
298
-
299
- function findFileForServer(cwd: string, serverConfig: ServerConfig): string | null {
300
- return findFileByExtensions(cwd, serverConfig.fileTypes, FILE_SEARCH_MAX_DEPTH);
301
- }
302
-
303
- function getRustServer(config: LspConfig): [string, ServerConfig] | null {
304
- const entries = getLspServers(config);
305
- const byName = entries.find(([name, server]) => name === "rust-analyzer" || server.command === "rust-analyzer");
306
- if (byName) return byName;
307
-
308
- for (const [name, server] of entries) {
309
- if (
310
- hasCapability(server, "flycheck") ||
311
- hasCapability(server, "ssr") ||
312
- hasCapability(server, "runnables") ||
313
- hasCapability(server, "expandMacro") ||
314
- hasCapability(server, "relatedTests")
315
- ) {
316
- return [name, server];
317
- }
318
- }
319
-
320
- return null;
321
- }
322
-
323
248
  function getServerForWorkspaceAction(config: LspConfig, action: string): [string, ServerConfig] | null {
324
249
  const entries = getLspServers(config);
325
250
  if (entries.length === 0) return null;
326
251
 
327
- if (action === "workspace_symbols") {
252
+ if (action === "symbols" || action === "reload") {
328
253
  return entries[0];
329
254
  }
330
255
 
331
- if (action === "flycheck" || action === "ssr" || action === "runnables" || action === "reload_workspace") {
332
- return getRustServer(config);
333
- }
334
-
335
256
  return null;
336
257
  }
337
258
 
@@ -386,45 +307,9 @@ function detectProjectType(cwd: string): ProjectType {
386
307
  }
387
308
 
388
309
  /** Run workspace diagnostics command and parse output */
389
- async function runWorkspaceDiagnostics(
390
- cwd: string,
391
- config: LspConfig,
392
- ): Promise<{ output: string; projectType: ProjectType }> {
310
+ async function runWorkspaceDiagnostics(cwd: string): Promise<{ output: string; projectType: ProjectType }> {
393
311
  const projectType = detectProjectType(cwd);
394
312
 
395
- // For Rust, use flycheck via rust-analyzer if available
396
- if (projectType.type === "rust") {
397
- const rustServer = getRustServer(config);
398
- if (rustServer && hasCapability(rustServer[1], "flycheck")) {
399
- const [_serverName, serverConfig] = rustServer;
400
- try {
401
- const client = await getOrCreateClient(serverConfig, cwd);
402
- await rustAnalyzer.flycheck(client);
403
-
404
- const collected: Array<{ filePath: string; diagnostic: Diagnostic }> = [];
405
- for (const [diagUri, diags] of client.diagnostics.entries()) {
406
- const relPath = path.relative(cwd, uriToFile(diagUri));
407
- for (const diag of diags) {
408
- collected.push({ filePath: relPath, diagnostic: diag });
409
- }
410
- }
411
-
412
- if (collected.length === 0) {
413
- return { output: "No issues found", projectType };
414
- }
415
-
416
- const summary = formatDiagnosticsSummary(collected.map(d => d.diagnostic));
417
- const formatted = collected.slice(0, 50).map(d => formatDiagnostic(d.diagnostic, d.filePath));
418
- const more = collected.length > 50 ? `\n ... and ${collected.length - 50} more` : "";
419
- return { output: `${summary}:\n${formatted.map(f => ` ${f}`).join("\n")}${more}`, projectType };
420
- } catch (err) {
421
- logger.debug("LSP diagnostics failed, falling back to shell", { error: String(err) });
422
- // Fall through to shell command
423
- }
424
- }
425
- }
426
-
427
- // Fall back to shell command
428
313
  if (!projectType.command) {
429
314
  return {
430
315
  output: `Cannot detect project type. Supported: Rust (Cargo.toml), TypeScript (tsconfig.json), Go (go.mod), Python (pyproject.toml)`,
@@ -979,22 +864,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
979
864
  _onUpdate?: AgentToolUpdateCallback<LspToolDetails>,
980
865
  _context?: AgentToolContext,
981
866
  ): Promise<AgentToolResult<LspToolDetails>> {
982
- const {
983
- action,
984
- file,
985
- files,
986
- line,
987
- column,
988
- end_line,
989
- end_character,
990
- query,
991
- new_name,
992
- replacement,
993
- kind,
994
- apply,
995
- action_index,
996
- include_declaration,
997
- } = params;
867
+ const { action, file, files, line, column, query, new_name, apply, include_declaration } = params;
998
868
 
999
869
  const config = getConfig(this.session.cwd);
1000
870
 
@@ -1020,27 +890,20 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1020
890
  };
1021
891
  }
1022
892
 
1023
- // Workspace diagnostics - check entire project
1024
- if (action === "workspace_diagnostics") {
1025
- const result = await runWorkspaceDiagnostics(this.session.cwd, config);
1026
- return {
1027
- content: [
1028
- {
1029
- type: "text",
1030
- text: `Workspace diagnostics (${result.projectType.description}):\n${result.output}`,
1031
- },
1032
- ],
1033
- details: { action, success: true, request: params },
1034
- };
1035
- }
1036
-
1037
893
  // Diagnostics can be batch or single-file - queries all applicable servers
1038
894
  if (action === "diagnostics") {
1039
895
  const targets = files?.length ? files : file ? [file] : null;
1040
896
  if (!targets) {
897
+ // No file specified - run workspace diagnostics
898
+ const result = await runWorkspaceDiagnostics(this.session.cwd);
1041
899
  return {
1042
- content: [{ type: "text", text: "Error: file or files parameter required for diagnostics" }],
1043
- details: { action, success: false },
900
+ content: [
901
+ {
902
+ type: "text",
903
+ text: `Workspace diagnostics (${result.projectType.description}):\n${result.output}`,
904
+ },
905
+ ],
906
+ details: { action, success: true, request: params },
1044
907
  };
1045
908
  }
1046
909
 
@@ -1125,13 +988,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1125
988
  };
1126
989
  }
1127
990
 
1128
- const requiresFile =
1129
- !file &&
1130
- action !== "workspace_symbols" &&
1131
- action !== "flycheck" &&
1132
- action !== "ssr" &&
1133
- action !== "runnables" &&
1134
- action !== "reload_workspace";
991
+ const requiresFile = !file && action !== "symbols" && action !== "reload";
1135
992
 
1136
993
  if (requiresFile) {
1137
994
  return {
@@ -1156,16 +1013,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1156
1013
 
1157
1014
  try {
1158
1015
  const client = await getOrCreateClient(serverConfig, this.session.cwd);
1159
- let targetFile = resolvedFile;
1160
- if (action === "runnables" && !targetFile) {
1161
- targetFile = findFileForServer(this.session.cwd, serverConfig);
1162
- if (!targetFile) {
1163
- return {
1164
- content: [{ type: "text", text: "Error: no matching files found for runnables" }],
1165
- details: { action, serverName, success: false },
1166
- };
1167
- }
1168
- }
1016
+ const targetFile = resolvedFile;
1169
1017
 
1170
1018
  if (targetFile) {
1171
1019
  await ensureFileOpen(client, targetFile);
@@ -1245,52 +1093,47 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1245
1093
  }
1246
1094
 
1247
1095
  case "symbols": {
1248
- const result = (await sendRequest(client, "textDocument/documentSymbol", {
1249
- textDocument: { uri },
1250
- })) as (DocumentSymbol | SymbolInformation)[] | null;
1251
-
1252
- if (!result || result.length === 0) {
1253
- output = "No symbols found";
1254
- } else if (!targetFile) {
1255
- return {
1256
- content: [{ type: "text", text: "Error: file parameter required for symbols" }],
1257
- details: { action, serverName, success: false },
1258
- };
1259
- } else {
1260
- const relPath = path.relative(this.session.cwd, targetFile);
1261
- // Check if hierarchical (DocumentSymbol) or flat (SymbolInformation)
1262
- if ("selectionRange" in result[0]) {
1263
- // Hierarchical
1264
- const lines = (result as DocumentSymbol[]).flatMap(s => formatDocumentSymbol(s));
1265
- output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
1096
+ // If no file, do workspace symbol search (requires query)
1097
+ if (!targetFile) {
1098
+ if (!query) {
1099
+ return {
1100
+ content: [
1101
+ { type: "text", text: "Error: query parameter required for workspace symbol search" },
1102
+ ],
1103
+ details: { action, serverName, success: false },
1104
+ };
1105
+ }
1106
+ const result = (await sendRequest(client, "workspace/symbol", { query })) as
1107
+ | SymbolInformation[]
1108
+ | null;
1109
+ if (!result || result.length === 0) {
1110
+ output = `No symbols matching "${query}"`;
1266
1111
  } else {
1267
- // Flat
1268
- const lines = (result as SymbolInformation[]).map(s => {
1269
- const line = s.location.range.start.line + 1;
1270
- const icon = symbolKindToIcon(s.kind);
1271
- return `${icon} ${s.name} @ line ${line}`;
1272
- });
1273
- output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
1112
+ const lines = result.map(s => formatSymbolInformation(s, this.session.cwd));
1113
+ output = `Found ${result.length} symbol(s) matching "${query}":\n${lines.map(l => ` ${l}`).join("\n")}`;
1274
1114
  }
1275
- }
1276
- break;
1277
- }
1278
-
1279
- case "workspace_symbols": {
1280
- if (!query) {
1281
- return {
1282
- content: [{ type: "text", text: "Error: query parameter required for workspace_symbols" }],
1283
- details: { action, serverName, success: false },
1284
- };
1285
- }
1286
-
1287
- const result = (await sendRequest(client, "workspace/symbol", { query })) as SymbolInformation[] | null;
1288
-
1289
- if (!result || result.length === 0) {
1290
- output = `No symbols matching "${query}"`;
1291
1115
  } else {
1292
- const lines = result.map(s => formatSymbolInformation(s, this.session.cwd));
1293
- output = `Found ${result.length} symbol(s) matching "${query}":\n${lines.map(l => ` ${l}`).join("\n")}`;
1116
+ // File-based document symbols
1117
+ const result = (await sendRequest(client, "textDocument/documentSymbol", {
1118
+ textDocument: { uri },
1119
+ })) as (DocumentSymbol | SymbolInformation)[] | null;
1120
+
1121
+ if (!result || result.length === 0) {
1122
+ output = "No symbols found";
1123
+ } else {
1124
+ const relPath = path.relative(this.session.cwd, targetFile);
1125
+ if ("selectionRange" in result[0]) {
1126
+ const lines = (result as DocumentSymbol[]).flatMap(s => formatDocumentSymbol(s));
1127
+ output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
1128
+ } else {
1129
+ const lines = (result as SymbolInformation[]).map(s => {
1130
+ const line = s.location.range.start.line + 1;
1131
+ const icon = symbolKindToIcon(s.kind);
1132
+ return `${icon} ${s.name} @ line ${line}`;
1133
+ });
1134
+ output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
1135
+ }
1136
+ }
1294
1137
  }
1295
1138
  break;
1296
1139
  }
@@ -1324,314 +1167,22 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1324
1167
  break;
1325
1168
  }
1326
1169
 
1327
- case "actions": {
1328
- if (!targetFile) {
1329
- return {
1330
- content: [{ type: "text", text: "Error: file parameter required for actions" }],
1331
- details: { action, serverName, success: false },
1332
- };
1333
- }
1334
-
1335
- const actionsMinVersion = client.diagnosticsVersion;
1336
- await refreshFile(client, targetFile);
1337
- const diagnostics = await waitForDiagnostics(client, uri, 3000, undefined, actionsMinVersion);
1338
- const endLine = (end_line ?? line ?? 1) - 1;
1339
- const endCharacter = (end_character ?? column ?? 1) - 1;
1340
- const range = { start: position, end: { line: endLine, character: endCharacter } };
1341
- const relevantDiagnostics = diagnostics.filter(
1342
- d => d.range.start.line <= range.end.line && d.range.end.line >= range.start.line,
1343
- );
1344
-
1345
- const codeActionContext: { diagnostics: Diagnostic[]; only?: string[] } = {
1346
- diagnostics: relevantDiagnostics,
1347
- };
1348
- if (kind) {
1349
- codeActionContext.only = [kind];
1350
- }
1351
-
1352
- const result = (await sendRequest(client, "textDocument/codeAction", {
1353
- textDocument: { uri },
1354
- range,
1355
- context: codeActionContext,
1356
- })) as Array<CodeAction | Command> | null;
1357
-
1358
- if (!result || result.length === 0) {
1359
- output = "No code actions available";
1360
- } else if (action_index !== undefined) {
1361
- // Apply specific action
1362
- if (action_index < 0 || action_index >= result.length) {
1363
- return {
1364
- content: [
1365
- {
1366
- type: "text",
1367
- text: `Error: action_index ${action_index} out of range (0-${result.length - 1})`,
1368
- },
1369
- ],
1370
- details: { action, serverName, success: false },
1371
- };
1170
+ case "reload": {
1171
+ // Try graceful reload first, fall back to kill
1172
+ output = `Restarted ${serverName}`;
1173
+ const reloadMethods = ["rust-analyzer/reloadWorkspace", "workspace/didChangeConfiguration"];
1174
+ for (const method of reloadMethods) {
1175
+ try {
1176
+ await sendRequest(client, method, method.includes("Configuration") ? { settings: {} } : null);
1177
+ output = `Reloaded ${serverName}`;
1178
+ break;
1179
+ } catch {
1180
+ // Method not supported, try next
1372
1181
  }
1373
-
1374
- const isCommand = (candidate: CodeAction | Command): candidate is Command =>
1375
- typeof (candidate as Command).command === "string";
1376
- const isCodeAction = (candidate: CodeAction | Command): candidate is CodeAction =>
1377
- !isCommand(candidate);
1378
- const getCommandPayload = (
1379
- candidate: CodeAction | Command,
1380
- ): { command: string; arguments?: unknown[] } | null => {
1381
- if (isCommand(candidate)) {
1382
- return { command: candidate.command, arguments: candidate.arguments };
1383
- }
1384
- if (candidate.command) {
1385
- return { command: candidate.command.command, arguments: candidate.command.arguments };
1386
- }
1387
- return null;
1388
- };
1389
-
1390
- const codeAction = result[action_index];
1391
-
1392
- // Resolve if needed
1393
- let resolvedAction = codeAction;
1394
- if (
1395
- isCodeAction(codeAction) &&
1396
- !codeAction.edit &&
1397
- codeAction.data &&
1398
- client.serverCapabilities?.codeActionProvider
1399
- ) {
1400
- const provider = client.serverCapabilities.codeActionProvider;
1401
- if (typeof provider === "object" && provider.resolveProvider) {
1402
- resolvedAction = (await sendRequest(client, "codeAction/resolve", codeAction)) as CodeAction;
1403
- }
1404
- }
1405
-
1406
- if (isCodeAction(resolvedAction) && resolvedAction.edit) {
1407
- const applied = await applyWorkspaceEdit(resolvedAction.edit, this.session.cwd);
1408
- output = `Applied "${codeAction.title}":\n${applied.map(a => ` ${a}`).join("\n")}`;
1409
- } else {
1410
- const commandPayload = getCommandPayload(resolvedAction);
1411
- if (commandPayload) {
1412
- await sendRequest(client, "workspace/executeCommand", commandPayload);
1413
- output = `Executed "${codeAction.title}"`;
1414
- } else {
1415
- output = `Code action "${codeAction.title}" has no edits or command to apply`;
1416
- }
1417
- }
1418
- } else {
1419
- // List available actions
1420
- const lines = result.map((actionItem, i) => {
1421
- if ("kind" in actionItem || "isPreferred" in actionItem || "edit" in actionItem) {
1422
- const actionDetails = actionItem as CodeAction;
1423
- const preferred = actionDetails.isPreferred ? " (preferred)" : "";
1424
- const kindInfo = actionDetails.kind ? ` [${actionDetails.kind}]` : "";
1425
- return ` [${i}] ${actionDetails.title}${kindInfo}${preferred}`;
1426
- }
1427
- return ` [${i}] ${actionItem.title}`;
1428
- });
1429
- output = `Available code actions:\n${lines.join("\n")}\n\nUse action_index parameter to apply a specific action.`;
1430
1182
  }
1431
- break;
1432
- }
1433
-
1434
- case "incoming_calls":
1435
- case "outgoing_calls": {
1436
- // First, prepare the call hierarchy item at the cursor position
1437
- const prepareResult = (await sendRequest(client, "textDocument/prepareCallHierarchy", {
1438
- textDocument: { uri },
1439
- position,
1440
- })) as CallHierarchyItem[] | null;
1441
-
1442
- if (!prepareResult || prepareResult.length === 0) {
1443
- output = "No callable symbol found at this position";
1444
- break;
1445
- }
1446
-
1447
- const item = prepareResult[0];
1448
-
1449
- if (action === "incoming_calls") {
1450
- const calls = (await sendRequest(client, "callHierarchy/incomingCalls", { item })) as
1451
- | CallHierarchyIncomingCall[]
1452
- | null;
1453
-
1454
- if (!calls || calls.length === 0) {
1455
- output = `No callers found for "${item.name}"`;
1456
- } else {
1457
- const lines = calls.map(call => {
1458
- const loc = { uri: call.from.uri, range: call.from.selectionRange };
1459
- const detail = call.from.detail ? ` (${call.from.detail})` : "";
1460
- return ` ${call.from.name}${detail} @ ${formatLocation(loc, this.session.cwd)}`;
1461
- });
1462
- output = `Found ${calls.length} caller(s) of "${item.name}":\n${lines.join("\n")}`;
1463
- }
1464
- } else {
1465
- const calls = (await sendRequest(client, "callHierarchy/outgoingCalls", { item })) as
1466
- | CallHierarchyOutgoingCall[]
1467
- | null;
1468
-
1469
- if (!calls || calls.length === 0) {
1470
- output = `"${item.name}" doesn't call any functions`;
1471
- } else {
1472
- const lines = calls.map(call => {
1473
- const loc = { uri: call.to.uri, range: call.to.selectionRange };
1474
- const detail = call.to.detail ? ` (${call.to.detail})` : "";
1475
- return ` ${call.to.name}${detail} @ ${formatLocation(loc, this.session.cwd)}`;
1476
- });
1477
- output = `"${item.name}" calls ${calls.length} function(s):\n${lines.join("\n")}`;
1478
- }
1479
- }
1480
- break;
1481
- }
1482
-
1483
- // =====================================================================
1484
- // Rust-Analyzer Specific Operations
1485
- // =====================================================================
1486
-
1487
- case "flycheck": {
1488
- if (!hasCapability(serverConfig, "flycheck")) {
1489
- return {
1490
- content: [{ type: "text", text: "Error: flycheck requires rust-analyzer" }],
1491
- details: { action, serverName, success: false },
1492
- };
1183
+ if (output.startsWith("Restarted")) {
1184
+ client.proc.kill();
1493
1185
  }
1494
-
1495
- await rustAnalyzer.flycheck(client, resolvedFile ?? undefined);
1496
- const collected: Array<{ filePath: string; diagnostic: Diagnostic }> = [];
1497
- for (const [diagUri, diags] of client.diagnostics.entries()) {
1498
- const relPath = path.relative(this.session.cwd, uriToFile(diagUri));
1499
- for (const diag of diags) {
1500
- collected.push({ filePath: relPath, diagnostic: diag });
1501
- }
1502
- }
1503
-
1504
- if (collected.length === 0) {
1505
- output = "Flycheck: no issues found";
1506
- } else {
1507
- const summary = formatDiagnosticsSummary(collected.map(d => d.diagnostic));
1508
- const formatted = collected.slice(0, 20).map(d => formatDiagnostic(d.diagnostic, d.filePath));
1509
- const more = collected.length > 20 ? `\n ... and ${collected.length - 20} more` : "";
1510
- output = `Flycheck ${summary}:\n${formatted.map(f => ` ${f}`).join("\n")}${more}`;
1511
- }
1512
- break;
1513
- }
1514
-
1515
- case "expand_macro": {
1516
- if (!hasCapability(serverConfig, "expandMacro")) {
1517
- return {
1518
- content: [{ type: "text", text: "Error: expand_macro requires rust-analyzer" }],
1519
- details: { action, serverName, success: false },
1520
- };
1521
- }
1522
-
1523
- if (!targetFile) {
1524
- return {
1525
- content: [{ type: "text", text: "Error: file parameter required for expand_macro" }],
1526
- details: { action, serverName, success: false },
1527
- };
1528
- }
1529
-
1530
- const result = await rustAnalyzer.expandMacro(client, targetFile, line || 1, column || 1);
1531
- if (!result) {
1532
- output = "No macro expansion at this position";
1533
- } else {
1534
- output = `Macro: ${result.name}\n\nExpansion:\n${result.expansion}`;
1535
- }
1536
- break;
1537
- }
1538
-
1539
- case "ssr": {
1540
- if (!hasCapability(serverConfig, "ssr")) {
1541
- return {
1542
- content: [{ type: "text", text: "Error: ssr requires rust-analyzer" }],
1543
- details: { action, serverName, success: false },
1544
- };
1545
- }
1546
-
1547
- if (!query) {
1548
- return {
1549
- content: [{ type: "text", text: "Error: query parameter (pattern) required for ssr" }],
1550
- details: { action, serverName, success: false },
1551
- };
1552
- }
1553
-
1554
- if (!replacement) {
1555
- return {
1556
- content: [{ type: "text", text: "Error: replacement parameter required for ssr" }],
1557
- details: { action, serverName, success: false },
1558
- };
1559
- }
1560
-
1561
- const shouldApply = apply === true;
1562
- const result = await rustAnalyzer.ssr(client, query, replacement, !shouldApply);
1563
-
1564
- if (shouldApply) {
1565
- const applied = await applyWorkspaceEdit(result, this.session.cwd);
1566
- output =
1567
- applied.length > 0
1568
- ? `Applied SSR:\n${applied.map(a => ` ${a}`).join("\n")}`
1569
- : "SSR: no matches found";
1570
- } else {
1571
- const preview = formatWorkspaceEdit(result, this.session.cwd);
1572
- output =
1573
- preview.length > 0
1574
- ? `SSR preview:\n${preview.map(p => ` ${p}`).join("\n")}`
1575
- : "SSR: no matches found";
1576
- }
1577
- break;
1578
- }
1579
-
1580
- case "runnables": {
1581
- if (!hasCapability(serverConfig, "runnables")) {
1582
- return {
1583
- content: [{ type: "text", text: "Error: runnables requires rust-analyzer" }],
1584
- details: { action, serverName, success: false },
1585
- };
1586
- }
1587
-
1588
- if (!targetFile) {
1589
- return {
1590
- content: [{ type: "text", text: "Error: file parameter required for runnables" }],
1591
- details: { action, serverName, success: false },
1592
- };
1593
- }
1594
-
1595
- const result = await rustAnalyzer.runnables(client, targetFile, line);
1596
- if (result.length === 0) {
1597
- output = "No runnables found";
1598
- } else {
1599
- const lines = result.map(r => {
1600
- const args = r.args?.cargoArgs?.join(" ") || "";
1601
- return ` [${r.kind}] ${r.label}${args ? ` (cargo ${args})` : ""}`;
1602
- });
1603
- output = `Found ${result.length} runnable(s):\n${lines.join("\n")}`;
1604
- }
1605
- break;
1606
- }
1607
-
1608
- case "related_tests": {
1609
- if (!hasCapability(serverConfig, "relatedTests")) {
1610
- return {
1611
- content: [{ type: "text", text: "Error: related_tests requires rust-analyzer" }],
1612
- details: { action, serverName, success: false },
1613
- };
1614
- }
1615
-
1616
- if (!targetFile) {
1617
- return {
1618
- content: [{ type: "text", text: "Error: file parameter required for related_tests" }],
1619
- details: { action, serverName, success: false },
1620
- };
1621
- }
1622
-
1623
- const result = await rustAnalyzer.relatedTests(client, targetFile, line || 1, column || 1);
1624
- if (result.length === 0) {
1625
- output = "No related tests found";
1626
- } else {
1627
- output = `Found ${result.length} related test(s):\n${result.map(t => ` ${t}`).join("\n")}`;
1628
- }
1629
- break;
1630
- }
1631
-
1632
- case "reload_workspace": {
1633
- await rustAnalyzer.reloadWorkspace(client);
1634
- output = "Workspace reloaded successfully";
1635
1186
  break;
1636
1187
  }
1637
1188