@mrclrchtr/supi-lsp 3.2.0 → 4.1.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 (58) hide show
  1. package/README.md +72 -62
  2. package/node_modules/@mrclrchtr/supi-code-runtime/README.md +8 -0
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -4
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +0 -4
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +3 -2
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/index.ts +0 -4
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/llm.ts +0 -10
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +19 -15
  9. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  10. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  11. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  12. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  13. package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +10 -1
  14. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +19 -29
  15. package/node_modules/@mrclrchtr/supi-code-runtime/src/index.ts +7 -1
  16. package/node_modules/@mrclrchtr/supi-code-runtime/src/query-result.ts +42 -0
  17. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +15 -10
  18. package/node_modules/@mrclrchtr/supi-core/package.json +2 -4
  19. package/node_modules/@mrclrchtr/supi-core/src/api.ts +0 -4
  20. package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +3 -2
  21. package/node_modules/@mrclrchtr/supi-core/src/index.ts +0 -4
  22. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +0 -10
  23. package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +19 -15
  24. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  25. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  26. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  27. package/package.json +4 -5
  28. package/src/api.ts +9 -9
  29. package/src/client/client.ts +70 -41
  30. package/src/client/transport.ts +16 -13
  31. package/src/config/config.ts +2 -2
  32. package/src/config/lsp-settings.ts +0 -2
  33. package/src/index.ts +6 -6
  34. package/src/manager/manager-diagnostics.ts +5 -95
  35. package/src/manager/manager-workspace-recovery.ts +4 -3
  36. package/src/manager/manager-workspace-symbol.ts +44 -6
  37. package/src/manager/manager.ts +47 -65
  38. package/src/provider/lsp-semantic-provider.ts +132 -79
  39. package/src/provider/refactor-planning.ts +6 -14
  40. package/src/session/readiness.ts +38 -0
  41. package/src/session/runtime-controller.ts +87 -75
  42. package/src/session/runtime-registration.ts +5 -5
  43. package/src/session/{service-registry.ts → runtime-registry.ts} +163 -71
  44. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +0 -31
  45. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/substrate-types.ts +0 -11
  46. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/types.ts +0 -2
  47. package/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +0 -31
  48. package/node_modules/@mrclrchtr/supi-core/src/substrate-types.ts +0 -11
  49. package/node_modules/@mrclrchtr/supi-core/src/types.ts +0 -2
  50. package/src/diagnostics/diagnostic-augmentation.ts +0 -82
  51. package/src/diagnostics/diagnostic-context.ts +0 -116
  52. package/src/diagnostics/diagnostic-display.ts +0 -69
  53. package/src/manager/capability-index.ts +0 -24
  54. package/src/manager/client-pool.ts +0 -33
  55. package/src/manager/diagnostic-store.ts +0 -51
  56. package/src/manager/manager-stale-resync.ts +0 -47
  57. package/src/manager/recovery-coordinator.ts +0 -37
  58. package/src/manager/workspace-router.ts +0 -44
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  Language Server Protocol runtime library for the [pi coding agent](https://github.com/earendil-works/pi).
12
12
 
13
- This is a **library-only** package it has no pi extension surface and does not register public tools by itself. Public semantic code-intelligence behavior is exposed through `@mrclrchtr/supi-code-intelligence`'s `code_*` tools.
13
+ This package is library-only. It registers no model-callable tools; `@mrclrchtr/supi-code-intelligence` owns the public `code_*` family.
14
14
 
15
15
  ## Install
16
16
 
@@ -18,90 +18,100 @@ This is a **library-only** package — it has no pi extension surface and does n
18
18
  npm install @mrclrchtr/supi-lsp
19
19
  ```
20
20
 
21
- ## What this package provides
21
+ ## What it provides
22
22
 
23
- `@mrclrchtr/supi-lsp` provides the semantic substrate used by SuPi's code-intelligence stack:
23
+ - `LspRuntimeController` for workspace lifecycle and status
24
+ - `WorkspaceLspRuntime` for routing, readiness, semantic operations, tracked files, diagnostics, and recovery
25
+ - explicit ready, pending, inactive, disabled, and unavailable registry states
26
+ - a `SemanticProvider` adapter for `supi-code-runtime`
27
+ - precise rename and code-action edit conversion
24
28
 
25
- - a session-scoped `SessionLspService`
26
- - LSP client lifecycle, transport, and workspace routing
27
- - diagnostics collection, stale-diagnostic recovery, and summaries
28
- - a shared `SemanticProvider` adapter for the workspace runtime
29
- - operation-aware semantic refactor planning for first-wave precise text-edit operations
29
+ Clients, `LspManager`, and the default runtime implementation remain internal.
30
30
 
31
- First-wave refactor mapping in the semantic provider:
31
+ ## Runtime split
32
32
 
33
- - `rename_symbol` and legacy `rename` alias → `textDocument/rename`
34
- - `update_imports` → precise organize-imports/source actions only
35
- - `delete_dead_code` → precise quickfix/refactor-rewrite actions only
36
- - `rename_file` / `move_file` → explicit unavailable results for now
33
+ `LspRuntimeController` owns:
37
34
 
38
- The historical LSP status overlay is no longer part of this package. Substrate-owned status UX now lives in `@mrclrchtr/supi-code-intelligence` as the `/supi-ci-status` command.
35
+ - language-server detection and startup
36
+ - shutdown
37
+ - settings and missing-server inventory
38
+ - publishing workspace runtime state
39
39
 
40
- ## Startup performance
40
+ `WorkspaceLspRuntime` owns:
41
41
 
42
- Language servers start automatically when a PI session opens. By default, every server with matching source files in the project is started **concurrently** — in polyglot repos or monorepos with multiple language footprints, this parallel startup can cause a significant CPU spike.
42
+ - hover, definition, references, implementations, symbols, rename, and code actions
43
+ - file/workspace readiness waits
44
+ - tracked-file lifecycle and workspace change notifications
45
+ - diagnostics, summaries, refresh, and recovery
46
+ - project-server inventory and file support checks
43
47
 
44
- **To reduce startup overhead:**
48
+ This separation keeps lifecycle/status distinct from workspace operations.
45
49
 
46
- - **Disable specific language servers** that you don't need. Only servers whose source files are detected in the project will be started. To explicitly exclude a language:
50
+ ## Example
47
51
 
48
- ```json
49
- {
50
- "lsp": {
51
- "servers": {
52
- "python": { "enabled": false },
53
- "rust": { "enabled": false }
54
- }
55
- }
52
+ ```ts
53
+ import { getWorkspaceLspRuntime, toLspPosition } from "@mrclrchtr/supi-lsp/api";
54
+
55
+ const state = getWorkspaceLspRuntime("/project");
56
+ if (state.kind === "ready") {
57
+ const definitions = await state.runtime.definition(
58
+ "src/index.ts",
59
+ toLspPosition(6, 11),
60
+ );
61
+ if (definitions.kind === "completed") {
62
+ // `data: null` is a successful no-definition observation.
63
+ console.log(definitions.data);
56
64
  }
57
- ```
65
+ }
66
+ ```
58
67
 
59
- Add this to `.pi/supi/config.json` (project) or `~/.pi/agent/supi/config.json` (global). Only the listed language servers are disabled; all others remain active.
68
+ Runtime methods use raw 0-based LSP positions. `toLspPosition()` converts user-facing 1-based coordinates. Read-only semantic and diagnostic methods return `CodeQueryResult<T>` so completed empty protocol responses remain distinct from partial or unavailable requests. A ready runtime owner may contain only lazy routes: workspace semantic readiness requires at least one active ready client, while file readiness requires the routed client for that file to start successfully. Empty client sets and failed routes are unavailable, not vacuously ready.
60
69
 
61
- > **Note:** The global `lsp.enabled` switch and `lsp.active` allowlist were removed in v0.7.0. LSP is always-on by default. Per-language `lsp.servers.<language>.enabled: false` is the only supported way to opt out. If your config still has `lsp.enabled` or `lsp.active` keys, they are ignored and a deprecation warning will appear at session start.
70
+ ## Startup performance
62
71
 
63
- Server discovery walks the project tree at depth 3 (skipping `node_modules`, `.git`, `.pnpm`). Every detected language server starts in parallel via `Promise.all`.
72
+ Detected servers start concurrently. In a polyglot workspace, disable unneeded languages in `.pi/supi/config.json` or `~/.pi/agent/supi/config.json`:
64
73
 
65
- ## Architecture
74
+ ```json
75
+ {
76
+ "lsp": {
77
+ "servers": {
78
+ "python": { "enabled": false },
79
+ "rust": { "enabled": false }
80
+ }
81
+ }
82
+ }
83
+ ```
66
84
 
67
- `@mrclrchtr/supi-lsp` is the **semantic substrate** in SuPi's code-understanding stack.
68
- It depends on `@mrclrchtr/supi-core` and `@mrclrchtr/supi-code-runtime` for shared
69
- contracts, and provides a session-scoped LSP service that publishes semantic and
70
- diagnostic capabilities into the shared workspace runtime.
85
+ The old `lsp.enabled` and `lsp.active` settings are deprecated and ignored. If every server definition is disabled, the controller publishes an explicit `disabled` runtime state instead of an empty `ready` runtime. When a ready owner has no active client yet, it stays published for lazy routing while semantic capability remains pending.
86
+
87
+ ## Architecture
71
88
 
72
89
  ```text
73
- supi-code-runtime shared contracts + workspace runtime
74
-
75
- supi-lsp LSP client + session-scoped service + runtime capabilities
76
-
77
- supi-code-intelligence public code_* tools over the semantic substrate
90
+ supi-code-runtime canonical contracts + capability broker
91
+
92
+ supi-lsp semantic lifecycle + Workspace LSP runtime
93
+
94
+ supi-code-intelligence Workspace session + public code_* tools
78
95
  ```
79
96
 
80
- ## Package surfaces
81
-
82
- - `@mrclrchtr/supi-lsp/api` reusable session-scoped LSP service and related types
83
- - `@mrclrchtr/supi-lsp/provider/lsp-semantic-provider` shared `SemanticProvider` adapter
97
+ The private `DefaultWorkspaceLspRuntime` is the single operational seam. It normalizes
98
+ paths and coordinates readiness, semantic requests, tracked files, diagnostics, recovery,
99
+ and owner-controlled shutdown around the package-internal manager. Clients and the manager
100
+ remain hidden from consumers.
84
101
 
85
- Example:
102
+ See [`docs/adr/0016-workspace-lsp-runtime-interface.md`](../../docs/adr/0016-workspace-lsp-runtime-interface.md).
86
103
 
87
- ```ts
88
- import { getSessionLspService, toLspPosition } from "@mrclrchtr/supi-lsp/api";
89
-
90
- const state = getSessionLspService("/project");
91
- if (state.kind === "ready") {
92
- const defs = await state.service.definition("src/index.ts", toLspPosition(6, 11));
93
- }
94
- ```
104
+ ## Package exports
95
105
 
96
- `SessionLspService` methods use raw **0-based LSP positions**. Public `code_*` tools keep the user-facing **1-based** coordinate UX.
106
+ - `@mrclrchtr/supi-lsp/api` runtime/controller/config types and registry operations
107
+ - `@mrclrchtr/supi-lsp/provider/lsp-semantic-provider` — semantic provider adapter
97
108
 
98
109
  ## Source
99
110
 
100
- - `src/client/` — LSP client, transport, refresh, and request handling
101
- - `src/config/` — server config, defaults, capabilities, and exported LSP protocol types
102
- - `src/diagnostics/` — stale diagnostics, suppression diagnostics, and workspace sentinels
103
- - `src/manager/` — manager lifecycle, routing, diagnostics, and recovery helpers
104
- - `src/provider/lsp-semantic-provider.ts`shared `SemanticProvider` adapter
105
- - `src/session/`service registry, runtime registration, scanner, and controller logic
106
- - `src/api.ts` — reusable developer-facing surface
107
- - `src/index.ts` — package-root re-export surface
111
+ - `src/client/` — protocol client, transport, refresh, and requests
112
+ - `src/config/` — server configuration and protocol types
113
+ - `src/diagnostics/` — stale diagnostics and workspace sentinels
114
+ - `src/manager/` — package-internal server pool and routing, diagnostic, and recovery mechanics
115
+ - `src/provider/`semantic and refactor adapters
116
+ - `src/session/runtime-controller.ts`lifecycle/status
117
+ - `src/session/runtime-registry.ts` — `WorkspaceLspRuntime` and registry
@@ -8,6 +8,14 @@ This is a **library-only package** — it has no pi extension surface, no user-f
8
8
 
9
9
  - `@mrclrchtr/supi-code-runtime/api` — shared canonical types, capability interfaces, workspace runtime registry, and typed request context
10
10
 
11
+ Read-only provider methods return `CodeQueryResult<T>`:
12
+
13
+ - `completed` — collection completed; `data` may be empty or `null`
14
+ - `partial` — usable data was collected, but one or more provider branches failed
15
+ - `unavailable` — no result was established; includes a reason
16
+
17
+ This keeps successful zero-result facts distinct from routing, transport, and provider failures.
18
+
11
19
  ## License
12
20
 
13
21
  MIT
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "3.2.0",
3
+ "version": "4.1.0",
4
4
  "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -50,7 +50,6 @@
50
50
  },
51
51
  "main": "src/api.ts",
52
52
  "exports": {
53
- "./abort-utils": "./src/abort-utils.ts",
54
53
  "./api": "./src/api.ts",
55
54
  "./config": "./src/config.ts",
56
55
  "./context": "./src/context.ts",
@@ -71,7 +70,6 @@
71
70
  "./status-spinner": "./src/status-spinner.ts",
72
71
  "./terminal": "./src/terminal.ts",
73
72
  "./tool-framework": "./src/tool-framework.ts",
74
- "./prompt-surface": "./src/prompt-surface.ts",
75
- "./types": "./src/types.ts"
73
+ "./prompt-surface": "./src/prompt-surface.ts"
76
74
  }
77
75
  }
@@ -6,8 +6,6 @@
6
6
  // For lighter imports, use one of the domain subpaths directly
7
7
  // (e.g. @mrclrchtr/supi-core/config, @mrclrchtr/supi-core/context).
8
8
 
9
- // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
10
- export * from "./abort-utils.ts";
11
9
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
12
10
  export * from "./config.ts";
13
11
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
@@ -42,5 +40,3 @@ export * from "./status-spinner.ts";
42
40
  export * from "./terminal.ts";
43
41
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
44
42
  export * from "./tool-framework.ts";
45
- // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
46
- export * from "./types.ts";
@@ -22,14 +22,15 @@ export interface EvidenceBadgeInput {
22
22
  * |------------------------------------------------------|---------------------------------------|
23
23
  * | shown=12, total=12, omitted=0, label="references" | `12 references` |
24
24
  * | shown=8, total=20, omitted=12, label="symbols" | `8 of 20 symbols (12 omitted)` |
25
- * | shown=5, total=null, reason="timeout", label="matches" | `5 matches — timeout` |
25
+ * | shown=5, total=null, omitted=2, reason="timeout" | `5 matches (2 collected omitted; more may exist — timeout)` |
26
26
  */
27
27
  export function formatEvidenceBadge(input: EvidenceBadgeInput): string {
28
28
  const { shownCount, totalCount, omittedCount, partialReason, label } = input;
29
29
 
30
30
  if (totalCount === null) {
31
31
  const reasonSuffix = partialReason ? ` — ${partialReason}` : "";
32
- return `${shownCount} ${label}${reasonSuffix}`;
32
+ const omittedPrefix = (omittedCount ?? 0) > 0 ? `${omittedCount} collected omitted; ` : "";
33
+ return `${shownCount} ${label} (${omittedPrefix}more may exist${reasonSuffix})`;
33
34
  }
34
35
 
35
36
  if (omittedCount !== null && omittedCount > 0) {
@@ -6,8 +6,6 @@
6
6
  // For lighter imports, use one of the domain subpaths directly
7
7
  // (e.g. @mrclrchtr/supi-core/config, @mrclrchtr/supi-core/context).
8
8
 
9
- // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
10
- export * from "./abort-utils.ts";
11
9
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
12
10
  export * from "./config.ts";
13
11
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
@@ -36,5 +34,3 @@ export * from "./status-spinner.ts";
36
34
  export * from "./terminal.ts";
37
35
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
38
36
  export * from "./tool-framework.ts";
39
- // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
40
- export * from "./types.ts";
@@ -24,16 +24,6 @@ export interface WithRetryOptions {
24
24
  onRetry?: (attempt: number, delayMs: number) => void;
25
25
  }
26
26
 
27
- /**
28
- * Attempt an async operation with retries and exponential backoff.
29
- *
30
- * If the signal is already aborted on entry, the operation is skipped entirely.
31
- * If the signal aborts during a delay, the delay is cancelled immediately.
32
- *
33
- * @param fn - The async operation to retry.
34
- * @param options - Optional configuration for retries, backoff, signal, and callbacks.
35
- * @returns The result on success, or `null` if all attempts fail or the signal aborts.
36
- */
37
27
  /**
38
28
  * Create a promise that resolves after `ms` milliseconds, or rejects if
39
29
  * the signal fires before the timeout elapses.
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { fileURLToPath, pathToFileURL } from "node:url";
2
3
 
3
4
  /** Strip pi's optional leading `@` file-path prefix from a tool input. */
4
5
  export function stripToolPathPrefix(target: string): string {
@@ -16,25 +17,28 @@ export function resolveToolPath(cwd: string, target: string): string {
16
17
  return path.resolve(cwd, stripToolPathPrefix(target));
17
18
  }
18
19
 
19
- /** Convert a file path to a file:// URI. */
20
+ /**
21
+ * Convert a file path to a file:// URI.
22
+ *
23
+ * Uses Node's `pathToFileURL` to produce a standards-compliant URI with
24
+ * proper percent-encoding of spaces, hashes, and other special characters.
25
+ */
20
26
  export function fileToUri(filePath: string): string {
21
- const resolved = path.resolve(filePath);
22
- if (process.platform === "win32") {
23
- return `file:///${resolved.replace(/\\/g, "/")}`;
24
- }
25
- return `file://${resolved}`;
27
+ return pathToFileURL(filePath).href;
26
28
  }
