@trim21/personal-pi-extensions 0.1.612 → 0.1.619

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 (77) hide show
  1. package/package.json +1 -1
  2. package/src/aft/config.ts +9 -3
  3. package/src/aft/index.ts +6 -2
  4. package/src/aft/logger.ts +9 -3
  5. package/src/aft/tools.ts +3 -1
  6. package/src/bwrap/README.md +19 -10
  7. package/src/bwrap/approval-rules.ts +42 -14
  8. package/src/bwrap/approval-suggest.ts +12 -4
  9. package/src/bwrap/core.ts +39 -13
  10. package/src/bwrap/dcg-scan.ts +16 -6
  11. package/src/bwrap/holder.ts +3 -1
  12. package/src/bwrap/mihomo-config.ts +18 -6
  13. package/src/bwrap/network-stack.ts +69 -24
  14. package/src/bwrap/runtime.ts +30 -10
  15. package/src/bwrap/sandbox.ts +1 -1
  16. package/src/claude-code/common.ts +6 -2
  17. package/src/claude-code/edit-utils.ts +3 -1
  18. package/src/claude-code/files.ts +40 -14
  19. package/src/claude-code/glob.ts +12 -4
  20. package/src/claude-code/grep.ts +41 -14
  21. package/src/claude-code/session-tools.ts +18 -6
  22. package/src/claude-code/shell.ts +9 -3
  23. package/src/gh/base.ts +72 -24
  24. package/src/gh/tools/download-release-assets.ts +12 -4
  25. package/src/gh/tools/list-releases.ts +3 -1
  26. package/src/gh/tools/list-workflow-runs.ts +9 -3
  27. package/src/gh/tools/read-ci-logs.ts +21 -7
  28. package/src/gh/tools/read-repo.ts +3 -1
  29. package/src/lib/cli-args.ts +6 -2
  30. package/src/lib/cli.ts +48 -16
  31. package/src/lib/file-reads.ts +24 -8
  32. package/src/lib/github.ts +35 -12
  33. package/src/lib/lsp/adapter.ts +6 -2
  34. package/src/lib/lsp/bin.ts +12 -4
  35. package/src/lib/lsp/client.ts +205 -69
  36. package/src/lib/lsp/diagnostic.ts +6 -2
  37. package/src/lib/lsp/inspect-tool.ts +25 -9
  38. package/src/lib/lsp/lsp.ts +138 -46
  39. package/src/lib/lsp/rename-tool.ts +11 -4
  40. package/src/lib/lsp/rename.ts +25 -8
  41. package/src/lib/lsp/server-config.ts +41 -14
  42. package/src/lib/lsp/watcher.ts +51 -17
  43. package/src/lib/path.ts +18 -6
  44. package/src/lib/proc.ts +3 -1
  45. package/src/lib/proxy.ts +9 -3
  46. package/src/lib/request-policy.ts +3 -1
  47. package/src/lib/seq-state.ts +3 -1
  48. package/src/lib/ui.ts +23 -8
  49. package/src/lib/write-guard.ts +21 -7
  50. package/src/openai-cost/config.ts +3 -1
  51. package/src/openai-cost/cost.ts +54 -18
  52. package/src/openai-cost/index.ts +3 -1
  53. package/src/openai-cost/provider.ts +6 -2
  54. package/src/opencode/bash.ts +9 -3
  55. package/src/opencode/edit-engine.ts +24 -8
  56. package/src/opencode/files.ts +30 -10
  57. package/src/opencode/glob.ts +3 -1
  58. package/src/opencode/grep.ts +12 -4
  59. package/src/opencode/question.ts +3 -1
  60. package/src/opencode/ripgrep.ts +18 -6
  61. package/src/opencode/todo.ts +3 -1
  62. package/src/session-name.ts +39 -13
  63. package/src/spawn-agent-agents.ts +18 -6
  64. package/src/spawn-agent.ts +53 -18
  65. package/src/system-prompt/index.ts +9 -3
  66. package/src/talk/core.ts +143 -48
  67. package/src/talk/format.ts +12 -4
  68. package/src/talk/group.ts +9 -3
  69. package/src/talk/index.ts +30 -10
  70. package/src/talk/mailbox.ts +30 -10
  71. package/src/talk/policy.ts +6 -2
  72. package/src/talk/registry.ts +33 -11
  73. package/src/talk/storage.ts +3 -1
  74. package/src/vision-agent.ts +41 -14
  75. package/src/web/config.ts +3 -1
  76. package/src/web/fetch.ts +75 -25
  77. package/src/web/search.ts +30 -10
