@mrclrchtr/supi-code-intelligence 6.0.1 → 6.2.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 (147) hide show
  1. package/README.md +2 -2
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
  4. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  5. package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  6. package/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  7. package/node_modules/@mrclrchtr/supi-lsp/README.md +26 -4
  8. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  9. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-jsonrpc/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.colorProvider.d.ts +1 -1
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.d.ts +2 -2
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.fileOperations.d.ts +9 -9
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/package.json +3 -3
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/esm/main.d.ts +2 -2
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/esm/main.js +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/umd/main.d.ts +2 -2
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/umd/main.js +1 -1
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/package.json +1 -1
  23. package/node_modules/@mrclrchtr/supi-lsp/package.json +4 -5
  24. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +14 -7
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +1 -1
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +0 -5
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +6 -3
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +2 -1
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +5 -2
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +2 -1
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +23 -5
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +40 -18
  33. package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +164 -30
  34. package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +9 -87
  35. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +8 -1
  36. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +4 -5
  37. package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +1 -29
  38. package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +7 -21
  39. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-severity.ts +6 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +11 -14
  41. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +0 -5
  42. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +0 -1
  43. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +29 -49
  44. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +1 -1
  45. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -8
  46. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +1 -9
  47. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +12 -6
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +9 -1
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +26 -41
  50. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +849 -117
  51. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +6 -4
  52. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +59 -28
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +27 -19
  54. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +10 -0
  55. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +26 -0
  56. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registration.ts +0 -17
  57. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +118 -27
  58. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +7 -2
  59. package/node_modules/@mrclrchtr/supi-lsp/src/session/scanner.ts +24 -21
  60. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +33 -5
  61. package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +19 -40
  62. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +13 -19
  63. package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +287 -0
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +6 -18
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -5
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
  73. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -11
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +19 -0
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +5 -23
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +3 -21
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +5 -9
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +10 -20
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +7 -1
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +9 -5
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +134 -9
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-name.ts +96 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +11 -12
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +39 -56
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +9 -7
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/js-binding-pattern.ts +26 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +2 -21
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +144 -12
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +50 -37
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/scope.ts +138 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +0 -7
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +1 -14
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +0 -10
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +6 -8
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +34 -10
  96. package/package.json +5 -5
  97. package/src/analysis/capability/capability-warnings.ts +4 -22
  98. package/src/analysis/health/recovery.ts +4 -0
  99. package/src/analysis/health/server-status.ts +64 -0
  100. package/src/analysis/health/signals.ts +1 -0
  101. package/src/analysis/provider.ts +2 -1
  102. package/src/analysis/search/ast-scan-timing.ts +4 -2
  103. package/src/analysis/search/ast-scan.ts +1 -1
  104. package/src/analysis/target/symbol.ts +28 -16
  105. package/src/analysis/target/types.ts +1 -0
  106. package/src/api.ts +1 -0
  107. package/src/extension.ts +4 -2
  108. package/src/session/find-workflow.ts +5 -12
  109. package/src/session/health-refresh.ts +30 -4
  110. package/src/session/health-types.ts +27 -2
  111. package/src/session/health-workflow.ts +5 -1
  112. package/src/session/input/health-refactor.ts +16 -14
  113. package/src/session/refactor-types.ts +14 -1
  114. package/src/session/refactor-workflow.ts +21 -16
  115. package/src/session/semantic-demand.ts +36 -0
  116. package/src/session/target-workflow.ts +9 -12
  117. package/src/substrate/lsp/lifecycle.ts +0 -3
  118. package/src/substrate/lsp/maintenance.ts +4 -5
  119. package/src/substrate/lsp/settings.ts +6 -9
  120. package/src/substrate/lsp/state.ts +0 -2
  121. package/src/substrate/workspace-provider-host.ts +2 -2
  122. package/src/tool/code_health/file-scope-markdown.ts +43 -0
  123. package/src/tool/code_health/guidance.ts +2 -2
  124. package/src/tool/code_health/markdown.ts +81 -69
  125. package/src/tool/code_health/refresh-outcome.ts +46 -0
  126. package/src/tool/code_health/refresh-status.ts +166 -23
  127. package/src/tool/code_health/result.ts +15 -5
  128. package/src/tool/code_health/spec.ts +2 -1
  129. package/src/tool/code_health/tui.ts +42 -8
  130. package/src/tool/code_refactor_apply/guidance.ts +3 -2
  131. package/src/tool/code_refactor_plan/guidance.ts +1 -1
  132. package/src/tool/register.ts +5 -2
  133. package/src/tool/result/errors.ts +1 -0
  134. package/src/tool/result/types.ts +6 -0
  135. package/src/tool/schemas.ts +21 -11
  136. package/src/ui/footer.ts +7 -5
  137. package/src/ui/status-command.ts +31 -0
  138. package/src/ui/status-overlay.ts +69 -23
  139. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-actions.ts +0 -59
  140. package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +0 -24
  141. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +0 -132
  142. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +0 -158
  143. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +0 -167
  144. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -42
  145. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-registration.ts +0 -37
  146. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +0 -30
  147. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/structure.ts +0 -8