27
29
 
28
- /** Convert a file:// URI to a file path. */
30
+ /**
31
+ * Convert a file:// URI to a file path.
32
+ *
33
+ * Uses Node's `fileURLToPath` for standards-compliant decoding. Non-file
34
+ * URIs are passed through unchanged so consumers (such as LSP diagnostic
35
+ * handling) remain compatible with non-file URI schemes.
36
+ */
29
37
  export function uriToFile(uri: string): string {
30
38
  if (!uri.startsWith("file://")) return uri;
31
- let filePath = decodeURIComponent(uri.slice(7));
32
- if (
33
- process.platform === "win32" &&
34
- filePath.startsWith("/") &&
35
- /^[A-Za-z]:/.test(filePath.slice(1))
36
- ) {
37
- filePath = filePath.slice(1);
39
+ try {
40
+ return fileURLToPath(uri);
41
+ } catch {
42
+ return uri;
38
43
  }
39
- return filePath;
40
44
  }
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
34
34
  if (choices.length > 0) {
35
35
  for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
36
36
  } else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
37
+ else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
37
38
  else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
38
39
  else if (field.field.kind === "modelPicker")
39
40
  menu.push({ value: "edit", label: "Choose model…" });
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
86
86
  values?: string[];
87
87
  }
88
88
 