@@ -138,14 +138,19 @@ async function formatReferenceLocations(
138
138
  const byPath = new Map<string, InspectLocation[]>();
139
139
  for (const location of locations) {
140
140
  const existing = byPath.get(location.path);
141
- if (existing) existing.push(location);
142
- else byPath.set(location.path, [location]);
141
+ if (existing) {
142
+ existing.push(location);
143
+ } else {
144
+ byPath.set(location.path, [location]);
145
+ }
143
146
  }
144
147
  const paths = [...byPath.keys()];
145
148
  const sections = [`Found ${locations.length} reference(s) in ${paths.length} file(s):`];
146
149
  for (const path of paths.slice(0, MAX_FILES_LISTED)) {
147
150
  const entries = byPath.get(path);
148
- if (!entries) continue;
151
+ if (!entries) {
152
+ continue;
153
+ }
149
154
  const shown = entries.slice(0, MAX_ENTRIES_PER_FILE);
150
155
  sections.push(`### ${path} (${entries.length})`);
151
156
  for (const entry of shown) {
@@ -177,11 +182,15 @@ function formatMarkedString(marked: MarkedString): string {
177
182
 
178
183
  /** hover contents 原样透传:仅做结构格式化(MarkedString → code fence),不改写内容。 */
179
184
  function formatHoverContents(contents: Hover["contents"]): string {
180
- if (typeof contents === "string") return contents;
185
+ if (typeof contents === "string") {
186
+ return contents;
187
+ }
181
188
  if (Array.isArray(contents)) {
182
189
  return contents.map((marked) => formatMarkedString(marked)).join("\n\n");
183
190
  }
184
- if ("language" in contents) return formatMarkedString(contents);
191
+ if ("language" in contents) {
192
+ return formatMarkedString(contents);
193
+ }
185
194
  return contents.value;
186
195
  }
187
196
 
@@ -217,10 +226,15 @@ async function probeSymbolCandidates(options: {
217
226
  const groups = new Map<string, { output: InspectOutput; candidates: LspPosition[] }>();
218
227
  for (const [index, candidate] of candidates.entries()) {
219
228
  const output = outputs.at(index);
220
- if (!output) continue;
229
+ if (!output) {
230
+ continue;
231
+ }
221
232
  const group = groups.get(output.text);
222
- if (group) group.candidates.push(candidate);
223
- else groups.set(output.text, { output, candidates: [candidate] });
233
+ if (group) {
234
+ group.candidates.push(candidate);
235
+ } else {
236
+ groups.set(output.text, { output, candidates: [candidate] });
237
+ }
224
238
  }
225
239
  if (groups.size > 1) {
226
240
  const listing = [...groups.values()]
@@ -235,7 +249,9 @@ async function probeSymbolCandidates(options: {
235
249
  );
236
250
  }
237
251
  const first = [...groups.values()].at(0);
238
- if (!first) throw new Error("LSP inspect returned no result");
252
+ if (!first) {
253
+ throw new Error("LSP inspect returned no result");
254
+ }
239
255
  return first.output;
240
256
  }
241
257
 
@@ -152,7 +152,9 @@ export interface ResolvedLspConfig {
152
152
  function parseTimeoutString(value: string): number {
153
153
  // 单位组永远参与匹配(缺省为空串),避免"可选捕获组在类型上不可空"的歧义
154
154
  const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|)\s*$/.exec(value.trim());
155
- if (!match) return NaN;
155
+ if (!match) {
156
+ return NaN;
157
+ }
156
158
  const amount = Number(match[1]);
157
159
  const factors: Record<string, number | undefined> = {
158
160
  "": 1,
@@ -167,7 +169,9 @@ function parseTimeoutString(value: string): number {
167
169
 
168
170
  /** 时长字段换算为 ms:number 原样、字符串按 parseTimeoutString;无效值返回 undefined。 */
169
171
  function toMs(value: number | string | undefined): number | undefined {
170
- if (value === undefined) return undefined;
172
+ if (value === undefined) {
173
+ return undefined;
174
+ }
171
175
  const ms = typeof value === "number" ? value : parseTimeoutString(value);
172
176
  return Number.isFinite(ms) && ms > 0 ? ms : undefined;
173
177
  }
@@ -224,11 +228,15 @@ function unknownFieldWarnings(config: LspConfig, filePath: string): string[] {
224
228
  const warnings: string[] = [];
225
229
  const check = (record: object, known: Record<string, unknown>, scope: string): void => {
226
230
  for (const key of Object.keys(record)) {
227
- if (!(key in known)) warnings.push(`${filePath}${scope}: unknown field "${key}" ignored`);
231
+ if (!(key in known)) {
232
+ warnings.push(`${filePath}${scope}: unknown field "${key}" ignored`);
233
+ }
228
234
  }
229
235
  };
230
236
  check(config, lspConfigSchema.properties, "");
231
- if (config.watch) check(config.watch, watchConfigSchema.properties, " watch");
237
+ if (config.watch) {
238
+ check(config.watch, watchConfigSchema.properties, " watch");
239
+ }
232
240
  for (const [id, server] of Object.entries(config.servers ?? {})) {
233
241
  check(server, serverConfigSchema.properties, ` (server "${id}")`);
234
242
  }
@@ -243,10 +251,14 @@ async function readConfigFile(
243
251
  try {
244
252
  const raw = await readFile(filePath, "utf8");
245
253
  const config = Value.Parse(lspConfigSchema, JSON.parse(raw) as unknown);
246
- for (const message of unknownFieldWarnings(config, filePath)) onWarning?.(message);
254
+ for (const message of unknownFieldWarnings(config, filePath)) {
255
+ onWarning?.(message);
256
+ }
247
257
  return config;
248
258
  } catch (error) {
249
- if (isMissingFile(error)) return {};
259
+ if (isMissingFile(error)) {
260
+ return {};
261
+ }
250
262
  throw error;
251
263
  }
252
264
  }
@@ -256,10 +268,14 @@ function readConfigFileSync(filePath: string, onWarning?: (message: string) => v
256
268
  try {
257
269
  const raw = readFileSync(filePath, "utf8");
258
270
  const config = Value.Parse(lspConfigSchema, JSON.parse(raw) as unknown);
259
- for (const message of unknownFieldWarnings(config, filePath)) onWarning?.(message);
271
+ for (const message of unknownFieldWarnings(config, filePath)) {
272
+ onWarning?.(message);
273
+ }
260
274
  return config;
261
275
  } catch (error) {
262
- if (isMissingFile(error)) return {};
276
+ if (isMissingFile(error)) {
277
+ return {};
278
+ }
263
279
  throw error;
264
280
  }
265
281
  }
@@ -288,7 +304,9 @@ function mergeWatch(
288
304
  globalWatch: LspConfig["watch"],
289
305
  localWatch: LspConfig["watch"],
290
306
  ): LspConfig["watch"] {
291
- if (!globalWatch && !localWatch) return undefined;
307
+ if (!globalWatch && !localWatch) {
308
+ return undefined;
309
+ }
292
310
  return {
293
311
  ...globalWatch,
294
312
  ...localWatch,
@@ -337,8 +355,12 @@ export function filterAdapters(
337
355
  ): LspServerAdapter[] {
338
356
  validateConfig(config, adapters);
339
357
  return adapters.filter((adapter) => {
340
- if (config.enabled && !config.enabled.has(adapter.id)) return false;
341
- if (config.disabled?.has(adapter.id)) return false;
358
+ if (config.enabled && !config.enabled.has(adapter.id)) {
359
+ return false;
360
+ }
361
+ if (config.disabled?.has(adapter.id)) {
362
+ return false;
363
+ }
342
364
  return true;
343
365
  });
344
366
  }
@@ -532,7 +554,9 @@ export function createLspService(
532
554
 
533
555
  /** 配置缓存:同一 cwd 内复用;cwd 变化或 reload 清缓存后重读。 */
534
556
  async function currentConfig(cwd: string): Promise<ResolvedLspConfig> {
535
- if (state.config && state.configCwd === cwd) return state.config;
557
+ if (state.config && state.configCwd === cwd) {
558
+ return state.config;
559
+ }
536
560
  const config = await loadLspConfig(cwd, globalConfigPath, (message) =>
537
561
  sessionNotify?.(message, "warning"),
538
562
  );
@@ -553,8 +577,12 @@ export function createLspService(
553
577
  * 退化为 cwd(client 只服务 cwd 内的文件);与 cwd 无交集时返回 undefined。
554
578
  */
555
579
  function watchDirFor(root: string, cwd: string): string | undefined {
556
- if (containsPath(root, cwd)) return root;
557
- if (containsPath(cwd, root)) return cwd;
580
+ if (containsPath(root, cwd)) {
581
+ return root;
582
+ }
583
+ if (containsPath(cwd, root)) {
584
+ return cwd;
585
+ }
558
586
  return undefined;
559
587
  }
560
588
 
@@ -563,7 +591,9 @@ export function createLspService(
563
591
  const dirs = new Set<string>();
564
592
  for (const client of state.clients) {
565
593
  const dir = watchDirFor(client.root, cwd);
566
- if (dir && !watcherFailed.has(dir)) dirs.add(dir);
594
+ if (dir && !watcherFailed.has(dir)) {
595
+ dirs.add(dir);
596
+ }
567
597
  }
568
598
  return [...dirs]
569
599
  .filter((dir) => [...dirs].every((other) => other === dir || !containsPath(dir, other)))
@@ -646,7 +676,9 @@ export function createLspService(
646
676
  cwd: string,
647
677
  notify?: ExtensionUIContext["notify"],
648
678
  ): Promise<void> {
649
- if (state.closing || state.disabled) return;
679
+ if (state.closing || state.disabled) {
680
+ return;
681
+ }
650
682
  let config: ResolvedLspConfig;
651
683
  try {
652
684
  config = await currentConfig(cwd);
@@ -663,23 +695,31 @@ export function createLspService(
663
695
  const desired = config.watch.enabled ? desiredWatchRoots(cwd) : [];
664
696
  // Map 迭代期间删除当前项是安全的
665
697
  for (const [root, watcher] of watchers) {
666
- if (desired.includes(root)) continue;
698
+ if (desired.includes(root)) {
699
+ continue;
700
+ }
667
701
  watchers.delete(root);
668
702
  void watcher.stop().catch(() => {
669
703
  // 停止失败不影响流程
670
704
  });
671
705
  }
672
706
  for (const root of desired) {
673
- if (watchers.has(root)) continue;
707
+ if (watchers.has(root)) {
708
+ continue;
709
+ }
674
710
  const watcher = await startWatcher(root, config.watch, notify);
675
- if (watcher) watchers.set(root, watcher);
711
+ if (watcher) {
712
+ watchers.set(root, watcher);
713
+ }
676
714
  }
677
715
  }
678
716
 
679
717
  /** 事件按各 client 的 root 前缀 / 注册 pattern / 扩展名过滤后投递。 */
680
718
  async function fanOut(changes: FileChange[]): Promise<void> {
681
719
  const cwd = state.cwd;
682
- if (!cwd) return;
720
+ if (!cwd) {
721
+ return;
722
+ }
683
723
  await Promise.all(
684
724
  state.clients.map(async (client) => {
685
725
  const filtered = changes.filter((change) => {
@@ -705,7 +745,9 @@ export function createLspService(
705
745
  (minimatch(relativeCandidate, watcher.pattern) ||
706
746
  minimatch(absoluteCandidate, watcher.pattern)),
707
747
  );
708
- if (!matched) return false;
748
+ if (!matched) {
749
+ return false;
750
+ }
709
751
  }
710
752
  const extensions = state.clientExtensions.get(client);
711
753
  return (
@@ -714,7 +756,9 @@ export function createLspService(
714
756
  extensions.includes(extname(change.path))
715
757
  );
716
758
  });
717
- if (filtered.length === 0) return;
759
+ if (filtered.length === 0) {
760
+ return;
761
+ }
718
762
  try {
719
763
  await client.notify.watchedFiles(filtered);
720
764
  } catch {
@@ -726,7 +770,9 @@ export function createLspService(
726
770
 
727
771
  /** 汇总当前所有 LSP server 状态并渲染到 footer status。 */
728
772
  function updateStatusText(): void {
729
- if (!renderStatus) return;
773
+ if (!renderStatus) {
774
+ return;
775
+ }
730
776
  if (state.disabled) {
731
777
  renderStatus("lsp: disabled");
732
778
  return;
@@ -792,7 +838,9 @@ export function createLspService(
792
838
  ): Promise<LspClient | undefined> {
793
839
  const key = root + adapter.id;
794
840
  const inflight = state.spawning.get(key);
795
- if (inflight) return inflight;
841
+ if (inflight) {
842
+ return inflight;
843
+ }
796
844
  const task = (async () => {
797
845
  try {
798
846
  const handle = await adapter.spawn(root, cwd);
@@ -845,7 +893,9 @@ export function createLspService(
845
893
  })();
846
894
  state.spawning.set(key, task);
847
895
  void task.finally(() => {
848
- if (state.spawning.get(key) === task) state.spawning.delete(key);
896
+ if (state.spawning.get(key) === task) {
897
+ state.spawning.delete(key);
898
+ }
849
899
  });
850
900
  return task;
851
901
  }
@@ -856,8 +906,12 @@ export function createLspService(
856
906
  notify?: ExtensionUIContext["notify"],
857
907
  adapterFilter?: (adapter: LspServerAdapter) => boolean,
858
908
  ): Promise<LspClient[]> {
859
- if (state.closing || state.disabled) return [];
860
- if (!containsPath(file, cwd)) return [];
909
+ if (state.closing || state.disabled) {
910
+ return [];
911
+ }
912
+ if (!containsPath(file, cwd)) {
913
+ return [];
914
+ }
861
915
  const config = await currentConfig(cwd);
862
916
  const active = filterAdapters(adapters ?? createAdapters(config.servers), config);
863
917
  const extension = extname(file) || file;
@@ -870,15 +924,25 @@ export function createLspService(
870
924
  }
871
925
 
872
926
  for (const adapter of active) {
873
- if (adapterFilter && !adapterFilter(adapter)) continue;
874
- if (adapter.extensions.length > 0 && !adapter.extensions.includes(extension)) continue;
927
+ if (adapterFilter && !adapterFilter(adapter)) {
928
+ continue;
929
+ }
930
+ if (adapter.extensions.length > 0 && !adapter.extensions.includes(extension)) {
931
+ continue;
932
+ }
875
933
  const root = serverRoot(adapter, file, cwd);
876
- if (!containsPath(file, root)) continue;
877
- if (!matchesInclude(adapter.include ?? [], file, root, cwd)) continue;
934
+ if (!containsPath(file, root)) {
935
+ continue;
936
+ }
937
+ if (!matchesInclude(adapter.include ?? [], file, root, cwd)) {
938
+ continue;
939
+ }
878
940
  const key = root + adapter.id;
879
941
  const failedAt = state.brokenFailAt.get(key);
880
942
  if (failedAt !== undefined) {
881
- if (Date.now() - failedAt < retryCooldownMs) continue;
943
+ if (Date.now() - failedAt < retryCooldownMs) {
944
+ continue;
945
+ }
882
946
  // 冷却已过:允许重试;重试成功后下面会清除 broken 记录
883
947
  state.brokenFailAt.delete(key);
884
948
  }
@@ -890,7 +954,9 @@ export function createLspService(
890
954
  }
891
955
 
892
956
  const client = await startClient(adapter, root, cwd, config, notify);
893
- if (client) result.push(client);
957
+ if (client) {
958
+ result.push(client);
959
+ }
894
960
  }
895
961
 
896
962
  return result;
@@ -911,7 +977,9 @@ export function createLspService(
911
977
  clients.map(async (client) => {
912
978
  const after = Date.now();
913
979
  const version = await client.notify.open({ path: file });
914
- if (!diagnostics) return;
980
+ if (!diagnostics) {
981
+ return;
982
+ }
915
983
  await abortable(
916
984
  client.waitForDiagnostics({
917
985
  path: file,
@@ -948,7 +1016,9 @@ export function createLspService(
948
1016
  cwd: string,
949
1017
  options?: LspRequestOptions,
950
1018
  ): Promise<DiagnosticReport> {
951
- if (options?.signal?.aborted) return EMPTY_DIAGNOSTIC_REPORT;
1019
+ if (options?.signal?.aborted) {
1020
+ return EMPTY_DIAGNOSTIC_REPORT;
1021
+ }
952
1022
  await touchFile(file, cwd, "document", options);
953
1023
  const all = await diagnostics();
954
1024
  const normalized = normalize(file);
@@ -1107,7 +1177,9 @@ export function createLspService(
1107
1177
  running: readonly { serverID: string; root: string }[],
1108
1178
  ): Promise<string[]> {
1109
1179
  const cwd = state.cwd;
1110
- if (!cwd) return [];
1180
+ if (!cwd) {
1181
+ return [];
1182
+ }
1111
1183
  let config: ResolvedLspConfig;
1112
1184
  try {
1113
1185
  config = await currentConfig(cwd);
@@ -1125,7 +1197,9 @@ export function createLspService(
1125
1197
  const restarted = await Promise.all(
1126
1198
  [...unique.values()].map(async ({ serverID, root }): Promise<string | undefined> => {
1127
1199
  const adapter = active.find((candidate) => candidate.id === serverID);
1128
- if (!adapter) return;
1200
+ if (!adapter) {
1201
+ return;
1202
+ }
1129
1203
  const client = await startClient(adapter, root, cwd, config);
1130
1204
  return client ? serverID : undefined;
1131
1205
  }),
@@ -1144,9 +1218,13 @@ export function createLspService(
1144
1218
  // 个别进程退出失败不阻止清理流程
1145
1219
  });
1146
1220
  state.clients = state.clients.filter((client) => client.serverID !== serverID);
1147
- for (const client of targets) state.clientExtensions.delete(client);
1221
+ for (const client of targets) {
1222
+ state.clientExtensions.delete(client);
1223
+ }
1148
1224
  for (const [key, server] of state.servers) {
1149
- if (server.serverID !== serverID) continue;
1225
+ if (server.serverID !== serverID) {
1226
+ continue;
1227
+ }
1150
1228
  state.brokenFailAt.delete(key);
1151
1229
  state.brokenNotifiedAt.delete(key);
1152
1230
  state.servers.delete(key);
@@ -1155,9 +1233,13 @@ export function createLspService(
1155
1233
  state.disabled = false;
1156
1234
  // reload 是资源耗尽后的重试入口:清掉停用记录并按新配置重算监听范围
1157
1235
  watcherFailed.clear();
1158
- if (state.cwd) await reconcileWatchers(state.cwd);
1236
+ if (state.cwd) {
1237
+ await reconcileWatchers(state.cwd);
1238
+ }
1159
1239
  updateStatusText();
1160
- if (running.length === 0) return [];
1240
+ if (running.length === 0) {
1241
+ return [];
1242
+ }
1161
1243
  return respawnRunning(running);
1162
1244
  }
1163
1245
 
@@ -1180,7 +1262,9 @@ export function createLspService(
1180
1262
  state.closing = false;
1181
1263
  state.disabled = false;
1182
1264
  watcherFailed.clear();
1183
- if (state.cwd) await reconcileWatchers(state.cwd);
1265
+ if (state.cwd) {
1266
+ await reconcileWatchers(state.cwd);
1267
+ }
1184
1268
  updateStatusText();
1185
1269
  return respawnRunning(running);
1186
1270
  }
@@ -1247,8 +1331,12 @@ function getNoopService(): LspService {
1247
1331
  function enabledServerCount(config: ResolvedLspConfig, adapters?: LspServerAdapter[]): number {
1248
1332
  const ids = adapters ? adapters.map((adapter) => adapter.id) : Object.keys(config.servers);
1249
1333
  return ids.filter((id) => {
1250
- if (config.enabled && !config.enabled.has(id)) return false;
1251
- if (config.disabled?.has(id)) return false;
1334
+ if (config.enabled && !config.enabled.has(id)) {
1335
+ return false;
1336
+ }
1337
+ if (config.disabled?.has(id)) {
1338
+ return false;
1339
+ }
1252
1340
  return true;
1253
1341
  }).length;
1254
1342
  }
@@ -1286,7 +1374,9 @@ export function createLspManager(
1286
1374
  }
1287
1375
  });
1288
1376
  validateConfig(config, options?.adapters);
1289
- if (enabledServerCount(config, options?.adapters) === 0) return;
1377
+ if (enabledServerCount(config, options?.adapters) === 0) {
1378
+ return;
1379
+ }
1290
1380
  // 下面两个闭包被 service 长期持有,可能在会话被替换或 reload 后仍触发
1291
1381
  // (session_shutdown 清理、后台 watcher 错误、in-flight spawn 失败),
1292
1382
  // 而旧 ctx 已被 pi 标记 stale,ctx.ui getter 会直接抛错。UI 写入是尽力
@@ -1317,7 +1407,9 @@ export function createLspManager(
1317
1407
  hooks.onEnabled(pi, next);
1318
1408
  } catch (error) {
1319
1409
  service = undefined;
1320
- if (error instanceof Error) ctx.ui.notify(error.message, "error");
1410
+ if (error instanceof Error) {
1411
+ ctx.ui.notify(error.message, "error");
1412
+ }
1321
1413
  }
1322
1414
  });
1323
1415
 
@@ -161,8 +161,11 @@ export function registerLspRenameTool(
161
161
  for (const { candidate, result: probe } of successes) {
162
162
  const key = canonicalizeEdit(probe.edit);
163
163
  const group = groups.get(key);
164
- if (group) group.candidates.push(candidate);
165
- else groups.set(key, { result: probe, candidates: [candidate] });
164
+ if (group) {
165
+ group.candidates.push(candidate);
166
+ } else {
167
+ groups.set(key, { result: probe, candidates: [candidate] });
168
+ }
166
169
  }
167
170
  if (groups.size > 1) {
168
171
  const listing = [...groups.values()]
@@ -180,7 +183,9 @@ export function registerLspRenameTool(
180
183
  );
181
184
  }
182
185
  const firstGroup = [...groups.values()].at(0);
183
- if (!firstGroup) throw new Error("LSP rename returned no target");
186
+ if (!firstGroup) {
187
+ throw new Error("LSP rename returned no target");
188
+ }
184
189
  const { result } = firstGroup;
185
190
 
186
191
  // ── 内存展开 → 审批 → 写盘 → 诊断 ────────────────────────────────────
@@ -218,7 +223,9 @@ export function registerLspRenameTool(
218
223
  notify,
219
224
  signal,
220
225
  });
221
- if (diagnostics.text !== "") diagnosticText += `${diagnostics.text}\n`;
226
+ if (diagnostics.text !== "") {
227
+ diagnosticText += `${diagnostics.text}\n`;
228
+ }
222
229
  }
223
230
 
224
231
  const renamed =
@@ -55,7 +55,9 @@ function toPath(uri: string): string {
55
55
  function lineStartOffsets(text: string): number[] {
56
56
  const starts = [0];
57
57
  for (let i = 0; i < text.length; i++) {
58
- if (text.codePointAt(i) === 10) starts.push(i + 1);
58
+ if (text.codePointAt(i) === 10) {
59
+ starts.push(i + 1);
60
+ }
59
61
  }
60
62
  return starts;
61
63
  }
@@ -64,7 +66,9 @@ function lineStartOffsets(text: string): number[] {
64
66
  function lineContentLength(text: string, starts: readonly number[], line: number): number {
65
67
  const base = starts[line];
66
68
  let end = line + 1 < starts.length ? starts[line + 1] - 1 : text.length;
67
- if (end > base && text.codePointAt(end - 1) === 13) end--;
69
+ if (end > base && text.codePointAt(end - 1) === 13) {
70
+ end--;
71
+ }
68
72
  return end - base;
69
73
  }
70
74
 
@@ -117,8 +121,11 @@ function collectTextEdits(edit: WorkspaceEdit): Map<string, RangeLike[]> {
117
121
  const byPath = new Map<string, RangeLike[]>();
118
122
  const push = (path: string, edits: readonly RangeLike[]): void => {
119
123
  const existing = byPath.get(path);
120
- if (existing) existing.push(...edits);
121
- else byPath.set(path, [...edits]);
124
+ if (existing) {
125
+ existing.push(...edits);
126
+ } else {
127
+ byPath.set(path, [...edits]);
128
+ }
122
129
  };
123
130
  for (const [uri, edits] of Object.entries(edit.changes ?? {})) {
124
131
  push(toPath(uri), edits.filter(isRangeLike));
@@ -190,8 +197,14 @@ const WORD_PATTERN = /[\p{L}\p{N}_$]+/gu;
190
197
 
191
198
  /** 两个路径集合是否一致(用于判断 references 结果是否收敛)。 */
192
199
  export function samePathSet(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
193
- if (a.size !== b.size) return false;
194
- for (const path of a) if (!b.has(path)) return false;
200
+ if (a.size !== b.size) {
201
+ return false;
202
+ }
203
+ for (const path of a) {
204
+ if (!b.has(path)) {
205
+ return false;
206
+ }
207
+ }
195
208
  return true;
196
209
  }
197
210
 
@@ -244,7 +257,9 @@ export function symbolCandidates(
244
257
  character?: number,
245
258
  ): LspPosition[] {
246
259
  const starts = lineStartOffsets(text);
247
- if (line < 0 || line >= starts.length) return [];
260
+ if (line < 0 || line >= starts.length) {
261
+ return [];
262
+ }
248
263
  const base = starts[line];
249
264
  const length = lineContentLength(text, starts, line);
250
265
  const matches = [...text.slice(base, base + length).matchAll(WORD_PATTERN)];
@@ -264,7 +279,9 @@ export function symbolCandidates(
264
279
  }
265
280
  const candidates: LspPosition[] = [];
266
281
  for (const match of matches) {
267
- if (match[0] === symbol) candidates.push({ line, character: match.index });
282
+ if (match[0] === symbol) {
283
+ candidates.push({ line, character: match.index });
284
+ }
268
285
  }
269
286
  return candidates;
270
287
  }