@trim21/personal-pi-extensions 0.1.20261103084002 → 0.1.20261104043713

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.20261103084002",
3
+ "version": "0.1.20261104043713",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -54,7 +54,7 @@
54
54
  "eslint-plugin-import-x": "^4.17.1",
55
55
  "eslint-plugin-promise": "7.3.0",
56
56
  "eslint-plugin-simple-import-sort": "14.0.0",
57
- "eslint-plugin-unicorn": "77.0.0",
57
+ "eslint-plugin-unicorn": "79.0.0",
58
58
  "eslint-plugin-unused-imports": "4.4.1",
59
59
  "husky": "^9.1.7",
60
60
  "jiti": "^2.7.0",
@@ -79,7 +79,6 @@ function formatBashSuccess(result: Awaited<ReturnType<BwrapRuntime["execute"]>>)
79
79
  content: { type: "text"; text: string }[];
80
80
  details: BashToolDetails | undefined;
81
81
  } {
82
- // eslint-disable-next-line unicorn/prefer-default-parameters -- output 是 string,空输出需显示 "(no output)",默认参数只覆盖 undefined
83
82
  const { output, truncation, fullOutputPath } = result;
84
83
  const text = appendTruncationNotice(output || "(no output)", truncation, fullOutputPath);
85
84
  return {
@@ -438,6 +438,39 @@ async function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<
438
438
  await Promise.race([promise, abort]);
439
439
  }
440
440
 
441
+ /**
442
+ * 打开文档让服务器索引 / 产出诊断。diagnostics 传 "document" 时最多等 5s,
443
+ * "full" 最多等 10s;不传则只 didOpen 不等待。
444
+ */
445
+ async function openAndWaitForDiagnostics(
446
+ clients: LspClient[],
447
+ file: string,
448
+ diagnostics?: "document" | "full",
449
+ options?: LspRequestOptions,
450
+ ): Promise<void> {
451
+ await Promise.all(
452
+ clients.map(async (client) => {
453
+ const after = Date.now();
454
+ const version = await client.notify.open({ path: file });
455
+ if (!diagnostics) {
456
+ return;
457
+ }
458
+ await abortable(
459
+ client.waitForDiagnostics({
460
+ path: file,
461
+ version,
462
+ mode: diagnostics,
463
+ after,
464
+ signal: options?.signal,
465
+ }),
466
+ options?.signal,
467
+ );
468
+ }),
469
+ ).catch(() => {
470
+ // 诊断等待失败不影响读 / 写操作本身
471
+ });
472
+ }
473
+
441
474
  export interface LspService {
442
475
  touchFile(
443
476
  file: string,
@@ -1034,10 +1067,6 @@ export function createLspService(
1034
1067
  return result;
1035
1068
  }
1036
1069
 
1037
- /**
1038
- * 打开文档让服务器索引 / 产出诊断。diagnostics 传 "document" 时最多等 5s,
1039
- * "full" 最多等 10s;不传则只 didOpen 不等待。
1040
- */
1041
1070
  async function touchFile(
1042
1071
  file: string,
1043
1072
  cwd: string,
@@ -1045,34 +1074,21 @@ export function createLspService(
1045
1074
  options?: LspRequestOptions,
1046
1075
  ): Promise<void> {
1047
1076
  const clients = await getClients(file, cwd, options?.notify);
1048
- await Promise.all(
1049
- clients.map(async (client) => {
1050
- const after = Date.now();
1051
- const version = await client.notify.open({ path: file });
1052
- if (!diagnostics) {
1053
- return;
1054
- }
1055
- await abortable(
1056
- client.waitForDiagnostics({
1057
- path: file,
1058
- version,
1059
- mode: diagnostics,
1060
- after,
1061
- signal: options?.signal,
1062
- }),
1063
- options?.signal,
1064
- );
1065
- }),
1066
- ).catch(() => {
1067
- // 诊断等待失败不影响写操作本身
1068
- });
1077
+ await openAndWaitForDiagnostics(clients, file, diagnostics, options);
1069
1078
  }
1070
1079
 
1071
- /** 聚合所有 client 的当前诊断(path → diagnostics)。 */
1080
+ /** 聚合活跃工作区内的当前诊断(path → diagnostics)。 */
1072
1081
  function diagnostics(): Promise<Record<string, Diagnostic[]>> {
1073
1082
  const results: Record<string, Diagnostic[]> = {};
1074
1083
  for (const client of state.clients) {
1075
1084
  for (const [filePath, diags] of client.diagnostics) {
1085
+ // 服务器可以为 root 外或 cwd 外的路径推送 / pull 诊断:只保留活跃工作区内的
1086
+ if (state.cwd !== undefined && !containsPath(filePath, state.cwd)) {
1087
+ continue;
1088
+ }
1089
+ if (!containsPath(filePath, client.root)) {
1090
+ continue;
1091
+ }
1076
1092
  (results[filePath] ??= []).push(...diags);
1077
1093
  }
1078
1094
  }
@@ -1081,7 +1097,8 @@ export function createLspService(
1081
1097
 
1082
1098
  /**
1083
1099
  * read / edit / write 用:等待文档诊断并返回该文件的 ERROR / WARN 报告(text 空串表示无此类诊断)
1084
- * 与数量。内部所有 LSP 失败都会被吞掉,不干扰读取或写操作本身。
1100
+ * 与数量。诊断只取拥有该文件的 client(见 getClients),不属于活跃工作区的文件报告为空。
1101
+ * 内部所有 LSP 失败都会被吞掉,不干扰读取或写操作本身。
1085
1102
  */
1086
1103
  async function lspDiagnosticsForFile(
1087
1104
  file: string,
@@ -1091,10 +1108,14 @@ export function createLspService(
1091
1108
  if (options?.signal?.aborted) {
1092
1109
  return EMPTY_DIAGNOSTIC_REPORT;
1093
1110
  }
1094
- await touchFile(file, cwd, "document", options);
1095
- const all = await diagnostics();
1111
+ const clients = await getClients(file, cwd, options?.notify);
1112
+ if (clients.length === 0) {
1113
+ return EMPTY_DIAGNOSTIC_REPORT;
1114
+ }
1115
+ await openAndWaitForDiagnostics(clients, file, "document", options);
1096
1116
  const normalized = normalize(file);
1097
- return report(normalized, all[normalized] ?? []);
1117
+ const collected = clients.flatMap((client) => client.diagnostics.get(normalized) ?? []);
1118
+ return report(normalized, collected);
1098
1119
  }
1099
1120
 
1100
1121
  /** 符号重命名:只面向 language 类服务器;按配置顺序取第一个成功结果。 */
@@ -229,9 +229,9 @@ export function registerLspRenameTool(
229
229
  }
230
230
 
231
231
  const renamed =
232
- result.placeholder !== undefined && result.placeholder !== params.new_name
233
- ? `'${result.placeholder}' -> '${params.new_name}'`
234
- : `to '${params.new_name}'`;
232
+ result.placeholder === undefined || result.placeholder === params.new_name
233
+ ? `to '${params.new_name}'`
234
+ : `'${result.placeholder}' -> '${params.new_name}'`;
235
235
  const summary = `Renamed ${renamed} across ${applied.length} file(s).`;
236
236
  const text =
237
237
  diagnosticText === ""
@@ -123,7 +123,7 @@ function interpolateEnvVars(value: string, env: NodeJS.ProcessEnv): string {
123
123
  // eslint-disable-next-line unicorn/prefer-default-parameters -- replaceAll 回调参数被上下文的 ...args: any[] 定成 any,默认参数无法保留 string 注解
124
124
  return value.replaceAll(envVarPattern, (_match, name: string, fallback?: string) => {
125
125
  const resolved = env[name];
126
- return resolved !== undefined && resolved !== "" ? resolved : (fallback ?? "");
126
+ return resolved === undefined || resolved === "" ? (fallback ?? "") : resolved;
127
127
  });
128
128
  }
129
129
 
@@ -446,7 +446,6 @@ function registerReadTool(bus: ToolBus, getService: () => LspService, state: Rea
446
446
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
447
447
  signal?.throwIfAborted();
448
448
 
449
- // eslint-disable-next-line unicorn/prefer-default-parameters -- params.offset || 1:0 视为 1,默认参数只覆盖 undefined
450
449
  const { filePath: rawPath, offset, limit } = params;
451
450
 
452
451
  const absolutePath = isAbsolute(rawPath) ? rawPath : resolvePath(ctx.cwd, rawPath);