89
+ /** One free-form string. */
90
+ export interface StringField extends BaseField {
91
+ kind: "string";
92
+ }
93
+
89
94
  /** Comma-separated string list. */
90
95
  export interface StringListField extends BaseField {
91
96
  kind: "stringList";
@@ -164,6 +169,7 @@ export type SettingsField =
164
169
  | BoolField
165
170
  | EnumField
166
171
  | NumberField
172
+ | StringField
167
173
  | StringListField
168
174
  | ModelPickerField
169
175
  | CustomField;
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
248
254
  return value ? "on" : "off";
249
255
  case "number":
250
256
  return String(value ?? "");
257
+ case "string":
258
+ return typeof value === "string" && value ? value : "none";
251
259
  case "stringList": {
252
260
  const arr = Array.isArray(value) ? value : [];
253
261
  return arr.length > 0 ? arr.map(String).join(", ") : "none";
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
271
279
 
272
280
  /** Format the value used to prefill editors and compare concrete choices. */
273
281
  export function formatEditValue(value: unknown, field: SettingsField): string {
282
+ if (field.kind === "string") return typeof value === "string" ? value : "";
274
283
  if (field.kind === "stringList") {
275
284
  const arr = Array.isArray(value) ? value : [];
276
285
  return arr.map(String).join(", ");
@@ -26,6 +26,7 @@ export type {
26
26
  SettingsField,
27
27
  SettingsFieldAction,
28
28
  SettingsPersistedChange,
29
+ StringField,
29
30
  StringListField,
30
31
  ValueSource,
31
32
  } from "./settings/settings-schema.ts";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "3.2.0",
3
+ "version": "4.1.0",
4
4
  "description": "SuPi code-runtime — shared workspace context, capability contracts, and canonical types for the code-understanding stack",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "!__tests__"
30
30
  ],
31
31
  "dependencies": {
32
- "@mrclrchtr/supi-core": "3.2.0"
32
+ "@mrclrchtr/supi-core": "4.1.0"
33
33
  },
34
34
  "bundledDependencies": [
35
35
  "@mrclrchtr/supi-core"
@@ -15,6 +15,14 @@ export type {
15
15
  StructuralProvider,
16
16
  StructuralResult,
17
17
  } from "./capability/types.ts";
18
+ export type { CodeQueryResult } from "./query-result.ts";
19
+ // Shared query-result constructors and contract
20
+ export {
21
+ completedCodeQuery,
22
+ mapCodeQueryResult,
23
+ partialCodeQuery,
24
+ unavailableCodeQuery,
25
+ } from "./query-result.ts";
18
26
  // Shared canonical types
19
27
  export type {
20
28
  CalleeDepth,
@@ -25,7 +33,9 @@ export type {
25
33
  CodeResult,
26
34
  CodeSymbol,
27
35
  ConfidenceMode,
36
+ DeclarationNesting,
28
37
  DisambiguationCandidate,
38
+ DocumentCodeSymbol,
29
39
  ExportData,
30
40
  FileEdit,
31
41
  ImportData,
@@ -37,7 +47,6 @@ export type {
37
47
  SourceRange,
38
48
  WorkspaceEdit,
39
49
  } from "./types.ts";
40
- export { normalizeRefactorOperation } from "./types.ts";
41
50
  export type { WorkspaceContext } from "./workspace/context.ts";
42
51
  // Workspace context
43
52
  export { createWorkspaceContext } from "./workspace/context.ts";
@@ -4,6 +4,7 @@
4
4
  * tree-sitter) advertise what they can do.
5
5
  */
6
6
 
7
+ import type { CodeQueryResult } from "../query-result.ts";
7
8
  import type {
8
9
  CalleeDepth,
9
10
  CalleesData,
@@ -12,6 +13,7 @@ import type {
12
13
  CodePosition,
13
14
  CodeResult,
14
15
  CodeSymbol,
16
+ DocumentCodeSymbol,
15
17
  ExportData,
16
18
  ImportData,
17
19
  NodeAtData,
@@ -44,32 +46,30 @@ export type CapabilityState =
44
46
  /**
45
47
  * Semantic analysis capability backed by a language server (LSP).
46
48
  *
47
- * Methods return `null` to signal absence (unavailable / inactive) and
48
- * an array to signal a successful query (possibly empty).
49
+ * Every read query preserves whether collection completed, completed only
50
+ * partially, or was unavailable. Successful empty arrays and protocol-level
51
+ * null values are completed observations rather than capability failures.
49
52
  */
50
53
  export interface SemanticProvider {
51
- references(filePath: string, position: CodePosition): Promise<CodeLocation[] | null>;
52
- implementation(filePath: string, position: CodePosition): Promise<CodeLocation[] | null>;
53
- documentSymbols(filePath: string): Promise<CodeSymbol[] | null>;
54
- workspaceSymbols(query: string): Promise<CodeSymbol[] | null>;
54
+ references(filePath: string, position: CodePosition): Promise<CodeQueryResult<CodeLocation[]>>;
55
+ implementation(
56
+ filePath: string,
57
+ position: CodePosition,
58
+ ): Promise<CodeQueryResult<CodeLocation[]>>;
59
+ documentSymbols(filePath: string): Promise<CodeQueryResult<DocumentCodeSymbol[]>>;
60
+ workspaceSymbols(query: string): Promise<CodeQueryResult<CodeSymbol[]>>;
55
61
 
56
- /**
57
- * Optional definition capability. Returns the definition location(s) for
58
- * the symbol at the given position. When the provider cannot produce
59
- * definition info, returns `null`.
60
- */
61
- definition?(filePath: string, position: CodePosition): Promise<CodeLocation[] | null>;
62
+ /** Optional definition capability with explicit completed-empty semantics. */
63
+ definition?(filePath: string, position: CodePosition): Promise<CodeQueryResult<CodeLocation[]>>;
62
64
 
63
65
  /**
64
- * Optional hover capability. Returns a simplified type/signature info
65
- * shape that does not depend on vscode-languageserver-types. When the
66
- * provider cannot produce hover info (unavailable, unsupported file,
67
- * no result at the given position), returns `null`.
66
+ * Optional hover capability. A completed `null` data value means the
67
+ * provider successfully found no hover at the requested point.
68
68
  */
69
69
  hover?(
70
70
  filePath: string,
71
71
  position: CodePosition,
72
- ): Promise<{ contents: string; range?: SourceRange } | null>;
72
+ ): Promise<CodeQueryResult<{ contents: string; range?: SourceRange } | null>>;
73
73
 
74
74
  /**
75
75
  * Optional operation-aware refactor capability.
@@ -85,8 +85,8 @@ export interface SemanticProvider {
85
85
  * Optional rename capability. When present, the provider supports
86
86
  * precise semantic symbol-rename operations.
87
87
  *
88
- * Kept as a low-level substrate helper for compatibility while the public
89
- * tool surface still exposes a legacy `rename` alias.
88
+ * This remains a lower-level substrate helper for providers that expose
89
+ * symbol rename independently of their general refactor planner.
90
90
  */
91
91
  rename?(file: string, position: CodePosition, newName: string): Promise<RefactorResult>;
92
92
 
@@ -97,16 +97,6 @@ export interface SemanticProvider {
97
97
  * Kept as a low-level substrate helper and for lightweight introspection.
98
98
  */
99
99
  codeActions?(file: string, position: CodePosition): Promise<RefactorResult[]>;
100
-
101
- /**
102
- * Optional lightweight code action titles for display purposes.
103
- * Returns simplified title/kind pairs at the given position.
104
- * When the provider cannot produce code actions, returns `null`.
105
- */
106
- codeActionTitles?(
107
- file: string,
108
- position: CodePosition,
109
- ): Promise<Array<{ title: string; kind?: string }> | null>;
110
100
  }
111
101
 
112
102
  /**
@@ -12,9 +12,12 @@ export type {
12
12
  CapabilityState,
13
13
  CodeLocation,
14
14
  CodePosition,
15
+ CodeQueryResult,
15
16
  CodeResult,
16
17
  CodeSymbol,
17
18
  ConfidenceMode,
19
+ DeclarationNesting,
20
+ DocumentCodeSymbol,
18
21
  ExportData,
19
22
  ImportData,
20
23
  NodeAtData,
@@ -29,8 +32,11 @@ export type {
29
32
  WorkspaceContext,
30
33
  } from "./api.ts";
31
34
  export {
35
+ completedCodeQuery,
32
36
  createWorkspaceContext,
33
37
  getDefaultWorkspaceRuntime,
34
- normalizeRefactorOperation,
38
+ mapCodeQueryResult,
39
+ partialCodeQuery,
40
+ unavailableCodeQuery,
35
41
  WorkspaceRuntime,
36
42
  } from "./api.ts";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Result of a read-only code-provider query.
3
+ *
4
+ * `completed` includes successful empty observations, such as an empty
5
+ * reference list or a protocol-level null hover. `partial` preserves usable
6
+ * data when a multi-provider query could not complete every branch.
7
+ * `unavailable` means the query did not establish a result.
8
+ */
9
+ export type CodeQueryResult<T> =
10
+ | { readonly kind: "completed"; readonly data: T }
11
+ | { readonly kind: "partial"; readonly data: T; readonly reason: string }
12
+ | { readonly kind: "unavailable"; readonly reason: string };
13
+
14
+ /** Construct a successfully completed code-query result, including empty data. */
15
+ export function completedCodeQuery<T>(data: T): CodeQueryResult<T> {
16
+ return { kind: "completed", data };
17
+ }
18
+
19
+ /** Construct a usable but incomplete code-query result. */
20
+ export function partialCodeQuery<T>(data: T, reason: string): CodeQueryResult<T> {
21
+ return { kind: "partial", data, reason };
22
+ }
23
+
24
+ /** Construct a code-query result that could not be established. */
25
+ export function unavailableCodeQuery<T = never>(reason: string): CodeQueryResult<T> {
26
+ return { kind: "unavailable", reason };
27
+ }
28
+
29
+ /** Map completed or partial query data while preserving its availability state. */
30
+ export function mapCodeQueryResult<T, U>(
31
+ result: CodeQueryResult<T>,
32
+ map: (data: T) => U,
33
+ ): CodeQueryResult<U> {
34
+ switch (result.kind) {
35
+ case "completed":
36
+ return completedCodeQuery(map(result.data));
37
+ case "partial":
38
+ return partialCodeQuery(map(result.data), result.reason);
39
+ case "unavailable":
40
+ return unavailableCodeQuery(result.reason);
41
+ }
42
+ }