@@ -150,7 +150,7 @@ export async function enumerateAstFiles(
150
150
  class AstFileEnumerator {
151
151
  readonly #operations: AstScanOperations;
152
152
  readonly #now: () => number;
153
- readonly #grammarExtensions = new Set(getSupportedExtensions());
153
+ readonly #grammarExtensions: ReadonlySet<string> = new Set(getSupportedExtensions());
154
154
  readonly #operationExtensions: ReadonlySet<string>;
155
155
  readonly #files = new Set<string>();
156
156
  readonly #visitedDirectories = new Set<string>();
@@ -8,6 +8,7 @@
8
8
 
9
9
  import * as path from "node:path";
10
10
  import {
11
+ type CodeQueryResult,
11
12
  type CodeRequestControl,
12
13
  type CodeSymbol,
13
14
  isCodeRequestInterruption,
@@ -114,33 +115,27 @@ export async function resolveSymbolTarget(
114
115
  control?: CodeRequestControl;
115
116
  },
116
117
  ): Promise<TargetOutcome> {
117
- const result = await semantic.workspaceSymbols(symbol);
118
+ const scopePath = options?.path ? normalizePath(options.path, cwd) : null;
119
+ const result = await semantic.workspaceSymbols(
120
+ symbol,
121
+ options?.control,
122
+ scopePath ? [scopePath] : undefined,
123
+ );
118
124
  if (result.kind === "unavailable") {
119
125
  return {
120
- kind: "error",
121
- message: `Symbol discovery for \`${symbol}\` is unavailable: ${result.reason}`,
122
- };
123
- }
124
- if (result.data.length === 0) {
125
- return {
126
- kind: "error",
127
- message: `Symbol not found: \`${symbol}\`. Try \`code_resolve\` to search for it, or use an anchor target (file/line/character) if you know the location.`,
126
+ kind: "unavailable",
127
+ reason: `Symbol discovery for \`${symbol}\` is unavailable: ${result.reason}`,
128
128
  };
129
129
  }
130
+ if (result.data.length === 0) return emptySymbolOutcome(symbol, result, options?.path);
130
131
 
131
- const scopePath = options?.path ? normalizePath(options.path, cwd) : null;
132
132
  const scoped = result.data.filter((candidate) =>
133
133
  scopePath ? isWithinOrEqual(scopePath, candidate.file) : true,
134
134
  );
135
135
  const eligible = options?.exportedOnly
136
136
  ? scoped.filter((candidate) => !NON_EXPORTED_KINDS.has(candidate.kind))
137
137
  : scoped;
138
- if (eligible.length === 0) {
139
- return {
140
- kind: "error",
141
- message: `Symbol not found: \`${symbol}\`${scopePath ? ` in path \`${options?.path}\`` : ""}. Try \`code_resolve\` to search for it, or use an anchor target (file/line/character) if you know the location.`,
142
- };
143
- }
138
+ if (eligible.length === 0) return emptySymbolOutcome(symbol, result, options?.path);
144
139
 
145
140
  const requestedKind = options?.kind;
146
141
  if (requestedKind) {
@@ -248,6 +243,23 @@ type CandidateOutcomeOptions = CandidateOutcomeBase &
248
243
  * exact provider result count for omission disclosure. No candidate is
249
244
  * promoted when the requested provider kind did not match.
250
245
  */
246
+ function emptySymbolOutcome(
247
+ symbol: string,
248
+ result: Exclude<CodeQueryResult<CodeSymbol[]>, { kind: "unavailable" }>,
249
+ scope?: string,
250
+ ): TargetOutcome {
251
+ if (result.kind === "partial") {
252
+ return {
253
+ kind: "unavailable",
254
+ reason: `Symbol discovery for \`${symbol}\` is incomplete: ${result.reason}`,
255
+ };
256
+ }
257
+ return {
258
+ kind: "error",
259
+ message: `Symbol not found: \`${symbol}\`${scope ? ` in path \`${scope}\`` : ""}. Try \`code_resolve\` to search for it, or use an anchor target (file/line/character) if you know the location.`,
260
+ };
261
+ }
262
+
251
263
  async function buildCandidateOutcome(
252
264
  options: CandidateOutcomeOptions,
253
265
  ): Promise<Extract<TargetOutcome, { kind: "disambiguation" | "kind-mismatch" }>> {
@@ -156,4 +156,5 @@ export type TargetOutcome =
156
156
  candidates: DisambiguationCandidateData[];
157
157
  omittedCount: number;
158
158
  }
159
+ | { kind: "unavailable"; reason: string }
159
160
  | { kind: "error"; message: string };
package/src/api.ts CHANGED
@@ -48,6 +48,7 @@ export type {
48
48
  export type {
49
49
  HealthDiagnosticObservation,
50
50
  HealthDiagnosticScope,
51
+ HealthFileReadiness,
51
52
  HealthRefreshAttempt,
52
53
  HealthRefreshState,
53
54
  HealthStaleAssessment,
package/src/extension.ts CHANGED
@@ -6,8 +6,10 @@ import type {
6
6
  ExtensionAPI,
7
7
  ExtensionContext,
8
8
  } from "@earendil-works/pi-coding-agent";
9
- import { recordDebugEvent } from "@mrclrchtr/supi-core/debug";
10
- import { truncateIdentity } from "@mrclrchtr/supi-lsp/debug-telemetry";
9
+ import {
10
+ recordDebugEvent,
11
+ truncateDebugIdentity as truncateIdentity,
12
+ } from "@mrclrchtr/supi-core/debug";
11
13
  import { buildArchitectureModel } from "./analysis/architecture/discovery.ts";
12
14
  import { createCodeIntelligenceApp } from "./app/app.ts";
13
15
  import { registerCodeIntelligenceSettings, resolveOverviewEnabled } from "./config.ts";
@@ -9,6 +9,7 @@ import { isCodeFindAstKind } from "../tool/code_find/ast-kinds.ts";
9
9
  import type { CapabilityAdapter } from "./capability-adapter.ts";
10
10
  import type { FindWorkflowInput, FindWorkflowOutcome } from "./find-types.ts";
11
11
  import { parseFindWorkflowInput } from "./input/workflows.ts";
12
+ import { resolveWorkspaceSemanticDemand } from "./semantic-demand.ts";
12
13
  import { reportProgress, throwIfAborted, type WorkflowControl } from "./workflow-control.ts";
13
14
 
14
15
  export interface FindWorkflowDeps {
@@ -71,22 +72,14 @@ async function runSemanticSearch(options: {
71
72
  control?: WorkflowControl;
72
73
  }): Promise<FindWorkflowOutcome> {
73
74
  const { query, scopePaths, scopeLabel, maxResults, deps, control } = options;
74
- const readiness = await deps.capability.ensureSemanticReadiness(
75
- deps.cwd,
76
- { kind: "workspace" },
77
- control,
78
- );
79
- if (readiness.kind === "timeout") {
75
+ const semanticDemand = await resolveWorkspaceSemanticDemand(deps.capability, deps.cwd, control);
76
+ if (semanticDemand.kind === "timeout") {
80
77
  return { kind: "unavailable", reason: SEMANTIC_READINESS_TIMEOUT_REASON };
81
78
  }
82
- if (readiness.kind === "unavailable") return readiness;
79
+ if (semanticDemand.kind === "unavailable") return semanticDemand;
83
80
  throwIfAborted(control);
84
81
 
85
- const provider = deps.capability.getSemanticProvider(deps.cwd);
86
- if (!provider?.workspaceSymbols) {
87
- return { kind: "unavailable", reason: "No semantic workspace-symbol provider is active." };
88
- }
89
- const result = await provider.workspaceSymbols(query, control);
82
+ const result = await semanticDemand.provider.workspaceSymbols(query, control, scopePaths);
90
83
  // A cancellation that landed during the request must not publish symbols
91
84
  // the caller no longer awaits.
92
85
  throwIfAborted(control);
@@ -2,11 +2,19 @@ import {
2
2
  type CodeRequestControl,
3
3
  isCodeRequestInterruption,
4
4
  } from "@mrclrchtr/supi-code-runtime/api";
5
- import type { WorkspaceDiagnosticReport, WorkspaceLspRuntime } from "@mrclrchtr/supi-lsp/api";
5
+ import {
6
+ emptyProcessCrashRecoverySummary,
7
+ type WorkspaceDiagnosticReport,
8
+ type WorkspaceLspRuntime,
9
+ } from "@mrclrchtr/supi-lsp/api";
6
10
  import { recoverDiagnosticRuntime } from "../analysis/health/recovery.ts";
7
11
  import { mergeDiagnosticEvidence } from "../diagnostics/evidence.ts";
8
12
  import { refreshFileLspMaintenance, refreshLspMaintenance } from "../substrate/lsp/maintenance.ts";
9
- import type { HealthDiagnosticScope, HealthRefreshAttempt } from "./health-types.ts";
13
+ import type {
14
+ HealthDiagnosticScope,
15
+ HealthFileReadiness,
16
+ HealthRefreshAttempt,
17
+ } from "./health-types.ts";
10
18
 
11
19
  interface HealthRefreshAttemptOptions {
12
20
  readonly runtime: WorkspaceLspRuntime;
@@ -51,6 +59,8 @@ async function collectFileRefreshAttempt(
51
59
  undefined,
52
60
  options.control,
53
61
  );
62
+ const fileReadiness: HealthFileReadiness =
63
+ readiness.kind === "ready" ? "ready" : readiness.kind === "timeout" ? "pending" : "unavailable";
54
64
  return {
55
65
  attempt: {
56
66
  kind: "completed",
@@ -59,7 +69,9 @@ async function collectFileRefreshAttempt(
59
69
  requestedDiagnosticScope: scope,
60
70
  operationScope: "file-runtime",
61
71
  attemptedActiveClients: readiness.kind === "ready" ? 1 : 0,
72
+ fileReadiness,
62
73
  restartedClients: 0,
74
+ processCrashRecovery: readiness.processCrashRecovery ?? emptyProcessCrashRecoverySummary(),
63
75
  staleAssessment: {
64
76
  scope: "file",
65
77
  suspected: null,
@@ -105,9 +117,13 @@ async function collectWorkspaceRefreshAttempt(
105
117
  service: options.runtime,
106
118
  control: options.control,
107
119
  progress: options.reportRecoveryProgress,
108
- // The maintenance pass already refreshed every open document; reuse its
109
- // evidence so the recovery pass skips a redundant second refresh.
120
+ // The maintenance pass already refreshed every running route. Reuse its
121
+ // evidence unless process-crash demand restores a required route; the
122
+ // runtime then performs a fresh pass that includes the replacement.
110
123
  initialEvidence: maintenance.diagnosticEvidence,
124
+ processCrashDemand: {
125
+ ...(workspaceScope?.filter ? { scopes: [workspaceScope.filter] } : {}),
126
+ },
111
127
  });
112
128
  const diagnosticEvidence = mergeDiagnosticEvidence(
113
129
  maintenance.diagnosticEvidence,
@@ -122,7 +138,16 @@ async function collectWorkspaceRefreshAttempt(
122
138
  elapsedMs: Date.now() - options.attemptedAt,
123
139
  requestedDiagnosticScope: options.diagnosticsScope,
124
140
  operationScope: "workspace-runtime",
141
+ attemptedActiveClients: recovery.attemptedClients,
142
+ restartedClients: recovery.restartedClients,
143
+ staleAssessment: {
144
+ scope: "workspace",
145
+ suspected: recovery.staleAssessment.suspected,
146
+ matchedFileCount: recovery.staleAssessment.matchedFiles.length,
147
+ warning: recovery.staleAssessment.warning,
148
+ },
125
149
  diagnosticEvidence,
150
+ processCrashRecovery: recovery.processCrashRecovery,
126
151
  reason: recovery.refreshFailureReason,
127
152
  },
128
153
  diagnosticReport: recovery.diagnosticReport,
@@ -137,6 +162,7 @@ async function collectWorkspaceRefreshAttempt(
137
162
  operationScope: "workspace-runtime",
138
163
  attemptedActiveClients: recovery.attemptedClients,
139
164
  restartedClients: recovery.restartedClients,
165
+ processCrashRecovery: recovery.processCrashRecovery,
140
166
  diagnosticEvidence,
141
167
  staleAssessment: {
142
168
  scope: "workspace",
@@ -1,4 +1,10 @@
1
- import type { DiagnosticEvidenceSummary, FileScopeDecision } from "@mrclrchtr/supi-lsp/api";
1
+ import type {
2
+ DiagnosticEvidenceSummary,
3
+ FileScopeDecision,
4
+ ProcessCrashRecoverySummary,
5
+ ProjectServerInfo,
6
+ ProjectServerStatusReason,
7
+ } from "@mrclrchtr/supi-lsp/api";
2
8
  import type { CapabilityWarningReport } from "../analysis/capability/capability-warnings.ts";
3
9
 
4
10
  export type HealthSection = "diagnostics" | "servers";
@@ -22,7 +28,8 @@ export interface HealthServerInfo {
22
28
  readonly name: string;
23
29
  readonly root: string;
24
30
  readonly fileTypes: readonly string[];
25
- readonly status: string;
31
+ readonly status: ProjectServerInfo["status"];
32
+ readonly statusReason?: ProjectServerStatusReason;
26
33
  readonly ready: boolean;
27
34
  }
28
35
 
@@ -87,6 +94,9 @@ export interface HealthStaleAssessment {
87
94
 
88
95
  export type HealthRefreshOperationScope = "file-runtime" | "workspace-runtime";
89
96
 
97
+ /** Readiness result for a file-scoped maintenance attempt. */
98
+ export type HealthFileReadiness = "ready" | "pending" | "unavailable";
99
+
90
100
  /** Shared facts for one completed refresh or file-maintenance attempt. */
91
101
  interface CompletedHealthRefreshAttempt {
92
102
  readonly kind: "completed";
@@ -97,7 +107,12 @@ interface CompletedHealthRefreshAttempt {
97
107
  readonly requestedDiagnosticScope: HealthDiagnosticScope;
98
108
  /** Active clients targeted by the best-effort operation; this is not a confirmed-success count. */
99
109
  readonly attemptedActiveClients: number;
110
+ /** File readiness after the bounded wait, when this is file-scoped. */
111
+ readonly fileReadiness?: HealthFileReadiness;
112
+ /** Clients restarted by stale-diagnostic recovery. */
100
113
  readonly restartedClients: number;
114
+ /** Separate outcome for process-crash route recovery. */
115
+ readonly processCrashRecovery: ProcessCrashRecoverySummary;
101
116
  readonly staleAssessment: HealthStaleAssessment;
102
117
  }
103
118
 
@@ -119,8 +134,18 @@ export type HealthRefreshAttempt =
119
134
  readonly elapsedMs: number;
120
135
  readonly requestedDiagnosticScope: HealthDiagnosticScope;
121
136
  readonly operationScope: HealthRefreshOperationScope;
137
+ /** Active clients targeted before the operation failed, when available. */
138
+ readonly attemptedActiveClients?: number;
139
+ /** Clients restarted by stale-diagnostic recovery, when available. */
140
+ readonly restartedClients?: number;
141
+ /** Stale-diagnostic assessment, when the recovery pass returned one. */
142
+ readonly staleAssessment?: HealthStaleAssessment;
122
143
  /** Evidence collected before the operation failed, when available. */
123
144
  readonly diagnosticEvidence?: DiagnosticEvidenceSummary;
145
+ /** Process-crash outcome, when the recovery pass returned one. */
146
+ readonly processCrashRecovery?: ProcessCrashRecoverySummary;
147
+ /** File readiness outcome, when a file-scoped attempt failed. */
148
+ readonly fileReadiness?: HealthFileReadiness;
124
149
  readonly reason: string;
125
150
  };
126
151
 
@@ -59,6 +59,7 @@ export async function runHealthWorkflow(
59
59
  if (prepared.kind === "invalid-input") return prepared;
60
60
  const { request, scopeFilter, included, level } = prepared.value;
61
61
  const semanticRequested = healthNeedsSemantic(included);
62
+ const fileReadinessRequested = included.includes("diagnostics");
62
63
  throwIfAborted(control);
63
64
 
64
65
  reportProgress(control, {
@@ -85,6 +86,7 @@ export async function runHealthWorkflow(
85
86
  });
86
87
  const semanticState = await establishSemanticHealthState({
87
88
  requested: semanticRequested,
89
+ fileReadinessRequested,
88
90
  runtime,
89
91
  scopeFilter,
90
92
  lspState,
@@ -139,6 +141,8 @@ function healthNeedsSemantic(included: readonly HealthSection[]): boolean {
139
141
 
140
142
  interface SemanticHealthStateOptions {
141
143
  requested: boolean;
144
+ /** Whether this call requests file-routed evidence rather than passive inventory. */
145
+ fileReadinessRequested: boolean;
142
146
  runtime: WorkspaceLspRuntime | null;
143
147
  scopeFilter: string | null;
144
148
  lspState: WorkspaceLspRuntimeState;
@@ -150,7 +154,7 @@ async function establishSemanticHealthState(
150
154
  options: SemanticHealthStateOptions,
151
155
  ): Promise<SemanticHealthState | null> {
152
156
  if (!options.requested) return null;
153
- if (options.runtime && isScopedFile(options.scopeFilter)) {
157
+ if (options.fileReadinessRequested && options.runtime && isScopedFile(options.scopeFilter)) {
154
158
  const readiness = await options.runtime.waitUntilReadyForFile(
155
159
  options.scopeFilter,
156
160
  undefined,
@@ -1,11 +1,12 @@
1
1
  /** Runtime parsers for health and refactor session workflows. */
2
2
 
3
3
  import type { HealthSection, HealthWorkflowInput } from "../health-types.ts";
4
- import type {
5
- PublicSourceRange,
6
- RefactorApplyWorkflowInput,
7
- RefactorOperationInput,
8
- RefactorPlanWorkflowInput,
4
+ import {
5
+ PUBLIC_REFACTOR_OPERATION_NAMES,
6
+ type PublicSourceRange,
7
+ type RefactorApplyWorkflowInput,
8
+ type RefactorOperationInput,
9
+ type RefactorPlanWorkflowInput,
9
10
  } from "../refactor-types.ts";
10
11
  import type { RefactorTargetInput } from "../target-input.ts";
11
12
  import {
@@ -94,22 +95,23 @@ export function parseRefactorPlanWorkflowInput(
94
95
  function parseRefactorOperation(value: unknown): InputValidation<RefactorOperationInput> {
95
96
  const record = requireRecord(value, "operation");
96
97
  if (record.kind === "invalid-input") return record;
97
- const keysError = requireOnlyKeys(
98
- record.value,
99
- ["rename_symbol", "extract_function", "extract_variable"],
100
- "operation",
101
- );
98
+ const keysError = requireOnlyKeys(record.value, PUBLIC_REFACTOR_OPERATION_NAMES, "operation");
102
99
  if (keysError) return invalid(keysError);
103
- const selected = (["rename_symbol", "extract_function", "extract_variable"] as const).filter(
104
- (key) => record.value[key] !== undefined,
105
- );
100
+ const selected = PUBLIC_REFACTOR_OPERATION_NAMES.filter((key) => record.value[key] !== undefined);
106
101
  if (selected.length !== 1) return invalid("Select exactly one refactor operation.");
107
102
 
108
103
  const name = selected[0];
109
104
  const payload = requireRecord(record.value[name], `operation.${name}`);
110
105
  if (payload.kind === "invalid-input") return payload;
111
106
  if (name === "rename_symbol") return parseRenameOperation(payload.value);
112
- return parseExtractOperation(name, payload.value);
107
+ if (name === "extract_function" || name === "extract_variable") {
108
+ return parseExtractOperation(name, payload.value);
109
+ }
110
+ const payloadError = requireOnlyKeys(payload.value, [], `operation.${name}`);
111
+ if (payloadError) return invalid(payloadError);
112
+ return name === "update_imports"
113
+ ? valid({ update_imports: {} })
114
+ : valid({ delete_dead_code: {} });
113
115
  }
114
116
 
115
117
  function parseRenameOperation(
@@ -3,6 +3,17 @@ import type { ApplyResult } from "../analysis/refactor/apply.ts";
3
3
  import type { RefactorPlan } from "./refactor-plans.ts";
4
4
  import type { RefactorTargetInput, SourcePointInput } from "./target-input.ts";
5
5
 
6
+ /** Public refactor operations exposed by code_refactor_plan. */
7
+ export const PUBLIC_REFACTOR_OPERATION_NAMES = [
8
+ "rename_symbol",
9
+ "extract_function",
10
+ "extract_variable",
11
+ "update_imports",
12
+ "delete_dead_code",
13
+ ] as const;
14
+
15
+ export type PublicRefactorOperationName = (typeof PUBLIC_REFACTOR_OPERATION_NAMES)[number];
16
+
6
17
  export interface PublicSourceRange {
7
18
  readonly start: Omit<SourcePointInput, "file">;
8
19
  readonly end: Omit<SourcePointInput, "file">;
@@ -21,7 +32,9 @@ export type RefactorOperationInput =
21
32
  readonly newName: string;
22
33
  readonly range: PublicSourceRange;
23
34
  };
24
- };
35
+ }
36
+ | { readonly update_imports: Record<string, never> }
37
+ | { readonly delete_dead_code: Record<string, never> };
25
38
 
26
39
  export interface RefactorPlanWorkflowInput {
27
40
  readonly target: RefactorTargetInput;
@@ -26,13 +26,14 @@ import {
26
26
  isPlanFresh,
27
27
  type RefactorPlan,
28
28
  } from "./refactor-plans.ts";
29
- import type {
30
- PublicSourceRange,
31
- RefactorApplyWorkflowInput,
32
- RefactorApplyWorkflowOutcome,
33
- RefactorOperationInput,
34
- RefactorPlanWorkflowInput,
35
- RefactorPlanWorkflowOutcome,
29
+ import {
30
+ PUBLIC_REFACTOR_OPERATION_NAMES,
31
+ type PublicSourceRange,
32
+ type RefactorApplyWorkflowInput,
33
+ type RefactorApplyWorkflowOutcome,
34
+ type RefactorOperationInput,
35
+ type RefactorPlanWorkflowInput,
36
+ type RefactorPlanWorkflowOutcome,
36
37
  } from "./refactor-types.ts";
37
38
  import { resolveTargetWorkflow, type TargetWorkflowDeps } from "./target-workflow.ts";
38
39
  import { reportProgress, throwIfAborted, type WorkflowControl } from "./workflow-control.ts";
@@ -208,13 +209,11 @@ function parseOperation(input: RefactorOperationInput):
208
209
  | {
209
210
  kind: "ok";
210
211
  operation: RefactorOperation;
211
- newName: string;
212
+ newName?: string;
212
213
  range?: PublicSourceRange;
213
214
  }
214
215
  | { kind: "invalid-input"; message: string } {
215
- const keys = ["rename_symbol", "extract_function", "extract_variable"].filter(
216
- (key) => key in input,
217
- );
216
+ const keys = PUBLIC_REFACTOR_OPERATION_NAMES.filter((key) => key in input);
218
217
  if (keys.length !== 1) {
219
218
  return { kind: "invalid-input", message: "Select exactly one refactor operation." };
220
219
  }
@@ -233,11 +232,17 @@ function parseOperation(input: RefactorOperationInput):
233
232
  range: input.extract_function.range,
234
233
  };
235
234
  }
235
+ if ("extract_variable" in input) {
236
+ return {
237
+ kind: "ok",
238
+ operation: "extract_variable",
239
+ newName: input.extract_variable.newName,
240
+ range: input.extract_variable.range,
241
+ };
242
+ }
236
243
  return {
237
244
  kind: "ok",
238
- operation: "extract_variable",
239
- newName: input.extract_variable.newName,
240
- range: input.extract_variable.range,
245
+ operation: "update_imports" in input ? "update_imports" : "delete_dead_code",
241
246
  };
242
247
  }
243
248
 
@@ -265,13 +270,13 @@ async function planWithProvider(
265
270
  file: string;
266
271
  position: { line: number; character: number };
267
272
  range?: SourceRange;
268
- newName: string;
273
+ newName?: string;
269
274
  },
270
275
  control?: WorkflowControl,
271
276
  ): Promise<RefactorResult> {
272
277
  if (!provider) return { kind: "unavailable", reason: "No semantic provider is active." };
273
278
  if (provider.refactor) return provider.refactor(request, control);
274
- if (request.operation === "rename_symbol" && provider.rename) {
279
+ if (request.operation === "rename_symbol" && request.newName !== undefined && provider.rename) {
275
280
  return provider.rename(request.file, request.position, request.newName, control);
276
281
  }
277
282
  return {
@@ -0,0 +1,36 @@
1
+ import type { CodeRequestControl, SemanticProvider } from "@mrclrchtr/supi-code-runtime/api";
2
+ import type { CapabilityAdapter, ReadinessOutcome } from "./capability-adapter.ts";
3
+
4
+ export type WorkspaceSemanticDemandState =
5
+ | { kind: "ready"; provider: SemanticProvider }
6
+ | Exclude<ReadinessOutcome, { kind: "ready" }>;
7
+
8
+ /**
9
+ * Wait for normal workspace startup, but let a registered provider proceed
10
+ * when known crashed routes make readiness recoverable only through demand.
11
+ */
12
+ export async function resolveWorkspaceSemanticDemand(
13
+ capability: CapabilityAdapter,
14
+ cwd: string,
15
+ control?: CodeRequestControl,
16
+ ): Promise<WorkspaceSemanticDemandState> {
17
+ let provider = capability.getSemanticProvider(cwd);
18
+ const readiness = await capability.ensureSemanticReadiness(cwd, { kind: "workspace" }, control);
19
+ if (readiness.kind === "timeout") return readiness;
20
+ if (readiness.kind === "unavailable" && !hasProcessCrashRoute(capability, cwd)) {
21
+ return readiness;
22
+ }
23
+
24
+ provider ??= capability.getSemanticProvider(cwd);
25
+ return provider
26
+ ? { kind: "ready", provider }
27
+ : { kind: "unavailable", reason: "No semantic workspace-symbol provider is active." };
28
+ }
29
+
30
+ function hasProcessCrashRoute(capability: CapabilityAdapter, cwd: string): boolean {
31
+ const lspState = capability.getLspRuntimeState(cwd);
32
+ return (
33
+ lspState.kind === "ready" &&
34
+ lspState.runtime.getProjectServers().some((server) => server.statusReason !== undefined)
35
+ );
36
+ }
@@ -23,6 +23,7 @@ import type {
23
23
  } from "../analysis/target/types.ts";
24
24
  import type { CapabilityAdapter } from "./capability-adapter.ts";
25
25
  import { parseTargetInput } from "./input/common.ts";
26
+ import { resolveWorkspaceSemanticDemand } from "./semantic-demand.ts";
26
27
  import { registerTargetCandidates, type TargetWorkflowCandidate } from "./target-candidates.ts";
27
28
  import type { TargetInput, TargetSymbolKind } from "./target-input.ts";
28
29
  import {
@@ -201,22 +202,15 @@ async function resolveSymbolWorkflow(
201
202
  scope = resolved.path;
202
203
  }
203
204
 
204
- const readiness = await deps.capability.ensureSemanticReadiness(
205
- deps.cwd,
206
- { kind: "workspace" },
207
- control,
208
- );
209
- if (readiness.kind === "timeout") {
205
+ const semanticDemand = await resolveWorkspaceSemanticDemand(deps.capability, deps.cwd, control);
206
+ if (semanticDemand.kind === "timeout") {
210
207
  return { kind: "unavailable", reason: "LSP readiness timed out. Retry shortly." };
211
208
  }
212
- if (readiness.kind === "unavailable") {
213
- return { kind: "unavailable", reason: readiness.reason };
209
+ if (semanticDemand.kind === "unavailable") {
210
+ return { kind: "unavailable", reason: semanticDemand.reason };
214
211
  }
215
212
 
216
- const provider = withSemanticRequestControl(
217
- deps.capability.getSemanticProvider(deps.cwd),
218
- control,
219
- );
213
+ const provider = withSemanticRequestControl(semanticDemand.provider, control);
220
214
  if (!provider) {
221
215
  return {
222
216
  kind: "unavailable",
@@ -291,6 +285,9 @@ function toWorkflowOutcome(
291
285
  policy: TargetWorkflowPolicy,
292
286
  deps: TargetWorkflowDeps,
293
287
  ): TargetWorkflowOutcome {
288
+ if (outcome.kind === "unavailable") {
289
+ return { kind: "unavailable", reason: outcome.reason };
290
+ }
294
291
  if (outcome.kind === "error") {
295
292
  return { kind: "invalid-input", message: outcome.message };
296
293
  }
@@ -45,7 +45,6 @@ export function registerLspSessionLifecycle(
45
45
  await state.providerLease?.release();
46
46
  state.providerLease = null;
47
47
  state.controller = null;
48
- state.lspActive = false;
49
48
  state.sentinelSnapshot = new Map();
50
49
 
51
50
  const lease = await acquireWorkspaceProviderHost(ctx.cwd, {
@@ -53,7 +52,6 @@ export function registerLspSessionLifecycle(
53
52
  });
54
53
  state.providerLease = lease;
55
54
  state.controller = lease.lspController;
56
- state.lspActive = lease.lspController?.kind === "ready";
57
55
  state.sentinelSnapshot = lease.sentinelSnapshot;
58
56
  await onStarted?.(ctx);
59
57
  startLifecycleSubscription();
@@ -64,7 +62,6 @@ export function registerLspSessionLifecycle(
64
62
  await state.providerLease?.release();
65
63
  state.providerLease = null;
66
64
  state.controller = null;
67
- state.lspActive = false;
68
65
  state.sentinelSnapshot = new Map();
69
66
  dispatchStateChange();
70
67
  });
@@ -24,7 +24,6 @@ import {
24
24
  isLikelyStaleDiagnostic,
25
25
  isProjectConfigFileName,
26
26
  raceRequestControl,
27
- syncWorkspaceSentinelSnapshot,
28
27
  type WorkspaceLspRuntime,
29
28
  } from "@mrclrchtr/supi-lsp/api";
30
29
  import { mergeDiagnosticEvidence } from "../../diagnostics/evidence.ts";
@@ -56,7 +55,7 @@ export async function refreshLspMaintenance(
56
55
  sentinelSnapshot: Map<string, number>,
57
56
  options: LspMaintenanceOptions = {},
58
57
  ): Promise<WorkspaceLspMaintenanceResult> {
59
- const { snapshot } = await synchronizeSentinels(runtime, cwd, sentinelSnapshot, options);
58
+ const { snapshot } = await synchronizeSentinels(runtime, sentinelSnapshot, options);
60
59
  let diagnosticEvidence = emptyEvidence();
61
60
  let failureReason: string | undefined;
62
61
 
@@ -115,7 +114,7 @@ export async function refreshFileLspMaintenance(options: {
115
114
  const { runtime, cwd, sentinelSnapshot, filePath, control } = options;
116
115
  // A cancelled caller stops before any maintenance work starts.
117
116
  throwIfCodeRequestInterrupted(control);
118
- const { snapshot } = await synchronizeSentinels(runtime, cwd, sentinelSnapshot);
117
+ const { snapshot } = await synchronizeSentinels(runtime, sentinelSnapshot);
119
118
  const target = nodePath.resolve(filePath);
120
119
  const stale = confirmedOutstandingDiagnostics(runtime, cwd).some(
121
120
  (entry) =>
@@ -149,12 +148,11 @@ export async function refreshFileLspMaintenance(options: {
149
148
  */
150
149
  async function synchronizeSentinels(
151
150
  runtime: WorkspaceLspRuntime,
152
- cwd: string,
153
151
  sentinelSnapshot: Map<string, number>,
154
152
  options: LspMaintenanceOptions = {},
155
153
  ) {
156
154
  const primed = sentinelSnapshot.size > 0;
157
- const state = syncWorkspaceSentinelSnapshot(cwd, sentinelSnapshot, {
155
+ const state = runtime.syncWorkspaceSentinelSnapshot(sentinelSnapshot, {
158
156
  includeSourceFiles: true,
159
157
  });
160
158
 
@@ -193,6 +191,7 @@ async function trackCreatedSourceFiles(
193
191
  if (change.type !== FileChangeType.Created) continue;
194
192
  const filePath = uriToFile(change.uri);
195
193
  if (scope && !isWithinOrEqual(scope, filePath)) continue;
194
+ if (!runtime.isSupportedSourceFile(filePath)) continue;
196
195
  // Best-effort: a file no client can serve stays untracked and is
197
196
  // simply absent from evidence until a later explicit request.
198
197
  await runtime.trackFile(filePath);