@mrclrchtr/supi-code-intelligence 4.10.0 → 5.0.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 (203) hide show
  1. package/README.md +2 -1
  2. package/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  3. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  5. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  6. package/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  7. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  8. package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  9. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  10. package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  11. package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  12. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
  13. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  14. package/node_modules/@mrclrchtr/supi-lsp/README.md +80 -5
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  23. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  24. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
  26. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  27. package/node_modules/@mrclrchtr/supi-lsp/package.json +7 -6
  28. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +10 -0
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +206 -0
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +78 -0
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +114 -0
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +372 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +14 -0
  34. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +62 -0
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +491 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +9 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +71 -11
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +206 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +365 -297
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +52 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +185 -0
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-file-state.ts +25 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +467 -115
  44. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +161 -33
  45. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +10 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
  47. package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +38 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +79 -9
  50. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +13 -4
  51. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +300 -24
  52. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
  53. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +464 -96
  54. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-refactor-provider.ts +130 -0
  55. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +45 -329
  56. package/node_modules/@mrclrchtr/supi-lsp/src/provider/refactor-planning.ts +57 -120
  57. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-edit-normalizer.ts +320 -0
  58. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +207 -0
  59. package/node_modules/@mrclrchtr/supi-lsp/src/session/readiness.ts +72 -2
  60. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +151 -26
  61. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +40 -0
  62. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +50 -0
  63. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +136 -120
  64. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +49 -0
  65. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +102 -0
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +17 -7
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  73. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/LICENSE +21 -0
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/README.md +258 -0
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/babel.cjs +257 -0
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/jiti.cjs +1 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-cli.mjs +34 -0
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-hooks.mjs +124 -0
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-native.mjs +121 -0
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.d.mts +1 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.mjs +4 -0
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-static.mjs +23 -0
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.cjs +30 -0
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.cts +8 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.mts +8 -0
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.mjs +29 -0
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/types.d.ts +420 -0
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/package.json +146 -0
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +6 -3
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +0 -9
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -8
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +16 -12
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +65 -87
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +47 -95
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-timing.ts +147 -0
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client-lifecycle.ts +39 -0
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +561 -0
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-message-size.ts +24 -0
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +452 -0
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +4 -2
  107. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +12 -4
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +4 -2
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/imports.ts +4 -2
  110. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +5 -3
  111. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +33 -12
  112. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/blocking-test-bootstrap.mjs +34 -0
  113. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/bootstrap.mjs +8 -0
  114. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store-helpers.ts +23 -0
  115. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +400 -0
  116. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/request-control.ts +48 -0
  117. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-parser-helpers.ts +24 -0
  118. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-query-helpers.ts +43 -0
  119. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +421 -0
  120. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +116 -0
  121. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +201 -0
  122. package/package.json +5 -5
  123. package/src/analysis/health/diagnostics.ts +236 -24
  124. package/src/analysis/health/recovery.ts +6 -2
  125. package/src/analysis/provider.ts +58 -0
  126. package/src/analysis/readiness.ts +30 -9
  127. package/src/analysis/refactor/apply.ts +45 -1
  128. package/src/analysis/refactor/mutation-authority.ts +143 -0
  129. package/src/analysis/relations/callees.ts +9 -1
  130. package/src/analysis/relations/types.ts +5 -2
  131. package/src/analysis/search/ast-scan-timing.ts +9 -2
  132. package/src/analysis/search/ast-scan.ts +1 -1
  133. package/src/analysis/search/deadline.ts +2 -2
  134. package/src/analysis/search/pattern-analysis.ts +46 -32
  135. package/src/analysis/search/pattern.ts +12 -2
  136. package/src/analysis/target/anchored.ts +20 -11
  137. package/src/analysis/target/file.ts +18 -11
  138. package/src/analysis/target/identity.ts +19 -3
  139. package/src/analysis/target/symbol.ts +36 -15
  140. package/src/api.ts +4 -0
  141. package/src/diagnostics/evidence.ts +51 -0
  142. package/src/session/capability-adapter.ts +12 -3
  143. package/src/session/find-workflow.ts +17 -7
  144. package/src/session/graph/collect.ts +18 -2
  145. package/src/session/graph-workflow.ts +19 -12
  146. package/src/session/health-refresh.ts +148 -0
  147. package/src/session/health-types.ts +37 -15
  148. package/src/session/health-workflow.ts +37 -39
  149. package/src/session/inspect/collect.ts +20 -3
  150. package/src/session/inspect-workflow.ts +10 -5
  151. package/src/session/orientation/collect.ts +10 -1
  152. package/src/session/orientation/context-facts.ts +3 -1
  153. package/src/session/orientation/context-sections.ts +20 -7
  154. package/src/session/orientation/gather.ts +17 -11
  155. package/src/session/orientation-types.ts +2 -1
  156. package/src/session/orientation-workflow.ts +15 -8
  157. package/src/session/refactor-plans.ts +2 -0
  158. package/src/session/refactor-workflow.ts +59 -14
  159. package/src/session/session.ts +4 -1
  160. package/src/session/target-workflow.ts +54 -23
  161. package/src/session/workflow-control.ts +7 -3
  162. package/src/substrate/lsp/lifecycle.ts +26 -46
  163. package/src/substrate/lsp/maintenance.ts +199 -25
  164. package/src/substrate/lsp/state.ts +2 -2
  165. package/src/substrate/workspace-provider-host.ts +25 -3
  166. package/src/tool/find/execute.ts +7 -1
  167. package/src/tool/find/render.ts +20 -3
  168. package/src/tool/find/tui.ts +23 -21
  169. package/src/tool/graph/execute.ts +23 -3
  170. package/src/tool/graph/tui.ts +59 -46
  171. package/src/tool/health/execute.ts +7 -2
  172. package/src/tool/health/markdown.ts +56 -14
  173. package/src/tool/health/refresh-status.ts +105 -0
  174. package/src/tool/health/tui.ts +95 -98
  175. package/src/tool/infra/workflow-control.ts +8 -0
  176. package/src/tool/inspect/execute.ts +7 -1
  177. package/src/tool/inspect/tui.ts +26 -18
  178. package/src/tool/orientation/execute.ts +21 -3
  179. package/src/tool/orientation/tui.ts +51 -35
  180. package/src/tool/refactor-apply/execute.ts +3 -0
  181. package/src/tool/refactor-apply/tui.ts +43 -25
  182. package/src/tool/refactor-plan/execute.ts +4 -0
  183. package/src/tool/refactor-plan/tui.ts +33 -20
  184. package/src/tool/register.ts +127 -22
  185. package/src/tool/resolve/execute.ts +18 -1
  186. package/src/tool/resolve/tui.ts +42 -24
  187. package/src/tool/result/display.ts +107 -0
  188. package/src/tool/result/errors.ts +20 -17
  189. package/src/tool/result/find.ts +52 -8
  190. package/src/tool/result/graph.ts +44 -1
  191. package/src/tool/result/health.ts +53 -1
  192. package/src/tool/result/inspect.ts +113 -1
  193. package/src/tool/result/orientation.ts +66 -14
  194. package/src/tool/result/refactor.ts +36 -4
  195. package/src/tool/result/resolve.ts +44 -7
  196. package/src/tool/result/types.ts +20 -0
  197. package/src/types/execution.ts +18 -5
  198. package/src/types/index.ts +8 -1
  199. package/src/ui/status-command.ts +5 -3
  200. package/src/ui/status-overlay.ts +4 -0
  201. package/src/ui/tui/common.ts +108 -10
  202. package/src/ui/tui/display.ts +48 -0
  203. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +0 -351
@@ -4,66 +4,49 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
4
4
  import { acquireWorkspaceProviderHost } from "../workspace-provider-host.ts";
5
5
  import { LSP_STATE_CHANGE_EVENT, type LspAdapterState } from "./state.ts";
6
6
 
7
- const STATE_CHANGE_EVENT = new Event(LSP_STATE_CHANGE_EVENT);
8
-
9
- /** Poll interval (ms) for detecting server readiness transitions in the footer. */
10
- const READINESS_POLL_MS = 2_000;
11
-
12
7
  /** Acquire providers once per workspace and release them after the final Pi session ends. */
13
8
  export function registerLspSessionLifecycle(
14
9
  pi: ExtensionAPI,
15
10
  state: LspAdapterState,
16
11
  onStarted?: (ctx: ExtensionContext) => void | Promise<void>,
17
12
  ): void {
18
- let readinessTimer: ReturnType<typeof setInterval> | null = null;
19
- let lastFingerprint = "";
13
+ let disposeLifecycle: (() => void) | null = null;
14
+ let subscriptionGeneration = 0;
20
15
 
21
- function fingerprint(): string {
22
- const servers = state.controller?.workspaceRuntime?.getProjectServers();
23
- if (!servers) return "";
24
- let ready = 0;
25
- let starting = 0;
26
- let error = 0;
27
- let unavailable = 0;
28
- let openFiles = 0;
29
- for (const s of servers) {
30
- openFiles += s.openFiles.length;
31
- if (s.status === "running" && s.ready) ready++;
32
- else if (s.status === "running") starting++;
33
- else if (s.status === "error") error++;
34
- else unavailable++;
35
- }
36
- return `${ready}:${starting}:${error}:${unavailable}:${openFiles}`;
16
+ function dispatchStateChange(): void {
17
+ state.stateChanges.dispatchEvent(new Event(LSP_STATE_CHANGE_EVENT));
37
18
  }
38
19
 
39
- function startReadinessPolling(): void {
40
- stopReadinessPolling();
41
- lastFingerprint = fingerprint();
42
- readinessTimer = setInterval(() => {
43
- if (state.controller?.kind !== "ready") return;
44
- const current = fingerprint();
45
- if (current === lastFingerprint) return;
46
- lastFingerprint = current;
47
- state.stateChanges.dispatchEvent(STATE_CHANGE_EVENT);
48
- }, READINESS_POLL_MS);
49
- readinessTimer.unref?.();
20
+ function stopLifecycleSubscription(): void {
21
+ subscriptionGeneration++;
22
+ disposeLifecycle?.();
23
+ disposeLifecycle = null;
50
24
  }
51
25
 
52
- function stopReadinessPolling(): void {
53
- if (readinessTimer) {
54
- clearInterval(readinessTimer);
55
- readinessTimer = null;
26
+ function startLifecycleSubscription(): void {
27
+ stopLifecycleSubscription();
28
+ const controller = state.controller;
29
+ if (!controller) {
30
+ dispatchStateChange();
31
+ return;
56
32
  }
57
- lastFingerprint = "";
33
+ const generation = subscriptionGeneration;
34
+ let receivedInitialTransition = false;
35
+ disposeLifecycle = controller.subscribeLifecycle(() => {
36
+ if (generation !== subscriptionGeneration) return;
37
+ receivedInitialTransition = true;
38
+ dispatchStateChange();
39
+ });
40
+ if (!receivedInitialTransition) dispatchStateChange();
58
41
  }
59
42
 
60
43
  pi.on("session_start", async (_event, ctx: ExtensionContext) => {
44
+ stopLifecycleSubscription();
61
45
  await state.providerLease?.release();
62
46
  state.providerLease = null;
63
47
  state.controller = null;
64
48
  state.lspActive = false;
65
49
  state.sentinelSnapshot = new Map();
66
- stopReadinessPolling();
67
50
 
68
51
  const lease = await acquireWorkspaceProviderHost(ctx.cwd, {
69
52
  projectTrusted: ctx.isProjectTrusted(),
@@ -73,19 +56,16 @@ export function registerLspSessionLifecycle(
73
56
  state.lspActive = lease.lspController?.kind === "ready";
74
57
  state.sentinelSnapshot = lease.sentinelSnapshot;
75
58
  await onStarted?.(ctx);
76
-
77
- // Dispatch initial server list so the footer shows starting servers immediately.
78
- state.stateChanges.dispatchEvent(STATE_CHANGE_EVENT);
79
- // Poll for readiness transitions (running → ready, errors, etc.).
80
- startReadinessPolling();
59
+ startLifecycleSubscription();
81
60
  });
82
61
 
83
62
  pi.on("session_shutdown", async () => {
84
- stopReadinessPolling();
63
+ stopLifecycleSubscription();
85
64
  await state.providerLease?.release();
86
65
  state.providerLease = null;
87
66
  state.controller = null;
88
67
  state.lspActive = false;
89
68
  state.sentinelSnapshot = new Map();
69
+ dispatchStateChange();
90
70
  });
91
71
  }
@@ -6,70 +6,244 @@
6
6
  * semantic tool queries.
7
7
  */
8
8
 
9
+ import { statSync } from "node:fs";
9
10
  import * as nodePath from "node:path";
10
- import type { WorkspaceLspRuntime } from "@mrclrchtr/supi-lsp/api";
11
+ import {
12
+ type CodeRequestControl,
13
+ isCodeRequestInterruption,
14
+ throwIfCodeRequestInterrupted,
15
+ } from "@mrclrchtr/supi-code-runtime/api";
16
+ import type { DiagnosticEvidenceSummary } from "@mrclrchtr/supi-lsp/api";
11
17
  import {
12
18
  clearTsconfigCache,
13
19
  isLikelyStaleDiagnostic,
20
+ raceRequestControl,
14
21
  syncWorkspaceSentinelSnapshot,
22
+ type WorkspaceLspRuntime,
15
23
  } from "@mrclrchtr/supi-lsp/api";
24
+ import { mergeDiagnosticEvidence } from "../../diagnostics/evidence.ts";
16
25
 
17
26
  /**
18
- * Run sentinel-sync, stale-module resync, prune, and diagnostic refresh
19
- * as a single maintenance pass. Call before explicit diagnostic queries
20
- * (code_health with refresh:true) or semantic tool readiness checks.
27
+ * Run sentinel-sync, diagnostic refresh, and stale-module resync as a single
28
+ * maintenance pass. Call before explicit diagnostic queries (code_health with
29
+ * refresh:true) or semantic tool readiness checks.
21
30
  */
22
31
  export async function refreshLspMaintenance(
23
32
  runtime: WorkspaceLspRuntime,
24
33
  cwd: string,
25
34
  sentinelSnapshot: Map<string, number>,
26
- ): Promise<Map<string, number>> {
27
- // Sentinel refresh: detect lockfile/tsconfig/d.ts changes from outside
28
- // write/edit/code_refactor_apply.
29
- const { snapshot, changes } = syncWorkspaceSentinelSnapshot(cwd, sentinelSnapshot);
35
+ control?: CodeRequestControl,
36
+ ): Promise<WorkspaceLspMaintenanceResult> {
37
+ const { snapshot } = synchronizeSentinels(runtime, cwd, sentinelSnapshot);
38
+ let diagnosticEvidence = emptyEvidence();
39
+ let failureReason: string | undefined;
30
40
 
31
- if (changes.length > 0) {
32
- clearTsconfigCache();
33
- runtime.noteWorkspaceChanges(changes);
41
+ // Refresh before stale-module inspection. The refresh owns deleted-file
42
+ // classification; later snapshots must not erase its removed evidence.
43
+ try {
44
+ diagnosticEvidence = await runtime.refreshOpenDiagnostics(undefined, control);
45
+ } catch (error) {
46
+ if (isCodeRequestInterruption(error, control)) throw error;
47
+ failureReason = errorMessage(error);
48
+ diagnosticEvidence = readRuntimeEvidence(runtime);
49
+ }
50
+
51
+ try {
52
+ const staleResult = await resyncStaleModuleFiles(runtime, cwd, control);
53
+ diagnosticEvidence = mergeDiagnosticEvidence(diagnosticEvidence, staleResult.evidence, cwd);
54
+ if (staleResult.completed) failureReason = undefined;
55
+ } catch (error) {
56
+ if (isCodeRequestInterruption(error, control)) throw error;
57
+ // Keep evidence from the first refresh when stale inspection fails.
34
58
  }
35
59
 
36
- // Stale-module resync: force-reopen files with "Cannot find module" errors
37
- await resyncStaleModuleFiles(runtime, cwd);
60
+ return {
61
+ snapshot,
62
+ diagnosticEvidence,
63
+ ...(failureReason ? { failureReason } : {}),
64
+ };
65
+ }
66
+
67
+ /** Sentinel state and diagnostic evidence retained from one workspace pass. */
68
+ export interface WorkspaceLspMaintenanceResult {
69
+ /** The next sentinel snapshot for the session-owned maintenance state. */
70
+ snapshot: Map<string, number>;
71
+ /** Evidence from every diagnostic refresh performed by this pass. */
72
+ diagnosticEvidence: DiagnosticEvidenceSummary;
73
+ /** Failure from the first workspace refresh, when no fresh pass completed. */
74
+ failureReason?: string;
75
+ }
76
+
77
+ /** Sentinel state and stale-file facts from one file-scoped pass. */
78
+ export interface FileLspMaintenanceResult {
79
+ /** The next sentinel snapshot for the session-owned maintenance state. */
80
+ snapshot: Map<string, number>;
81
+ /** Number of stale files that matched the requested file. */
82
+ matchedStaleFileCount: number;
83
+ }
84
+
85
+ /** Refresh sentinel and stale-module state for one exact file without a workspace resync. */
86
+ export async function refreshFileLspMaintenance(options: {
87
+ runtime: WorkspaceLspRuntime;
88
+ cwd: string;
89
+ sentinelSnapshot: Map<string, number>;
90
+ filePath: string;
91
+ control?: CodeRequestControl;
92
+ }): Promise<FileLspMaintenanceResult> {
93
+ const { runtime, cwd, sentinelSnapshot, filePath, control } = options;
94
+ // A cancelled caller stops before any maintenance work starts.
95
+ throwIfCodeRequestInterrupted(control);
96
+ const { snapshot } = synchronizeSentinels(runtime, cwd, sentinelSnapshot);
97
+ const target = nodePath.resolve(filePath);
98
+ const stale = runtime
99
+ .getOutstandingDiagnostics(1)
100
+ .entries.some(
101
+ (entry) =>
102
+ nodePath.resolve(cwd, entry.file) === target &&
103
+ entry.diagnostics.some((diagnostic) => isLikelyStaleDiagnostic(diagnostic)),
104
+ );
38
105
 
39
- // Two-pass prune/refresh for diagnostics
40
106
  runtime.pruneMissingFiles();
41
- try {
42
- await runtime.refreshOpenDiagnostics();
43
- } catch {
44
- /* best-effort */
107
+ if (stale) {
108
+ runtime.closeFile(target);
109
+ // Race the caller's cancellation so a client start during re-open does
110
+ // not outlive the caller; the underlying runtime keeps its own lifecycle.
111
+ await raceRequestControl(runtime.trackFile(target), control);
45
112
  }
46
113
  runtime.pruneMissingFiles();
114
+ throwIfCodeRequestInterrupted(control);
47
115
 
48
- return snapshot;
116
+ return { snapshot, matchedStaleFileCount: stale ? 1 : 0 };
117
+ }
118
+
119
+ function synchronizeSentinels(
120
+ runtime: WorkspaceLspRuntime,
121
+ cwd: string,
122
+ sentinelSnapshot: Map<string, number>,
123
+ ) {
124
+ const state = syncWorkspaceSentinelSnapshot(cwd, sentinelSnapshot);
125
+ if (state.changes.length > 0) {
126
+ clearTsconfigCache();
127
+ runtime.noteWorkspaceChanges(state.changes);
128
+ }
129
+ return state;
49
130
  }
50
131
 
51
132
  /** Re-open files with stale module-resolution errors. */
52
- async function resyncStaleModuleFiles(runtime: WorkspaceLspRuntime, cwd: string): Promise<void> {
133
+ async function resyncStaleModuleFiles(
134
+ runtime: WorkspaceLspRuntime,
135
+ cwd: string,
136
+ control?: CodeRequestControl,
137
+ ): Promise<{ evidence: DiagnosticEvidenceSummary; completed: boolean }> {
53
138
  const outstanding = runtime.getOutstandingDiagnostics(1);
54
139
  const staleFiles: string[] = [];
55
140
 
56
- for (const entry of outstanding) {
141
+ for (const entry of outstanding.entries) {
57
142
  if (entry.diagnostics.some((d) => isLikelyStaleDiagnostic(d))) {
58
143
  staleFiles.push(entry.file);
59
144
  }
60
145
  }
61
146
 
62
- if (staleFiles.length === 0) return;
147
+ if (staleFiles.length === 0) return { evidence: emptyEvidence(), completed: false };
63
148
 
149
+ const invalidated: Array<{
150
+ file: string;
151
+ status: "unconfirmed" | "failed" | "removed";
152
+ }> = staleFiles.map((file) => ({
153
+ file: nodePath.relative(cwd, nodePath.resolve(cwd, file)),
154
+ status: "unconfirmed",
155
+ }));
64
156
  for (const file of staleFiles) {
65
157
  const filePath = nodePath.resolve(cwd, file);
158
+ const relativeFile = nodePath.relative(cwd, filePath);
66
159
  runtime.closeFile(filePath);
67
- await runtime.trackFile(filePath);
160
+ try {
161
+ if (!(await runtime.trackFile(filePath))) {
162
+ markUnavailable(invalidated, relativeFile, filePath);
163
+ }
164
+ } catch (error) {
165
+ if (isCodeRequestInterruption(error, control)) throw error;
166
+ markUnavailable(invalidated, relativeFile, filePath);
167
+ }
168
+ }
169
+
170
+ try {
171
+ return {
172
+ evidence: mergeDiagnosticEvidence(
173
+ {
174
+ requested: invalidated.length,
175
+ confirmed: 0,
176
+ unconfirmed: invalidated.length,
177
+ failed: 0,
178
+ removed: 0,
179
+ documents: invalidated,
180
+ },
181
+ await runtime.refreshOpenDiagnostics({ quietMs: 300, maxWaitMs: 2000 }, control),
182
+ cwd,
183
+ ),
184
+ completed: true,
185
+ };
186
+ } catch (error) {
187
+ if (isCodeRequestInterruption(error, control)) throw error;
188
+ return {
189
+ evidence: {
190
+ requested: invalidated.length,
191
+ confirmed: 0,
192
+ unconfirmed: invalidated.filter((document) => document.status === "unconfirmed").length,
193
+ failed: invalidated.filter((document) => document.status === "failed").length,
194
+ removed: invalidated.filter((document) => document.status === "removed").length,
195
+ documents: invalidated,
196
+ },
197
+ completed: false,
198
+ };
68
199
  }
200
+ }
201
+
202
+ function markUnavailable(
203
+ documents: Array<{ file: string; status: "unconfirmed" | "failed" | "removed" }>,
204
+ file: string,
205
+ filePath: string,
206
+ ): void {
207
+ const entry = documents.find((document) => document.file === file);
208
+ if (entry) entry.status = diagnosticFailureStatus(filePath);
209
+ }
69
210
 
211
+ function diagnosticFailureStatus(filePath: string): "failed" | "removed" {
70
212
  try {
71
- await runtime.refreshOpenDiagnostics({ quietMs: 300, maxWaitMs: 2000 });
213
+ statSync(filePath);
214
+ return "failed";
215
+ } catch (error) {
216
+ if (
217
+ typeof error === "object" &&
218
+ error !== null &&
219
+ "code" in error &&
220
+ (error.code === "ENOENT" || error.code === "ENOTDIR")
221
+ ) {
222
+ return "removed";
223
+ }
224
+ return "failed";
225
+ }
226
+ }
227
+
228
+ function readRuntimeEvidence(runtime: WorkspaceLspRuntime): DiagnosticEvidenceSummary {
229
+ try {
230
+ return runtime.getWorkspaceDiagnosticSummary().evidence;
72
231
  } catch {
73
- /* best-effort */
232
+ return emptyEvidence();
74
233
  }
75
234
  }
235
+
236
+ function errorMessage(error: unknown): string {
237
+ return error instanceof Error && error.message ? error.message : "Diagnostic refresh failed.";
238
+ }
239
+
240
+ function emptyEvidence(): DiagnosticEvidenceSummary {
241
+ return {
242
+ requested: 0,
243
+ confirmed: 0,
244
+ unconfirmed: 0,
245
+ failed: 0,
246
+ removed: 0,
247
+ documents: [],
248
+ };
249
+ }
@@ -25,7 +25,7 @@ export interface LspAdapterState {
25
25
  lspActive: boolean;
26
26
  /** Snapshot of workspace sentinel files (package.json, tsconfig, lockfiles) for change detection. */
27
27
  sentinelSnapshot: Map<string, number>;
28
- /** Fired when server statuses or readiness change footer listens for re-renders. */
28
+ /** Fired for runtime lifecycle changes that require a footer render. */
29
29
  stateChanges: EventTarget;
30
30
  }
31
31
 
@@ -40,5 +40,5 @@ export function createLspAdapterState(): LspAdapterState {
40
40
  };
41
41
  }
42
42
 
43
- /** Event type dispatched on {@link LspAdapterState.stateChanges} when server statuses change. */
43
+ /** Event type dispatched on {@link LspAdapterState.stateChanges} for LSP lifecycle changes. */
44
44
  export const LSP_STATE_CHANGE_EVENT = "server-status-changed" as const;
@@ -5,9 +5,11 @@ import { LspRuntimeController, scanWorkspaceSentinels } from "@mrclrchtr/supi-ls
5
5
  import { TreeSitterRuntimeController } from "@mrclrchtr/supi-tree-sitter/api";
6
6
 
7
7
  const HOSTS = Symbol.for("supi-code-intelligence/workspace-provider-hosts");
8
+ const CLOSING_HOSTS = Symbol.for("supi-code-intelligence/closing-workspace-provider-hosts");
8
9
  const SHUTDOWN_GRACE_MS = 2_000;
9
10
 
10
11
  type HostRegistry = Map<string, WorkspaceProviderHost>;
12
+ type ClosingHostRegistry = Map<string, Promise<void>>;
11
13
 
12
14
  /** One acquired reference to a process-shared Workspace provider host. */
13
15
  export interface WorkspaceProviderHostLease {
@@ -23,6 +25,12 @@ function registry(): HostRegistry {
23
25
  return global[HOSTS];
24
26
  }
25
27
 
28
+ function closingRegistry(): ClosingHostRegistry {
29
+ const global = globalThis as typeof globalThis & { [CLOSING_HOSTS]?: ClosingHostRegistry };
30
+ global[CLOSING_HOSTS] ??= new Map();
31
+ return global[CLOSING_HOSTS];
32
+ }
33
+
26
34
  function canonicalWorkspace(cwd: string): string {
27
35
  try {
28
36
  return realpathSync(cwd);
@@ -76,8 +84,16 @@ class WorkspaceProviderHost {
76
84
  released = true;
77
85
  this.#leases--;
78
86
  if (this.#leases === 0) {
79
- registry().delete(this.cwd);
80
- await this.#shutdown();
87
+ await Promise.resolve();
88
+ if (this.#leases !== 0) return;
89
+ const shutdown = this.#shutdown();
90
+ closingRegistry().set(this.cwd, shutdown);
91
+ try {
92
+ await shutdown;
93
+ } finally {
94
+ if (closingRegistry().get(this.cwd) === shutdown) closingRegistry().delete(this.cwd);
95
+ registry().delete(this.cwd);
96
+ }
81
97
  }
82
98
  },
83
99
  };
@@ -101,7 +117,7 @@ class WorkspaceProviderHost {
101
117
  this.#closed = true;
102
118
  await Promise.all([
103
119
  ...(this.#lsp ? [settleWithin(this.#lsp.shutdown())] : []),
104
- ...(this.#tree ? [settleWithin(this.#tree.shutdown())] : []),
120
+ ...(this.#tree ? [this.#tree.shutdown()] : []),
105
121
  ]);
106
122
  this.#lsp = null;
107
123
  this.#tree = null;
@@ -114,6 +130,11 @@ export async function acquireWorkspaceProviderHost(
114
130
  options: { projectTrusted: boolean },
115
131
  ): Promise<WorkspaceProviderHostLease> {
116
132
  const workspace = canonicalWorkspace(cwd);
133
+ for (;;) {
134
+ const closing = closingRegistry().get(workspace);
135
+ if (!closing) break;
136
+ await closing.catch(() => undefined);
137
+ }
117
138
  const hosts = registry();
118
139
  let host = hosts.get(workspace);
119
140
  if (!host) {
@@ -126,4 +147,5 @@ export async function acquireWorkspaceProviderHost(
126
147
  /** Clear process-shared hosts between isolated tests. */
127
148
  export function resetWorkspaceProviderHostsForTests(): void {
128
149
  registry().clear();
150
+ closingRegistry().clear();
129
151
  }
@@ -26,12 +26,18 @@ export async function executeFindTool(
26
26
  return searchErrorResult(`**Error:** ${outcome.message}`, {
27
27
  scope: Array.isArray(params?.scope) ? params.scope.join(", ") : null,
28
28
  nextQueries: ["Fix the search input and retry"],
29
+ message: outcome.message,
29
30
  });
30
31
  }
31
32
 
32
33
  const assembly = assembleFindWorkflowResult(outcome);
33
34
  return {
34
35
  content: renderFindResult(assembly),
35
- details: { type: "search", data: assembly.details },
36
+ details: {
37
+ type: "search",
38
+ data: assembly.details,
39
+ status: "completed",
40
+ displaySections: assembly.displaySections,
41
+ },
36
42
  };
37
43
  }
@@ -38,9 +38,7 @@ function renderSemantic(assembly: FindResultAssembly, evidence: EvidenceListMeta
38
38
  const { outcome } = assembly;
39
39
  if (outcome.data.kind !== "semantic") return "";
40
40
  const symbols = outcome.data.symbols;
41
- if (symbols.length === 0) {
42
- return `**Semantic search** — \`${outcome.query}\`\n\nNo semantic results found in \`${outcome.scopeLabel}\`.`;
43
- }
41
+ if (symbols.length === 0) return renderEmptySemantic(assembly, evidence);
44
42
 
45
43
  const lines = [
46
44
  `**Semantic search** — \`${outcome.query}\` (${symbols.length} symbol${symbols.length === 1 ? "" : "s"} found)`,
@@ -55,3 +53,22 @@ function renderSemantic(assembly: FindResultAssembly, evidence: EvidenceListMeta
55
53
  if (disclosure) lines.push(disclosure);
56
54
  return lines.join("\n");
57
55
  }
56
+
57
+ function renderEmptySemantic(assembly: FindResultAssembly, evidence: EvidenceListMetadata): string {
58
+ const { outcome } = assembly;
59
+ const lines = [
60
+ `**Semantic search** — \`${outcome.query}\``,
61
+ "",
62
+ evidence.partialReason
63
+ ? `No LSP workspace-symbol results were collected in \`${outcome.scopeLabel}\`.`
64
+ : `No LSP workspace-symbol results found in \`${outcome.scopeLabel}\`.`,
65
+ ];
66
+ const disclosure = renderEvidenceListMetadataDisclosure(evidence);
67
+ if (disclosure) lines.push("", disclosure);
68
+ lines.push("", "Document-level semantic symbols can differ from the workspace index.");
69
+ const fileQuery = assembly.assembled.actions.find(
70
+ (action) => action.kind === "query" && action.instruction.startsWith("If you know the file"),
71
+ );
72
+ if (fileQuery?.kind === "query") lines.push(fileQuery.instruction);
73
+ return lines.join("\n");
74
+ }
@@ -1,39 +1,41 @@
1
- /**
2
- * TUI renderer for code_find — renderCall + renderResult.
3
- */
1
+ /** TUI renderer for code_find. */
4
2
  import type { Theme } from "@earendil-works/pi-coding-agent";
5
3
  import { Text } from "@earendil-works/pi-tui";
6
- import { type ResultOptios, renderSimpleResult, type ToolResult } from "../../ui/tui/common.ts";
4
+ import {
5
+ formatCallValue,
6
+ type ResultOptios,
7
+ renderSimpleResult,
8
+ type ToolRendererContext,
9
+ type ToolResult,
10
+ } from "../../ui/tui/common.ts";
7
11
  import type { CodeFindToolParams } from "./execute.ts";
8
12
 
9
- /** ── renderCall ────────────────────────────────────────────────── */
10
-
11
- export function renderFindCall(args: unknown, theme: Theme, _context: unknown): Text {
12
- const params = (args ?? {}) as CodeFindToolParams;
13
- const mode = params.mode ?? "mode?";
13
+ /** Render the compact code_find call header. */
14
+ export function renderFindCall(
15
+ args: unknown,
16
+ theme: Theme,
17
+ _context: ToolRendererContext | undefined,
18
+ ): Text {
19
+ const params = (args ?? {}) as Partial<CodeFindToolParams>;
20
+ const mode = formatCallValue(params.mode, 20) ?? "mode?";
14
21
 
15
22
  let content = theme.fg("toolTitle", "code_find");
16
-
17
- if (params.query) {
18
- content += ` ${theme.fg("accent", JSON.stringify(params.query))}`;
19
- }
20
-
23
+ const query = formatCallValue(params.query);
24
+ if (query) content += ` ${theme.fg("accent", JSON.stringify(query))}`;
21
25
  content += ` ${theme.fg("muted", mode)}`;
22
26
 
23
- if (params.kind) {
24
- content += theme.fg("dim", ` [${params.kind}]`);
25
- }
27
+ const kind = formatCallValue(params.kind, 30);
28
+ if (kind) content += theme.fg("dim", ` [${kind}]`);
26
29
 
27
30
  return new Text(content, 0, 0);
28
31
  }
29
32
 
30
- /** ── renderResult ──────────────────────────────────────────────── */
31
-
33
+ /** Render code_find progress, status, and structured result details. */
32
34
  export function renderFindResult(
33
35
  result: ToolResult,
34
36
  options: ResultOptios,
35
37
  theme: Theme,
36
- _context: unknown,
38
+ context: ToolRendererContext | undefined,
37
39
  ): ReturnType<typeof renderSimpleResult> {
38
- return renderSimpleResult(result, options, theme, "Searching…");
40
+ return renderSimpleResult(result, options, theme, "Searching…", context);
39
41
  }
@@ -4,6 +4,7 @@ import type { GraphWorkflowInput, RequestedGraphRelation } from "../../session/g
4
4
  import type { GraphTargetInput } from "../../session/target-input.ts";
5
5
  import type { CodeIntelResult, CodeIntelToolExecCtx } from "../../types/index.ts";
6
6
  import { toWorkflowControl } from "../infra/workflow-control.ts";
7
+ import { createToolDisplaySection } from "../result/display.ts";
7
8
  import { searchErrorResult } from "../result/errors.ts";
8
9
  import { assembleGraphResult } from "../result/graph.ts";
9
10
  import { renderGraphResult } from "./markdown.ts";
@@ -25,7 +26,7 @@ export async function executeGraphTool(
25
26
 
26
27
  if (outcome.kind === "unavailable") throw new Error(outcome.reason);
27
28
  if (outcome.kind === "invalid-input") {
28
- return searchErrorResult(`**Error:** ${outcome.message}`);
29
+ return searchErrorResult(`**Error:** ${outcome.message}`, { message: outcome.message });
29
30
  }
30
31
  if (outcome.kind === "disambiguation" || outcome.kind === "kind-mismatch") {
31
32
  const lines = [
@@ -45,7 +46,21 @@ export async function executeGraphTool(
45
46
  "Retry without `symbolKind`, use an observed provider kind, or choose a handle.",
46
47
  );
47
48
  }
48
- return searchErrorResult(lines.join("\n"));
49
+ return searchErrorResult(lines.join("\n"), {
50
+ message:
51
+ outcome.kind === "kind-mismatch"
52
+ ? `No target matched provider kind ${outcome.requestedKind}.`
53
+ : "The target is ambiguous.",
54
+ displaySections: [
55
+ createToolDisplaySection({
56
+ key: "graph.candidates",
57
+ title: "Candidates",
58
+ items: outcome.candidates,
59
+ format: (candidate) =>
60
+ `${candidate.targetId} — ${candidate.name} (${candidate.kind ?? "unknown"}) at ${candidate.file}:${candidate.line}:${candidate.character}`,
61
+ }),
62
+ ],
63
+ });
49
64
  }
50
65
 
51
66
  const assembly = assembleGraphResult({
@@ -57,6 +72,11 @@ export async function executeGraphTool(
57
72
  });
58
73
  return {
59
74
  content: renderGraphResult(assembly),
60
- details: { type: "search", data: assembly.details },
75
+ details: {
76
+ type: "search",
77
+ data: assembly.details,
78
+ status: "completed",
79
+ displaySections: assembly.displaySections,
80
+ },
61
81
  };
62
82
  }